Here’s an uncomfortable thought about your AI coding assistant, to start with.
In Part 2 of this series, Foundry spent about two million tokens sweeping Langflow’s API and confirmed a bug where an endpoint protects itself with a “local connections only” check, and then decides what “local” means by trusting an HTTP header that any attacker can set. Good catch. Verified against the source, logged, fingerprinted, triaged.
Now imagine that tomorrow morning, a developer on another team opens their editor and asks their AI coding assistant: “add an admin endpoint that only accepts requests from localhost”.
What do you think the AI coding assistant writes?
My bet: some version of the same bug. It learned from the same public code everyone else did. The assistant didn’t read our Foundry report. Nobody told it.
That’s the problem with detection on its own. Every finding is a lesson the organization paid for, and the fastest-growing author of new code in your company has never heard any of them. AI coding assistants now write code faster than any security team can review it. So if prevention doesn’t reach the keystroke, detection becomes a treadmill: you find the same weakness classes forever, in fresh code written yesterday.
This post is about stopping the treadmill. The forge from Part 2 now gets a guard.
Meet the guard: Project CodeGuard
Project CodeGuard is an open-source, model-agnostic framework of security rules and skills for AI coding agents. Cisco originally developed it and donated it to the Coalition for Secure AI (CoSAI).
The idea is simple and powerful. Security guidance is written once, as plain Markdown, and translated into whatever format each assistant reads: .cursor/rules/ for Cursor; .github/instructions/ for GitHub Copilot; .windsurf/rules/ for Windsurf; skills for Codex, Devin, OpenCode, and others; and a plugin for Claude Code. There’s even an MCP server so an organization can serve a centrally managed rule set to every coding assistant.
The rules work at three moments in time:
- Before generation , steering planning and spec-driven work toward secure designs.
- During generation , so the assistant writes the secure pattern in the first place.
- After generation , as review guidance for the same agents.
The core rule set covers the classics: cryptography, input validation and injection, authentication, authorization, session management, supply chain, cloud and infrastructure, data protection.
The question this post answers: what happens when the rules aren’t just a well-curated generic set, but are learned from the real vulnerabilities in your own code?
The flywheel, in three turns
The Foundry specification puts one mechanism at its center: when the system finds something its rules don’t cover, it should tell you which rule to write next. The spec calls it a “detection-to-prevention flywheel”, and then deliberately leaves the mechanism unspecified. Building it concretely was the part of this project I enjoyed most.
In my real-world runs the flywheel turned three different ways.
Turn one: a noisy rule got sharper
The first wide Langflow run fired the “error-information-leak” rule on an idiom that appears all over the codebase: returning the raw text of an exception in an HTTP error. 72 near-identical findings. Technically valid but completely useless. That’s not 72 bugs. It’s one systemic issue wearing 72 hats.
The instinct is to argue with the output. The right move, though, is to treat a noisy rule as a signal about the rule. I tightened it to confirm only genuinely sensitive leaks (tracebacks, database, filesystem and subprocess errors, paths and queries) and to record the generic idiom once. On re-run, that class dropped from 72 findings to 6, and real authorization and path-traversal issues led what remained.
That’s the flywheel making detection better. Fewer findings, more signal.
Turn two: the system found a class it had no rule for
The Detector’s exploratory pass flagged that header-trusting “local only” check, a pattern outside the entire 13-rule corpus. The Triager then confirmed it, and Foundry logged it to rule-gaps.jsonl:
{
"weakness_class": "CWE-346",
"symbol": "get_client_ip",
"unit": "src/backend/base/langflow/api/v1/mcp_projects.py::get_client_ip",
"verdict": "true-positive",
"why": "The client IP is taken verbatim from the attacker-controlled X-Forwarded-For header without validating that the request actually arrived through a trusted proxy, allowing a remote attacker to spoof a loopback address and bypass the local-only check in install_mcp_config."
}
On MLflow, the same mechanism caught something completely different: a method that pulls a run ID out of a caller-supplied artifact path. No bounds check, no format check, and subsequent privileged API calls use the result (CWE-129).
Neither of those was in the corpus. Both are now lessons.
Turn three: the baseline became a teacher
In Part 2, I compared Foundry against a fair, plain-agent baseline. Ranging freely, that baseline surfaced a weakness class outside my corpus too: CWE-348, “use of a less-trusted source”. I’d rather use that honestly than defensively. An unconstrained agent can range wider than a declared rule set. That isn’t a defeat for the harness. Instead, it’s free input to the flywheel, and the scorer computes the coverage gap deterministically.
And earlier turns had already paid out before these runs: four rules in the live corpus (object-level authorization, header injection, error leaks, sensitive-data exposure) came from previous rounds of exactly this loop.
Export: from lesson to rule with just one command
Here’s where the loop closes. In my Foundry repo there’s a synthesize command that reads the rule-gap log and, for each gap, asks the model to write two artifacts:
- A CodeGuard prevention rule , a Markdown-style
.mdcfile, written into the target project’s IDE rules directory. This stops the assistant from writing the weaknessagain . - A Foundry detection rule , in YAML, added to the harness’s own corpus. This catchesexisting instances on every future run.
For this post I ran it for real against both gaps:
python -m foundry_poc.cli synthesize --cwe CWE-346
python -m foundry_poc.cli --config config-mlflow.yaml synthesize --cwe CWE-129
Each call took about 90 seconds and roughly 28,000 tokens. For context, the Langflow sweep that discovered the first gap cost about two million. The expensive part was finding the lesson. Writing it down so you never pay for it again costs about 1.4% of that.
Specifically for the CodeGuard rule, here’s a slice of what came out for the Langflow gap. First, the rule names the exact anti-pattern, essentially a sanitized version of the code Foundry found:
def get_client_ip(request: Request) -> str:
forwarded_for = request.headers.get("X-Forwarded-For")
if forwarded_for:
return forwarded_for.split(",")[0].strip()
return request.client.host
Then it gives the assistant secure patterns to reach for instead: an explicit trusted-proxy allowlist and, most importantly for this bug, locality checks that use the socket-level peer and never a header:
def require_local(request: Request) -> None:
"""Raise 403 unless the request comes from the local machine."""
if request.client.host not in _LOCAL_ADDRS:
raise HTTPException(status_code=403, detail="This endpoint is restricted to local callers.")
It closes with a checklist, including a regression test that sends a spoofed “X-Forwarded-For: 127.0.0.1” and asserts a 403. That’s the kind of test that would have caught the original bug.
The paired detection rule for the Foundry Detector:
id: codeguard-py-cwe-346
weakness_class: CWE-346
severity: high
trigger_when:
- A function reads X-Forwarded-For, X-Real-IP, or similar proxy headers
- The extracted IP value is used in an equality or membership check (e.g., ip == '127.0.0.1', ip in LOCAL_ADDRS)
- There is no prior validation that request.client.host (the direct TCP peer) belongs to a trusted proxy CIDR
- A locality or admin-only access check relies on the header-derived IP rather than the socket-level peer
The MLflow gap produced the same pair for CWE-129: Foundry’s corpus went from 13 rules to 15, without me writing either one by hand.
All four generated files are in the repo, so you can judge the quality yourself: the two CodeGuard rules under examples/synthesized/ and the two detection rules under rules/. They are exactly what the model wrote.
Import: the prevention is the file
This is the coolest part… because there’s almost nothing to do.
By default, synthesize writes the CodeGuard rule into the target project’s own .cursor/rules/ directory. Commit that file, and the next developer who opens the project in Cursor has the rule loaded automatically. The next time anyone (human or agent) asks the assistant to write a “localhost-only” endpoint in that repo, the assistant has already been told why the header is a lie and what to do instead. No pipeline, no deployment, no ticket. The prevention is the file.
That sentence is the whole promise of this post.
Rolling it beyond one repo and one IDE follows CodeGuard’s own distribution model:
- Pick where it lands.
--output-dirwrites rules anywhere, for example a shared internal “security rules” repository rather than the target project. - Reach every assistant, not just Cursor. CodeGuard maintains its rules in a unified source format, with a validator and converters that produce the Cursor, Copilot, Windsurf, Codex, Devin, and other formats. Add a synthesized rule to an internal fork’s sources, run the validator and converter, and the same lesson ships to every assistant your developers use.
- Serve it centrally. CodeGuard’s MCP server exposes rules as tools over HTTP, so a platform team can push a newly learned rule once and have every connected assistant pick it up.
That’s a compounding claim, and it’s the reason Foundry’s roughly 10x token cost from Part 2 is defensible: detection is paid for once; prevention runs everywhere, indefinitely. Every lesson Foundry learns shrinks the population of bugs the next evaluation has to find.
What I am not claiming
Synthesized rules need a human reviewer. This one isn’t a formality. The CWE-346 rule I generated is genuinely good guidance, but as first written, its “this is not theoretical” paragraph cited CVE-2023-45857 as an example of the pattern. In fact, that CVE is an axios token-leak issue, not proxy-header spoofing. The guidance was right, but the model invented one supporting citation. I caught it in review and replaced it with the one example I can vouch for, the Langflow finding itself, before committing the rule. Notice the irony: Part 2 was all about never trusting a model’s citations without checking them. So rule synthesis deserves the same discipline. Treat every generated rule as a pull request, not a deployment.
Nothing in this post proves the rules work. I’ve shown that a learned rule lands in the IDE. But I haven’t shown that it changes how often an assistant writes the vulnerable pattern. That takes a controlled experiment, and it gets a post of its own: the one for next week.
Not every gap flows automatically. The synthesizer reads Foundry’s own rule-gap log. The CWE-348 gap from the baseline comparison lives in the scorer’s report, and promoting it to a rule is a manual step today.
Next: prove it
Here’s the question a good security architect should ask at this point, and the one I kept asking myself: does the rule actually change what the assistant writes?
Not “is the file in the right folder”. Not “does the guidance read well”. Does an AI coding agent, given an ordinary feature request, write the vulnerable pattern less often when the rule is there than when it isn’t?
Everything in Part 2 was about refusing to accept a model’s claims without evidence. It would be odd to accept my own claim about prevention on faith, right? So next week we will build the experiment: the same feature requests, a real AI coding agent, workspaces that are identical except for the rules, and a blind judge you’ve already met. The forge from Part 2 grades the guard from this post.
Everything here is reproducible from github.com/juliogomez/foundry: the rule-gap logs from the real runs, the synthesize command, the generated CodeGuard rules under examples/synthesized/ and detection rules under rules/, and docs/CODEGUARD.md for the full integration story. Project CodeGuard itself lives at project-codeguard.org.