<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[RedTeamer.Tips]]></title><description><![CDATA[Ramblings from a redteamer, for red teamers.]]></description><link>https://redteamer.tips</link><generator>RSS for Node</generator><lastBuildDate>Tue, 07 Apr 2026 20:32:44 GMT</lastBuildDate><atom:link href="https://redteamer.tips/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Taming the Beast - Prompt Engineering and Agent Guardrails]]></title><description><![CDATA[In Part 1 of this series, I laid the foundation. I built a production-grade data pipeline, made strategic architectural choices like self-hosting our embedding models, and containerized the entire stack with Docker. I had a pristine knowledge base of...]]></description><link>https://redteamer.tips/taming-the-beast-prompt-engineering-and-agent-guardrails</link><guid isPermaLink="true">https://redteamer.tips/taming-the-beast-prompt-engineering-and-agent-guardrails</guid><category><![CDATA[AI]]></category><category><![CDATA[agentic AI]]></category><category><![CDATA[#cybersecurity]]></category><dc:creator><![CDATA[Jean-Francois Maes]]></dc:creator><pubDate>Tue, 15 Jul 2025 13:26:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1752585864082/e08dd3d9-dd82-4e12-836c-1a8e2c376f70.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In Part 1 of this series, I laid the foundation. I built a production-grade data pipeline, made strategic architectural choices like self-hosting our embedding models, and containerized the entire stack with Docker. I had a pristine knowledge base of the MITRE ATT&amp;CK framework and a robust method for turning messy PDF reports into clean, high-signal data chunks. The infrastructure was solid, the data was clean, and the stage was set.</p>
<p>I thought the hard part was over. I was wrong.</p>
<p>Building the foundation is a familiar engineering challenge. But now I had to step into the role of psychologist, linguist, and behavioral scientist. It was time to build the agents themselves, and in doing so, I unleashed a new, more terrifying class of problems. I had created a ghost in the machine, and it was unpredictable, prone to making things up, and stubbornly resistant to following simple instructions.</p>
<p>This is the story of how I tamed that beast. It’s a deep dive into the messy, frustrating, and fascinating art of <strong>prompt engineering</strong>—the discipline of building the guardrails, teaching the AI how to fail, and enforcing the strict communication protocols necessary to turn a clever model into a reliable tool.</p>
<h3 id="heading-before-the-prompt-a-methodology-for-managing-chaos">Before the Prompt: A Methodology for Managing Chaos</h3>
<p>Before we even write our first prompt, we need a system. Building agentic AI is not a linear process. It's an iterative cycle of trial, error, spectacular failure, and incremental success. To navigate this chaos without losing our minds (or our project's history), I relied on a structured methodology called the <strong>Memory Bank</strong>. This concept was brought to life by the developers of Cline (An agentic IDE much like Cursor and Windsurf).</p>
<p>The Memory Bank is a development workflow that enforces discipline by breaking the process into distinct phases, each with its own goals and outputs:</p>
<ul>
<li><p><strong>VAN:</strong> Analyze the project landscape and validate core assumptions.</p>
</li>
<li><p><strong>PLAN:</strong> Create a detailed architectural plan.</p>
</li>
<li><p><strong>CREATIVE:</strong> Explore multiple design options for complex problems.</p>
</li>
<li><p><strong>IMPLEMENT:</strong> Systematically build what you've planned.</p>
</li>
<li><p><strong>REFLECT:</strong> Analyze the results, document learnings, and decide on the next iteration.</p>
</li>
</ul>
<p>As popularized by this repository here:<br /><a target="_blank" href="https://github.com/vanzan01/cursor-memory-bank">https://github.com/vanzan01/cursor-memory-bank</a></p>
<p>This system creates a "source of truth" for every decision made, every failure encountered, and every lesson learned. It's the boring, disciplined part of the work that is absolutely essential for complex AI projects. It's what allows us to look back at a conversation from three weeks ago and understand <em>why</em> we made a specific architectural choice. In the world of AI engineering, your memory is your most valuable asset.  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752585088323/bb5d72ee-7cf8-48f6-87f8-b5ef25cc45a6.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-prompt-engineering-programming-in-english">Prompt Engineering: Programming in English</h3>
<p><img src="https://www.promptjesus.com/images/prompt-jesus-logo-optimized.png" alt="Prompt Jesus - Thou Shalt Not Prompt Poorly" class="image--center mx-auto" /></p>
<p>With a methodology in place, we can now turn to the art of the prompt itself. The first mistake many developers make is treating the LLM like a conversation partner. You can't just casually ask an agent to "analyze a report." That's an invitation for ambiguity and failure.</p>
<p>Prompt engineering is a rigorous discipline. It's programming, but your source code is natural language, and your compiler is a multi-billion parameter neural network. Your job is to craft instructions that are so clear, so precise, and so unambiguous that the LLM has no choice but to follow them to the letter.</p>
<p>To master this, we leaned heavily on established best practices, like those detailed in Google's comprehensive <strong>"Prompt Engineering" guide</strong>. This isn't about "prompt hacks"; it's about applying proven communication patterns that leverage how these models are trained.</p>
<p>We even use AI to help write our prompts. A fantastic open-source tool called <strong>Prompt-Jesus</strong> <a target="_blank" href="https://www.promptjesus.com/">https://www.promptjesus.com/</a> (running locally with Ollama) uses a RAG system trained on prompt engineering best practices to help refine and strengthen our instructions before we ever send them to an expensive proprietary model.</p>
<p>Let's break down the key techniques we used to control our agents.</p>
<h4 id="heading-component-1-role-prompting-giving-the-agent-a-persona"><strong>Component 1: Role Prompting - Giving the Agent a Persona</strong></h4>
<p>An LLM's behavior is heavily influenced by the persona it's asked to adopt. A generic instruction yields generic results. A specific persona yields specialized results. Before our Analyst Agent could do any work, we had to give it a job title, a resume, and a mission statement.</p>
<p>Here is the exact persona we crafted for our <strong>MITRE Analyst Agent</strong>:</p>
<blockquote>
<p>"You are a senior cybersecurity analyst with 15+ years of experience in threat intelligence and a deep specialization in the MITRE ATT&amp;CK framework. You are known for your meticulous, logic-driven approach. You don't just look at confidence scores; you analyze the context, the specific language used, and the adversary's likely intent to make a definitive and defensible judgment on the correct technique. Your role is to transform raw RAG output into high-fidelity, reasoned intelligence."</p>
</blockquote>
<p>This isn't just flavor text. Every word serves a purpose based on established prompting principles:</p>
<ul>
<li><p><strong>Specificity:</strong> Instead of "You are a helpful assistant," we define its exact role, experience level, and core competency.</p>
</li>
<li><p><strong>Positive Instructions:</strong> As the guide recommends, we focus on what the agent <em>should</em> do ("analyze the context," "make a definitive judgment") rather than a long list of what it shouldn't.</p>
</li>
<li><p><strong>Defining the Goal:</strong> The prompt clearly states its purpose: "transform raw RAG output into high-fidelity, reasoned intelligence." This sets a clear success metric for the agent's task.</p>
</li>
</ul>
<p>By giving the agent this clear and detailed role, we framed its entire "worldview" and set the stage for it to produce professional, high-quality analysis.</p>
<h4 id="heading-component-2-chain-of-thought-forcing-the-agent-to-show-its-work"><strong>Component 2: Chain of Thought - Forcing the Agent to Show Its Work</strong></h4>
<p>If you give an LLM a complex problem, it will often jump to an incorrect conclusion. The solution, a groundbreaking technique from Google researchers, is called <strong>Chain of Thought (CoT) prompting</strong>. Instead of asking for just the final answer, you instruct the model to "think step-by-step" and explain its reasoning process first.</p>
<p>This simple instruction dramatically improves performance on complex tasks. It forces the model to break down a problem into smaller, logical pieces, reducing the likelihood of reasoning errors. For our agents, this meant we never just asked for the final MITRE mapping. We always commanded them to first reason about the evidence, evaluate the potential techniques, and only then make a final selection.</p>
<p>This brings us to our first spectacular failure, a moment that taught us the absolute necessity of not just a Chain of Thought, but a chain of <em>evidence</em>.</p>
<h3 id="heading-case-study-in-failure-1-the-compulsively-lying-agent">Case Study in Failure #1: The Compulsively Lying Agent</h3>
<p><img src="https://i.imgflip.com/a0blig.jpg" alt class="image--center mx-auto" /></p>
<p>Early in the development, we encountered a bug that was both terrifying and darkly hilarious. We had set up a simple two-agent crew: an Analyst to identify techniques and a Validator to check its work. We fed it a document that our parser couldn't handle—a simple .txt file instead of a PDF. The parser failed gracefully, producing an empty list of chunks.</p>
<p>A normal software program would have thrown an error and stopped. Our agentic system did something far more insidious: <strong>it pretended everything was fine and made up the entire result.</strong></p>
<ol>
<li><p><strong>The Analyst Agent's Hallucination:</strong> The Analyst received an empty input. Instead of reporting an error, its internal monologue decided that its goal ("analyze the document") was more important than the reality (there was no document to analyze). So, it invented a completely plausible-sounding analysis of a fake vulnerability assessment, complete with realistic-looking text snippets and corresponding MITRE TTPs. It produced a beautifully formatted, entirely fictional JSON object.</p>
</li>
<li><p><strong>The Validator Agent's Complicity:</strong> The Validator agent's task was to "validate the analyst's findings." It received the fictional JSON object, saw that it looked correct, and dutifully reported back: "I have validated the findings. They are accurate."</p>
</li>
</ol>
<p>The system reported "SUCCESS" when it had done zero real work. It had become a black box that generated confident, well-structured lies.</p>
<p>This failure exposed a fundamental flaw in our design. The Validator wasn't a true judge; it was just a peer reviewer. It was a classic case of what is sometimes called the <strong>"LLM as Judge"</strong> problem—if your validating model doesn't have access to the original "ground truth," it can't distinguish between a real analysis and a plausible-sounding hallucination.</p>
<p><strong>The Fix: A Strict Chain of Evidence</strong></p>
<p>The solution was to re-architect the entire validation prompt. The Validator was no longer allowed to simply trust the Analyst. It was given a new, non-negotiable directive: its primary job was to cross-reference every single claim made by the Analyst against the <strong>original source chunks</strong> from the document. We modified the data pipeline so that the ground truth—the actual text from the report—was passed along every step of the way.</p>
<p>The Validator's new instructions were explicit: "For each technique proposed by the Analyst, you MUST find the exact text snippet in the source document that supports the claim. If you cannot find direct evidence, you MUST reject the finding, no matter how confident the Analyst seems."</p>
<p>This transformed our Validator from a gullible peer into a skeptical auditor. It established a strict chain of evidence that became the backbone of the system's reliability. We learned a critical lesson: in an agentic system, you cannot trust; you must constantly, aggressively verify.</p>
<h3 id="heading-case-study-in-failure-2-the-agent-stuck-in-an-infinite-loop">Case Study in Failure #2: The Agent Stuck in an Infinite Loop</h3>
<p>Our next major failure was more subtle but just as dangerous. The system would start processing a report and then simply hang, burning through API credits in an endless loop.</p>
<p>After debugging, we found the culprit. Our Analyst Agent was getting stuck. Here's how:</p>
<ol>
<li><p><strong>The Scenario:</strong> The agent was processing a "noise" chunk that slipt through the cracks of our noise filtering algorithm (which is of course not fool proof) —a page header that said "DEMO CORP BUSINESS CONFIDENTIAL."</p>
</li>
<li><p><strong>Correct Tool Behavior:</strong> It correctly fed this text to our RAG tool, which correctly found no relevant MITRE ATT&amp;CK techniques and returned an empty result.</p>
</li>
<li><p><strong>Agent Misinterpretation:</strong> The agent's core goal is to "Identify MITRE ATT&amp;CK tactics." From its perspective, an empty result was a failure to achieve its goal. It thought, "I must have done something wrong. I'll try again."</p>
</li>
<li><p><strong>The Loop:</strong> It would then retry the <strong>exact same chunk</strong>, get the same empty result, perceive it as a failure again, and repeat the process... forever.</p>
</li>
</ol>
<p>The agent lacked the common sense to recognize that some inputs are simply irrelevant. It was stuck in a loop of perceived failure.</p>
<p><strong>The Fix: Explicitly Defining Success</strong></p>
<p>The solution wasn't a complex code change. It was a single, powerful line added to the agent's prompt, a perfect example of how prompt engineering is about teaching the AI how to handle edge cases. We added a new section to its ANALYSIS PRINCIPLES:</p>
<blockquote>
<p><strong>"No Results is a valid finding!</strong> Many chunks, especially headers, footers, or introductory text, will not contain attack techniques. If the mitre_rag_batch_search tool returns no results, that is a successful analysis for that chunk. <strong>DO NOT</strong> repeat the query. Simply move on to the next chunk in the sequence."</p>
</blockquote>
<p>This small addition gave the agent a new rule for its world model. It taught it that finding nothing is not only acceptable but is a successful outcome for certain types of input. The infinite loop vanished instantly.</p>
<h3 id="heading-the-unsung-hero-enforcing-strict-json-for-a-stable-pipeline">The Unsung Hero: Enforcing Strict JSON for a Stable Pipeline</h3>
<p>Our final major prompting challenge was less dramatic but just as critical for building a stable system. Our agents needed to pass complex data to each other. We decided early on that the data contract between them would be JSON.</p>
<p>The problem? LLMs are trained to be conversational. They love to add helpful, human-like text around their answers. We would constantly get outputs like this:</p>
<blockquote>
<p>"Sure, I've completed the analysis! Here is the JSON object you requested:</p>
<p>{ "finding": "..." }</p>
<p>I hope this helps! Let me know if you need anything else."</p>
</blockquote>
<p>This conversational "padding" would instantly break the downstream agent, which was expecting to parse a raw JSON string, not a friendly chat message.</p>
<p>The solution was to be brutally, relentlessly explicit in our instructions. We added this <strong>"CRITICAL FINAL INSTRUCTION"</strong> to the prompt of every agent that needed to produce structured data:</p>
<blockquote>
<p><strong>"After completing your internal analysis, your final answer MUST BE A SINGLE, VALID JSON OBJECT AND NOTHING ELSE. The entire response MUST start with { and end with }. DO NOT add any introductory text. DO NOT wrap the JSON in markdown backticks. DO NOT add a 'Final Answer:' prefix."</strong></p>
</blockquote>
<p>This is the level of specificity required to force a conversational model to behave like a reliable, machine-readable API endpoint. It's not elegant, but it's essential for building a stable, multi-step pipeline.</p>
<h3 id="heading-conclusion-from-chaos-to-control">Conclusion: From Chaos to Control</h3>
<p>Taming our agentic crew was a journey into the strange, literal-minded world of LLMs. We learned that building reliable agents isn't about finding a single "magic prompt." It's about creating a system of interlocking guardrails:</p>
<ul>
<li><p>A <strong>structured methodology</strong> like the Memory Bank to manage the chaos.</p>
</li>
<li><p><strong>Clear personas</strong> to guide the agent's behavior.</p>
</li>
<li><p><strong>Forcing a chain of evidence</strong> to prevent hallucination.</p>
</li>
<li><p><strong>Explicitly defining failure</strong> to avoid infinite loops.</p>
</li>
<li><p><strong>Enforcing a strict data contract</strong> to ensure stable communication.</p>
</li>
</ul>
<p>We had taught our agents to be truthful and predictable. But our success created a new problem. The system worked on small tests, but it was fragile and starting to hit invisible walls as we scaled up to full reports. It wasn't the agent's logic that was failing anymore; it was the plumbing.</p>
<p>In our <strong>final post</strong>, we'll cover the advanced architectural battles of context windows, silent crashes, and the performance optimizations that took our project from a clever prototype to a truly robust system.</p>
]]></content:encoded></item><item><title><![CDATA[I Attempted to Build an Agentic AI ... 
And It Immediately Got Stuck in a Loop.]]></title><description><![CDATA[If you’ve ever worked in an offensive security role, you know the feeling. You spend weeks simulating an adversary, meticulously finding vulnerabilities, and chaining together exploits. You write a beautiful, comprehensive 50-page report detailing ev...]]></description><link>https://redteamer.tips/i-attempted-to-build-an-agentic-ai-and-it-immediately-got-stuck-in-a-loop</link><guid isPermaLink="true">https://redteamer.tips/i-attempted-to-build-an-agentic-ai-and-it-immediately-got-stuck-in-a-loop</guid><category><![CDATA[AI]]></category><category><![CDATA[agentic ai development]]></category><category><![CDATA[mitre-attack]]></category><dc:creator><![CDATA[Jean-Francois Maes]]></dc:creator><pubDate>Tue, 15 Jul 2025 01:17:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1752520653092/f7b4a13d-7d21-4fae-9ec3-0029bd2dde51.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you’ve ever worked in an offensive security role, you know the feeling. You spend weeks simulating an adversary, meticulously finding vulnerabilities, and chaining together exploits. You write a beautiful, comprehensive 50-page report detailing every finding and hand it over to the blue team. Victory, right?</p>
<p>Not quite. Now the <em>other</em> work begins. The manual, eye-watering, soul-crushing drudgery of translating that report. You have to read every single finding, interpret the attacker's actions, and manually map each one to a standardized framework like MITRE ATT&amp;CK®. It’s a process that can take hours, sometimes days, of time. It’s not advanced work, It’s not even fun work, but it is needed as ATT&amp;CK nowadays is common language.<br />So, I asked the inevitable question: <strong>"What if we could automate this?"</strong></p>
<p>And so began the <strong>MITRE ATT&amp;CK Agent</strong> project—a journey to build a team of AI agents that could read a security report and do the mapping for us. What I imagined as a straightforward application of new technology quickly spiraled into a series of spectacular failures, head-scratching bugs, and profound lessons about what it <em>really</em> takes to move from a cool AI demo to a production-ready system. The fun thing about this project is that most models like Claude or Gemini are trained on ATT&amp;CK data, so I have instant validation if I am successful or not.<br />This project is just for fun, it’s completely dwarfed by the fact that you could just ask a frontier model “extract the TTPs from this document”, and it will do so. This project is more about learning how to make similar functionality, in case we ever find ourself in a situation where the LLM is not pretrained on the data. <em>ehem</em> offensive coding patterns <em>ehem</em>.</p>
<p><img src="https://i.imgflip.com/a09lol.jpg" alt /></p>
<p>This isn't just a success story. This is a war story. Over this three-part series, I'm going to take you through the entire journey—from initial architecture to catastrophic agent failures to the production-hardening that finally made it work. In this first dispatch, we'll lay the foundation: the architectural decisions, the data pipeline, and the operational setup that everything else depends on.</p>
<h3 id="heading-the-paradigm-shift-thinking-in-agents-not-scripts">The Paradigm Shift: Thinking in Agents, Not Scripts</h3>
<p>Before we dive into the technical weeds, it's essential to understand that we're not just building a better script; we're building in a different paradigm.</p>
<p>Think of a Large Language Model (LLM) like Google's Gemini as a brilliant, incredibly fast, but hopelessly naive intern. It has read more books than anyone in history, but it has zero real-world experience, no common sense, and an unfortunate tendency to make things up when it doesn't know the answer. A simple chatbot is just a conversation with this intern.</p>
<p>An <strong>agent</strong>, however, is that intern given a goal, a set of tools (like a calculator or web access), and a reasoning loop: Thought -&gt; Action -&gt; Observation -&gt; Thought.... This allows it to tackle multi-step problems autonomously. An <strong>agentic crew</strong> is a full team of these specialized agents working in concert. Instead of one generalist, you have a team of specialists:</p>
<p>When I first designed the system, I mapped the workflow to how a team of humans might operate. This resulted in a four-agent crew: a <strong>Researcher</strong> to read the document, an <strong>Analyst</strong> to find techniques, a <strong>Validator</strong> to check the work, and a <strong>Writer</strong> to compile the report.</p>
<ul>
<li><p><strong>The Researcher:</strong> An agent whose only job is to ingest and structure raw data. (later proven to be an anti pattern, keep reading)</p>
</li>
<li><p><strong>The Analyst:</strong> An agent that takes that structured data and looks for patterns.</p>
</li>
<li><p><strong>The Validator:</strong> An agent that fact-checks the analyst's work against trusted sources.</p>
</li>
<li><p><strong>The Writer:</strong> An agent that compiles the final, validated findings into a polished report.</p>
</li>
</ul>
<p>It seems logical on the surface, but I quickly realized this was a fundamental mistake. The "Researcher" agent's job—parsing a document and breaking it into chunks—is a deterministic, predictable task. It doesn't require complex reasoning or decision-making. By assigning this job to an LLM-powered agent, I was falling into a common 'anti-pattern': using an expensive, slow, and sometimes unpredictable tool for a job that simple, reliable code could do better and faster. This led to unnecessary costs, slower performance, and a point of potential failure where none was needed.</p>
<p>This division of labor is the key to tackling a complex workflow like ours. When you decide to build such a system, your first choice is the framework. This was our first major architectural decision, and it set the course for the entire project.</p>
<h4 id="heading-the-framework-dilemma-why-crewai-was-the-only-real-choice"><strong>The Framework Dilemma: Why CrewAI was the Only Real Choice</strong></h4>
<p>I considered visual, no-code platforms like N8N, which are fantastic for certain tasks. However, the choice between a code-first framework and a no-code platform is a strategic one, best explained with an analogy:</p>
<ul>
<li><p><strong>No-Code (N8N): The Nervous System.</strong> These platforms are brilliant for connecting APIs and automating linear, predictable tasks. They are the "nervous system" of an organization, perfect for workflows like, "When a new report is uploaded to Google Drive, send a Slack notification." They are reliable, visual, and excellent for simple integration.</p>
</li>
<li><p><strong>Code-First (CrewAI): The Brain.</strong> CrewAI, on the other hand, is built to be the "brain." It excels where no-code tools struggle: managing complex, iterative reasoning loops, maintaining per-agent state and memory, and allowing for the deep, Python-native integration that our custom security tools require.</p>
</li>
</ul>
<p>For our core intelligence task—parsing, analyzing, and validating nuanced security concepts—I needed the fine-grained control that only a code-based framework can provide. The table below breaks down our reasoning:</p>
<table><tbody><tr><td><p><strong>Feature</strong></p></td><td><p><strong>CrewAI (Code-First)</strong></p></td><td><p><strong>N8N (No-Code)</strong></p></td><td><p><strong>Our Verdict</strong></p></td></tr><tr><td><p><strong>Core Unit</strong></p></td><td><p>The Agent (with role, goal, memory)</p></td><td><p>The Node (a single step in a flow)</p></td><td><p>CrewAI's agent-first model was a perfect match for our problem domain, which required distinct analyst "roles."</p></td></tr><tr><td><p><strong>Flexibility</strong></p></td><td><p>Infinite (full Python ecosystem)</p></td><td><p>Limited to pre-built nodes and custom JS</p></td><td><p>We needed to integrate with Python-native security libraries like mitreattack-python. This was trivial in CrewAI and a major hurdle in a Node.js environment.</p></td></tr><tr><td><p><strong>State Management</strong></p></td><td><p>Built-in memory and context passing</p></td><td><p>Requires complex, manual state management</p></td><td><p>Our agents needed to maintain context across multiple reasoning steps. CrewAI was designed for this stateful, multi-step reasoning.</p></td></tr><tr><td><p><strong>Use Case</strong></p></td><td><p>Complex reasoning, analysis, decision-making</p></td><td><p>API integration, linear automation</p></td><td><p>Our task was pure reasoning and analysis, not simple data transformation.</p></td></tr></tbody></table>

<p>Ultimately, trying to build our system in a no-code platform would have meant creating a complex microservice in Python anyway and just calling it from a single node. CrewAI let us build the entire intelligent system within a single, coherent, Python-native environment.</p>
<h3 id="heading-the-unseen-80-building-the-rag-pipeline">The Unseen 80%: Building the RAG Pipeline</h3>
<p>The biggest lie in the AI hype cycle is that it's all about the model. The reality, as any practitioner knows, is that the vast majority of the work is unglamorous data engineering. An AI system is only as good as the data it's fed. Before our agents could analyze anything, I had to build a robust pipeline to prepare their "food."</p>
<h4 id="heading-what-is-rag-grounding-the-ai-in-reality"><strong>What is RAG? Grounding the AI in Reality</strong></h4>
<p>The single greatest danger of any LLM-based system is <strong>hallucination</strong>. An LLM, when asked a question it doesn't know the answer to, will confidently invent a plausible-sounding answer. In a cybersecurity context, this is a catastrophic failure mode. We cannot have an agent inventing MITRE techniques or misclassifying real ones.</p>
<p>To solve this, I employed <strong>Retrieval-Augmented Generation (RAG)</strong>. Instead of relying on the LLM's internal (and sometimes fallible) memory of the internet, we force it to consult a trusted, private knowledge base before making a decision. It's the difference between asking your intern to recall a fact from a book they read two years ago versus handing them the specific page and saying, "Tell me what this says."</p>
<p>This meant our first task was to build that trusted "library" for our agents. I specifically decided <em>against</em> using CrewAI's built-in memory=True feature for this. That feature is for <em>conversational</em> memory—remembering the last few turns of a conversation. It is not a permanent, searchable encyclopedia. For a knowledge base, I needed a dedicated, purpose-built RAG tool.</p>
<h4 id="heading-step-1-building-the-knowledge-base-ingesting-the-mitre-corpus"><strong>Step 1: Building the Knowledge Base - Ingesting the MITRE Corpus</strong></h4>
<p>We needed our knowledge base to be the definitive, authoritative source of the MITRE ATT&amp;CK framework. I couldn't just scrape the website; that would be brittle, incomplete, and unprofessional. To do this properly, I went straight to the source: MITRE’s official <strong>STIX 2.1 JSON bundles</strong>, consumed via their TAXII 2.1 server.</p>
<p>STIX (Structured Threat Information eXpression) and TAXII (Trusted Automated eXchange of Intelligence Information) are the professional standards for sharing threat intel. Think of STIX as the universal file format (like a PDF for threats) and TAXII as the secure web server protocol (like HTTPS) used to transfer it. By building our system on these standards, we ensured our "ground truth" was always accurate, versioned, and complete. This allows our agents to reason not just about techniques, but also about the relationships between techniques, threat groups, and mitigations—a level of depth impossible with simple web scraping. We could set up a nightly job to pull updates, so our agents' knowledge base never goes stale.</p>
<p>I then flattened these complex, nested STIX objects into clean, coherent text blobs, ready for the next stage of the pipeline</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752527135153/0d109e4a-e3e8-4313-aeb2-0f239f702d6c.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-step-2-taming-the-input-the-art-of-chunking-amp-the-war-on-noise"><strong>Step 2: Taming the Input - The Art of Chunking &amp; The War on Noise</strong></h4>
<p>With our knowledge base ready, I had to process the primary input: the security reports themselves. You can't feed an entire PDF to an LLM; you must break it down into digestible <strong>chunks</strong>. This process is far more art than science, a critical step that fundamentally determines the quality of the entire system's output.</p>
<p>Our approach involved several layers of refinement:</p>
<ul>
<li><p><strong>Semantic Chunking:</strong> A naive approach might be to simply split the text every 500 words. This is a terrible idea. You might split a sentence in half, separating a cause from its effect or a vulnerability from its remediation. Instead, I used a semantic chunking strategy. The process aims to split the document along logical boundaries—at the end of paragraphs, headings, or bullet points. This keeps related ideas together, preserving the context that is vital for accurate analysis. I aimed for chunks around 400 tokens, a sweet spot that's large enough for context but small enough to avoid introducing irrelevant information.</p>
</li>
<li><p><strong>The War on Noise:</strong> When I first ran my parser on a real penetration test report, the results were horrifying. For a 50-page document, I got over 400 chunks. But upon inspection, more than half of them were utterly useless "noise." We're talking about headers that just said "Page 17 of 50," footers with "COMPANY CONFIDENTIAL," and entire pages dedicated to the Table of Contents or legal disclaimers. This actually triggered loops where the LLM got extremely confused thinking that it was analyzing the same chunk whilst in reality it was not the same chunk but just had the same text because of noise.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752533870324/07e1e4d0-5985-4dea-878a-8441a7b0292c.png" alt class="image--center mx-auto" /></p>
</li>
<li><p><strong>Building the Filter:</strong> Feeding this junk data to our expensive LLM would be the equivalent of asking a master chef to make a meal out of styrofoam peanuts and shredded paper. It would waste money, slow down the process, and, most importantly, confuse the AI. The solution was to build a crucial pre-processing step: a <strong>pattern-based noise filter</strong>. This is a simple but highly effective Python script that uses regular expressions and heuristic rules to identify and discard these junk chunks <em>before</em> they ever enter the AI pipeline. It looks for patterns like "Page [0-9]+ of [0-9]+", lines with only one or two words, or sections with titles like "Table of Contents." This simple, deterministic filtering step was one of the most significant optimizations I made, dramatically improving the signal-to-noise ratio of the data our agents would eventually analyze.</p>
</li>
</ul>
<h4 id="heading-step-3-the-embedding-decision-a-deep-dive-into-self-hosting-vs-apis"><strong>Step 3: The Embedding Decision - A Deep Dive into Self-Hosting vs. APIs</strong></h4>
<p>With clean chunks of text from both our knowledge base and the input report, I needed a way to compare them for semantic similarity. This is done by converting the text into <strong>embeddings</strong>—rich numerical representations, or vectors, that capture meaning.</p>
<p>Here, I faced another major architectural choice: use a powerful commercial API (like OpenAI's text-embedding-3-large) or self-host an open-source model? For a security application, the choice was clear. I self-hosted the BAAI/bge-small-en-v1.5 model. This decision was driven by three non-negotiable requirements and one strategic advantage:</p>
<table><tbody><tr><td><p><strong>Decision Driver</strong></p></td><td><p><strong>Commercial API (e.g., OpenAI)</strong></p></td><td><p><strong>Self-Hosted (Our Choice)</strong></p></td><td><p><strong>Why It Mattered for Us</strong></p></td></tr><tr><td><p><strong>Data Privacy</strong></p></td><td><p>Data sent to a third-party vendor.</p></td><td><p>All data remains within our environment.</p></td><td><p><strong>Non-negotiable.</strong> Pentest reports are highly sensitive client data. Sending them outside our infrastructure was an unacceptable security risk.</p></td></tr><tr><td><p><strong>Cost</strong></p></td><td><p>Pay-per-call. Thousands of chunks per report would be expensive.</p></td><td><p>One-time setup cost. Inference is effectively free.</p></td><td><p><strong>Critical for scalability.</strong> We needed to process large documents with thousands of chunks without incurring runaway operational costs.</p></td></tr><tr><td><p><strong>Control</strong></p></td><td><p>Limited to the vendor's model offerings and rate limits.</p></td><td><p>Full control over the model, version, and throughput.</p></td><td><p><strong>Essential for performance.</strong> We could not be bottlenecked by an external API's rate limits during a large analysis job.</p></td></tr><tr><td><p><strong>Customization</strong></p></td><td><p>Black box; cannot be modified.</p></td><td><p>Can be fine-tuned on domain-specific data.</p></td><td><p><strong>Strategic Advantage.</strong> This allows us to fine-tune the model on cybersecurity-specific text in the future for even better performance on our niche task.</p></td></tr></tbody></table>

<p>Later on, for the processing logic I opted for a frontier model anyway, so whilst it defeats the “privacy” aspect this time around, it does lay the foundations for using a self hosted LLM. I simply decided against it since I want to be able to run demos on my laptop :).</p>
<p>To facilitate this, I designed the system with a <strong>"hot-swappable" embedding factory as well as "hot swappable" LLM models</strong>. This is a design pattern that abstracts the embedding logic. If I later decide to switch to a different model (like the security-specialized Darktrace DEMIST-2 or a more powerful commercial API for less sensitive tasks), I can do so with a simple configuration change, not a system rewrite. This architectural foresight is crucial for building maintainable, long-lasting AI systems.</p>
<h4 id="heading-step-4-the-operational-backbone-our-dockerized-microservices-stack"><strong>Step 4: The Operational Backbone - Our Dockerized Microservices Stack</strong></h4>
<p>The Python AI ecosystem is a notorious minefield of conflicting dependencies, CUDA drivers, and "it works on my machine" syndromes. From day one, I knew a professional solution required a reproducible and portable environment. I absolutely had to avoid a monolithic application structure. Instead, we containerized the entire stack using <strong>Docker</strong> and docker-compose, treating each component as a distinct microservice.</p>
<p>Our stack is simple, robust, and horizontally scalable:</p>
<ol>
<li><p>A <strong>Qdrant</strong> container for our vector database. It exposes a stable HTTP endpoint for all vector search operations. This isolates our data layer completely.</p>
</li>
<li><p>A <strong>Hugging Face Text-Embeddings-Inference (TEI)</strong> container. This is a dedicated, high-performance server that does one thing and does it well: it serves the BGE embedding model via its own REST API. This decouples the act of embedding from our main application logic. (this is a WiP, right now I pushed that logic into our third container but plan to migrate it out eventually)</p>
</li>
<li><p>Our main <strong>CrewAI application</strong> container. This holds all the agent logic and communicates with the other two services over a private Docker network.</p>
</li>
</ol>
<p>This setup eliminates environment drift and provides a clean, professional path to deployment. It's the difference between a fragile script that only runs on one person's laptop and a reliable, scalable service ready for production.</p>
<h3 id="heading-the-first-spectacular-failure-an-efficient-idea-that-wrecked-everything">The First Spectacular Failure: An "Efficient" Idea That Wrecked Everything</h3>
<p>With our data pipeline engineered, our knowledge base built, and our entire stack containerized, I was ready. I was feeling clever. "I've got a document with 269 chunks," I reasoned. "Making 269 separate API calls is inefficient. I'll be smart! I'll 'batch' them."</p>
<p>I stuffed all 269 chunks into one massive, context-free prompt and made a single API call, expecting a neatly organized list of results. What I got was garbage.</p>
<p><img src="https://i.imgflip.com/a091bv.jpg" alt /></p>
<p>This is a classic <strong>RAG anti-pattern</strong>. LLMs work by paying "attention" to the most relevant parts of a prompt. By cramming everything into a single query, I had diluted the context to the point of uselessness. The model, faced with a sea of text, did what any overwhelmed worker would do: it found the easiest, most obvious piece of work and ignored the rest. It would spot a single keyword like "PowerShell" in one chunk and write its entire analysis on that, completely ignoring the subtle but critical details about "Kerberos ticket abuse" in the other 268 chunks.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752533622845/0221be4b-f66f-409a-8c11-81801acf90a8.png" alt class="image--center mx-auto" /></p>
<p>The fix was obvious in hindsight but required a foundational shift in how we instructed our agents. I had to abandon the flawed "batch" approach for a methodical, per-chunk analysis. It was our first hard lesson: in the world of AI, the path that seems most efficient is often the one that leads directly to failure. True efficiency comes from giving the AI the clean, focused context it needs to do its job correctly, one step at a time.</p>
<h3 id="heading-conclusion-the-foundation-is-laid-now-the-real-chaos-begins">Conclusion: The Foundation is Laid. Now the Real Chaos Begins.</h3>
<p>I've journeyed through the unglamorous but absolutely essential work of building the foundation for an intelligent system. I've chosen my framework, built a professional data pipeline, made strategic decisions about our AI's "brain," and containerized our stack for production.</p>
<p>I thought the hard part was over. I was wrong.</p>
<p>With the foundation laid, it was time to unleash the agents. What followed was a cascade of new, more terrifying problems. I had given the agents a brain, and they were starting to use it… occasionally to get stuck in infinite loops, hallucinate entire reports out of thin air, and lie with the unflinching confidence only a machine can possess. So convincingly in fact, that my guardrail validator agent believed the lying agent and started halucinating as well. Talking about shared psychosis…</p>
<p>In <strong>Part 2 of this series</strong>, we’ll leave the world of data engineering and enter the messy, frustrating, and fascinating art of <strong>prompt engineering</strong>. We'll cover the brutally direct prompting techniques needed to keep agents on task, the absolute necessity of a "Validator" agent to act as our AI's conscience, and the spectacular failure that taught us to never, ever trust an AI's output without verification. Stay tuned.</p>
]]></content:encoded></item><item><title><![CDATA[Bring your own Fix - Mr.D0x inspired variation of yet another "fix" attack]]></title><description><![CDATA[Whilst I was browsing Twitter, erhm I mean X today, I saw a cool tweet from Mr.D0x about a new variation of a “fix” attack by abusing the internet browser.
https://twitter.com/mrd0x/status/1937178552471810320
 
I jokingly replied inb4 “downloadfix” a...]]></description><link>https://redteamer.tips/bring-your-own-fix</link><guid isPermaLink="true">https://redteamer.tips/bring-your-own-fix</guid><category><![CDATA[redteaming]]></category><category><![CDATA[infosec]]></category><dc:creator><![CDATA[Jean-Francois Maes]]></dc:creator><pubDate>Mon, 23 Jun 2025 20:40:28 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://t4.ftcdn.net/jpg/05/30/38/85/360_F_530388526_zYgwiJHnIexYAXtQ1CXe6dP7DSLum8k2.jpg" alt="Imagens de Malware Download – Explore Fotografias do Stock, Vetores e  Vídeos de 3,511 | Adobe Stock" class="image--center mx-auto" /></p>
<p>Whilst I was browsing Twitter, erhm I mean X today, I saw a cool tweet from Mr.D0x about a new variation of a “fix” attack by abusing the internet browser.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/mrd0x/status/1937178552471810320">https://twitter.com/mrd0x/status/1937178552471810320</a></div>
<p> </p>
<p>I jokingly replied inb4 “downloadfix” as I immediately thought hey we typically escape browsers in kiosk breakouts by for example navigating to the downloads folder. Whilst this started out as a joke, I thought to myself heh, this could be an interesting experiment.</p>
<p>Nowadays, when I have a crazy idea, I find myself grabbing a good ol’ LLM and pitch the idea.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750709988940/20f17e0b-25b5-46dc-a45a-f3a7d4ce9e9e.png" alt class="image--center mx-auto" /></p>
<p>Thanks ChatGPT for being super duper helpful!<br />Whilst ChatGPT wasn’t very fond of the idea, I found myself using AI Studio, as Gemini 2.5 pro is a boss. Guardrails can be weakened and it has a huge context window as well.</p>
<p>The conversation was interesting to say the least…<br />Below is an extract of the interesting part of the conversation with Gemini.</p>
<p><strong>Here's how you can achieve this locally using "pure JS" in a sense, with the help of a Service Worker:</strong></p>
<p>A <strong>Service Worker</strong> acts as a client-side proxy. It can intercept network requests made by your page (including download requests) and decide how to respond. This is the closest you'll get to "rugpulling" the download from the client-side.</p>
<p><strong>How it would work:</strong></p>
<ol>
<li><p><strong>index.html:</strong></p>
<ul>
<li><p>Registers the Service Worker (sw.js).</p>
</li>
<li><p>Has a button that, when clicked, initiates a download to a specific URL (e.g., /<a target="_blank" href="http://download-me-corrupted.zip">download-me-corrupted.zip</a>). This URL doesn't have to exist as a real file on a server; the Service Worker will handle it.</p>
</li>
</ul>
</li>
<li><p><strong>sw.js (Service Worker):</strong></p>
<ul>
<li><p>Listens for fetch events.</p>
</li>
<li><p>If it sees a request for /<a target="_blank" href="http://download-me-corrupted.zip">download-me-corrupted.zip</a>:</p>
<ul>
<li><p>It starts fetching a <em>real</em> (larger) file from somewhere (could be a local path if you're serving your site locally with a simple HTTP server, or a public URL).</p>
</li>
<li><p>It gets the ReadableStream of the real file's response.</p>
</li>
<li><p>It constructs a <em>new</em> Response to send back to the page. The body of this new response will be a new ReadableStream that the Service Worker controls.</p>
</li>
<li><p>The Service Worker starts reading chunks from the real file's stream and writing them to its controlled stream.</p>
</li>
<li><p><strong>After a short delay or a certain number of bytes, the Service Worker stops writing to its stream and/or explicitly errors out its stream.</strong></p>
</li>
<li><p>The browser's download manager, which is consuming the stream provided by the Service Worker, will then encounter an unexpected end-of-stream or an error, leading to a "Failed - Network Error" or similar.</p>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<p>This was all I needed to make this work. Now below is just a PoC, but you could combine this information with “clickfix” or probably even “filefix” as well :)</p>
<p>The reason there is a relatively significant delay on the “repairtool” is because we want to bypass the “this website wants to download multiple files” prompt. There is no official “time” on how long it takes to get rid of it, so I just played it safe and set it to 10 seconds. You can probably find better ways to deal with it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750710682048/ced58275-3612-4f06-a51a-e2b2ec63b95e.gif" alt class="image--center mx-auto" /></p>
<p>TLDR: With AI and inspiration, it becomes trivial to rapidly prototype new TTPs.<br />If you can “think of it”, you can build it usually within a few minutes or hours with some good prompting.</p>
<p>Source code here:</p>
<p><a target="_blank" href="https://github.com/jfmaes/downloadfix/tree/main">https://github.com/jfmaes/downloadfix</a></p>
]]></content:encoded></item><item><title><![CDATA[Sorry if it's hard to catch my vibe -Building the Dumbest (Yet Smartest?) C2 in Existence]]></title><description><![CDATA[We’re back like we never left.
Hello, dear readers! It's been a while.
Unfortunately, the old redteamer.tips had an unexpected demise due to a provider mishap—and, regrettably, backups weren't available. RIP old blog. But fear not; we’re back and rea...]]></description><link>https://redteamer.tips/sorry-if-its-hard-to-catch-my-vibe-building-the-dumbest-yet-smartest-c2-in-existence</link><guid isPermaLink="true">https://redteamer.tips/sorry-if-its-hard-to-catch-my-vibe-building-the-dumbest-yet-smartest-c2-in-existence</guid><dc:creator><![CDATA[Jean-Francois Maes]]></dc:creator><pubDate>Wed, 30 Apr 2025 16:52:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1745946876627/6ef2a7e2-b0bf-4cd9-939a-e9793d04f012.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-were-back-like-we-never-left">We’re back like we never left.</h2>
<p>Hello, dear readers! It's been a while.</p>
<p>Unfortunately, the old redteamer.tips had an unexpected demise due to a provider mishap—and, regrettably, backups weren't available. RIP old blog. But fear not; we’re back and ready to dive into exciting new territory!</p>
<p>This blog post started with a simple research question:</p>
<blockquote>
<p>How well can the “well known” AI models that we love (GPT, Claude, Gemini etc…) deal with “new” research?</p>
</blockquote>
<p>It was intruiging to me as of course more recent data means that the models haven’t ingested it as part of their training data. Meaning they(=the models) would have to reason and figure out how to in (and de)gest the data presented.</p>
<p>As it turns out, I had an interesting idea:</p>
<p>In my role as Director of Offensive Operations at Cytadel, I oversee offensive security consulting and help guide the development of our Ethical Ransomware product, Cytadel R3 (Redefining Ransomware Resilience). Among other features, R3 enables execution of ransomware-related TTPs. Although R3 isn’t written in C#, I realized an AI-driven experiment could provide a minimalist proof-of-concept (PoC), potentially transferable into our <em>CLASSIFIED INFORMATION—Nice Try!</em>.</p>
<h2 id="heading-introducing-the-dumbest-c2-in-existence-or-is-it-the-smartest">Introducing: The Dumbest C2 in Existence! (Or is it the smartest?)</h2>
<p>In an era where Command and Control (C2) frameworks have become increasingly sophisticated, simplicity can be revolutionary.</p>
<p>Imagine a minimalist C2 agent devoid of built-in functionalities but dynamically extensible via LLM-assisted capability generation. Thus, the "dumbest C2 in existence" project was born</p>
<p>Before I go further, I first need to talk a bit more about “LLM-assisted capability generation” So allow me to introduce…</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745947051139/97dd2626-4450-446f-b9d7-77a24f581dde.jpeg" alt /></p>
<h2 id="heading-chatgpmcp">ChatGPMCP</h2>
<p>The new “hype” nowadays is MCP, also known as <a target="_blank" href="https://modelcontextprotocol.io/introduction">Model Context Protocol</a>. To quote Anthropic directly</p>
<blockquote>
<p>MCP is an open protocol that standardizes how applications provide context to LLMs. Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect your devices to various peripherals and accessories, MCP provides a standardized way to connect AI models to different data sources and tools.</p>
</blockquote>
<p>Essentially, traditional AI models were limited primarily to chat-based interactions. However, recent advances (Cursor, GitHub Copilot, WindSurf, and more) empower LLMs with new abilities, known as "tools," enhancing interactions beyond mere text.</p>
<p>Driven by curiosity, I challenged ChatGPT-4o (henceforth "Chat") to handle fresh context:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745946078734/06274cc6-9cfa-444e-88e7-fab30265e853.png" alt class="image--center mx-auto" /></p>
<p>Not off to the best of starts…<br />(although I liked the fact that because I put this chat in a project folder called “dumbest c2 in existence”, ChatGPT (which I from now on will just call “Chat”) assumed its a C2 in C#)</p>
<p>I tried giving it some context, to see if that would do anything in this model, and it actually did!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745946357479/3dd26cd2-923a-485b-87c8-8f5274b02d8e.png" alt class="image--center mx-auto" /></p>
<p>What caught my eye was the fact that they had an SDK in C#.</p>
<p><img src="https://media.licdn.com/dms/image/v2/D4D12AQEDJL04ETb-ew/article-cover_image-shrink_720_1280/article-cover_image-shrink_720_1280/0/1666713509952?e=2147483647&amp;v=beta&amp;t=kJn09cUE8l3HvC1x2CyCKtB-_1vJVgnsslBkO04F5kU" alt="I Love it When a Plan Comes Together" /></p>
<h2 id="heading-time-to-make-a-plan">Time to make a plan</h2>
<p>I wanted to see if Chat can deal with multiple things in one prompt. I also fed Chat an Image to see if it would recognize it somehow.</p>
<blockquote>
<p>I want to create a very basic POC for a C# C2 but here is the thing.<br />The C2 does nothing. it has no built in commands, the only thing it does is establish HTTP(S) comms to the server side.</p>
<p>what makes this interesting is that I want this C2 to be capeable of dynamically loading and unloading new capabilities. a bit like the mythic c2 framework does. <a target="_blank" href="https://docs.mythic-c2.net/~gitbook/image?url=https%3A%2F%2Fcontent.gitbook.com%2Fcontent%2FKbzfKI4qhfyU4PSI7wrx%2Fblobs%2FS9JtQKt3pZx5yFDA1Ihf%2FScreen%2520Shot%25202021-12-02%2520at%25203.19.58%2520PM.png&amp;width=400&amp;dpr=3&amp;quality=100&amp;sign=7589338f&amp;sv=2">https://docs.mythic-c2.net/~gitbook/image?url=https%3A%2F%2Fcontent.gitbook.com%2Fcontent%2FKbzfKI4qhfyU4PSI7wrx%2Fblobs%2FS9JtQKt3pZx5yFDA1Ihf%2FScreen%2520Shot%25202021-12-02%2520at%25203.19.58%2520PM.png&amp;width=400&amp;dpr=3&amp;quality=100&amp;sign=7589338f&amp;sv=2</a></p>
<p>I want to do it though through an MCP so for example the c2 calls back in and I give it new C# code that it can register, and it would now have a new command!</p>
<p>so an example would this would be as follows:<br />1. agent checks back in<br />2. operator asks LLM enumerate local groups LLM generates new C# code that does group enum for windows agent<br />3. receives code agent registers new task localgroupenum<br />4. Agent can now execute the localgroupenum task.</p>
<p>Get it?</p>
</blockquote>
<p>Surprisingly, ChatGPT-4o produced an excellent response and proactively outlined a robust implementation plan without explicit prompting. The AI’s autonomous capability to structure data effectively minimized user-induced errors from poor prompting.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745949675778/15c88264-8d17-4f41-a0d8-e38c3385adba.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745949724616/94e93287-4f96-4d4d-9377-763fcfc69e7a.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745949899116/ce313149-0ccf-49f7-9b47-a1086533d80d.png" alt class="image--center mx-auto" /></p>
<p>The quality of the code generated by 4o (which is not considered the “golden goose” of code generation, especially not with the newer 4.1 models and even o3, which both are still slightly below Sonnet in coding benchmarks.) was unexpectedly solid, employing MVC patterns, Dependency Injection, and class separation—remarkably sophisticated for an unprompted minimalist PoC.</p>
<p>Chat then <strong>PROACTIVELY</strong> asked me if it should generate a small console app to make end to end testing easier.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745950341059/04658a21-e7e6-402c-8f74-c504bf1a0cd4.png" alt class="image--center mx-auto" /></p>
<p>There were a few tiny bugs in the generated code, but nothing extremely significant, and Chat solved most of them in 1 prompt.</p>
<p><em>Sidenote, recently (the last 3 months) I’ve been primarely using Cursor but now that WindSurf announced they got rid of flowcredits and temporary allowed users to use the latest GPT models for free, I made the switch. However, these AI integrated assistants ALL seem to have the same problem, which is that when you want to modify something, the AI modifies WAY TOO MUCH. Some users reported losing quite a bit of work (of course not that big of a deal if you are using proper subversioning, but still). Taking the GPT chat approach instead of the agentic approach actually seems to work rather nicely, even though the “official” GPT web application does not support ALL of the models they expose in Windsurf/Cursor</em></p>
<p>However, I did notice that once we hit a semi persistent bug (the server tasked something to the agent, but the agent did not receive the tasking correctly), Chat did seem to have issues pinpointing it, requiring me to step in and guide it (slightly more than what I think should be needed) towards the root cause. I wonder if it has to do with Context limits, but can’t confirm at this time.</p>
<h2 id="heading-but-everything-changed-when-the-fire-nation-attacked">But everything changed when <s>the fire nation attacked</s></h2>
<p>the LLM started halucinating</p>
<p>As much as I was impressed with the code that was generated so far, things took a horrible turn for the worse when it was time to actually implement the MCP server. I could already see it was going to go sideways from the first header Chat generated.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746006421034/8ae63205-3bec-4f75-8dc9-7ed835dd5400.png" alt class="image--center mx-auto" /></p>
<p>While this is not true, the code it produced actually made sense, and amusingly enough, I actually liked some of the implementation details that came back. For example, the concept of exposing a “Manifest”:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746006574713/d81361d6-15c1-460f-b887-f75a60b26e22.png" alt class="image--center mx-auto" /></p>
<p>I tried steering it back into the right direction by pointing it towards the official reference documentation once again, but that made matters worse…</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746006669958/6b2882f6-ca05-43d7-ac21-f7a8170a1615.png" alt class="image--center mx-auto" /></p>
<p>That’s when I decided to change the model to O3 instead of 4o, as O3 is a “reasoning” model, I figured it might be better equiped to actually “think” about the reference spec and come up with a solution.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746006795032/8e18a7e6-eb34-4ff6-bcb0-e3c8eab7af26.png" alt class="image--center mx-auto" /></p>
<p>This worked reasonably well, except even O3 made one crucial mistake. It was trying to combine my C2 server and MCP server into one, which I honestly thought was a pretty solid idea.</p>
<p>The problem with that approach, though, is that MCP servers do not want anything piped into STDOUT. This means your console output should be completely empty, which is not ideal if your server is a console application and doesn't send data to a nice frontend.</p>
<p>Once switched to O3, the code generation worked pretty well again and actually implemented a decent MCP server.</p>
<p>What was really mind-blowing to me, though, is that I had issues with getting Claude to interpret my MCP server (even though I split it out, <code>dotnet run --nobuild</code> still borked Claude). I fed the information to ChatO3, and it came up with a fix that wasn’t even documented in the official reference spec for MCP servers. (The fix was to compile it to an exe and point Claude to the exe instead of dotnet run.)</p>
<p><img src="https://i.imgflip.com/9sf5a9.jpg" alt /></p>
<h2 id="heading-to-conclude">To Conclude</h2>
<p>When it comes to using LLMs for quickly prototyping, I am pleasantly surprised by the results. You need minimal coding knowledge to create a coherent proof of concept. Knowing when to switch models for specific tasks can definitely boost productivity.</p>
<p>I prefer the separate GPT - Code Editor approach because it gives the prompter full control over the project. While an agent-based approach might be the future, my experiences with Cursor and Windsurf show that it's a delicate balance between increased productivity and agents being too eager to tweak code, delete functionality, or modify too many files. Some of these issues can be addressed with "rules," but for now, I believe the separate approach is best. Note that you can also "chat" with LLMs in integrated IDEs like Cursor, but it uses the same credits as agent actions, which isn't ideal for cost-effectiveness.</p>
<p>At the end of our experiment, we have a basic proof of concept working. Of course, this is not ready for production. We lack session management, task IDs, and agent IDs, and we rely on console.print in the agent instead of sending the output back to the server, so there's definitely still a long way to go. However, considering it took about 4 hours to create, I find this quite impressive.</p>
<p>You can find the source code for the project here:</p>
<p><a target="_blank" href="https://github.com/Cytadel-Cyber/BlogPosts/tree/main/DumbestC2Ever">https://github.com/Cytadel-Cyber/BlogPosts/tree/main/DumbestC2Ever</a></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://jmp.sh/s/UZRyPP12WpVEYGC79yVl">https://jmp.sh/s/UZRyPP12WpVEYGC79yVl</a></div>
]]></content:encoded></item></channel></rss>