The Regression That Shouldn't Have Shipped
AI coding assistants reason within a single file's context window and miss bugs whose taint flows across three or more files. The category that ships past AI-assisted review.
AI coding assistants reason within their context window, and the context window rarely holds three files at once. A SQL injection that lives in lib/db.ts but receives untrusted input from routes/api.ts via a helper in lib/util/strings.ts crosses three files. The LLM sees one file at a time and never reconstructs the taint path. The bug ships.
This is the third piece in BrassCoders’s AI Coding Assistant Blind Spots pillar — the deep dive on the first blind-spot category. The piece walks through a worked example, the academic literature on context-window decay, and the deterministic complement BrassCoders ships.
Why Single-File LLMs Lose The Bug
BrassCoders treats the cross-file taint category as a structural limit of LLM-based review, not a calibration issue. The structural limit is the context window: large language models attend to tokens unevenly, and tokens in the middle of long contexts receive less effective attention than tokens at the boundaries.
Liu et al. 2023 documented this in Lost in the Middle. Their finding: LLMs reliably attend to the start and end of long contexts and lose attention through the middle. A 200,000-token context window does not solve the problem; it redistributes the same attention budget across more tokens. The middle stays low-attention.
A cross-file taint flow that requires holding three files in mind passes through that low-attention zone. The model’s effective context drops the file in the middle. The taint flow becomes invisible. The bug ships.
ACM TOSEM 2026 confirmed the pattern in a realistic evaluation of Copilot review. The paper reported that Copilot review “frequently fails to detect critical vulnerabilities including SQL injection, cross-site scripting, and insecure deserialization” when those vulnerabilities involved data flows across files. The intra-file detection rate was much higher. The category is interprocedural, not just hard.
A Worked Example: SQL Injection Across Three Files
BrassCoders detected this exact pattern in a 2026 case study on the OWASP PyGoat repository, with the taint flowing across views.py, helpers.py, and db.py. The Claude Code review of the PR did not flag the flow; the BrassCoders YAML caught it via Pysa interprocedural analysis.
The shape of the bug. A Flask route in views.py reads a query parameter name from the user. The handler passes name to a helper format_query in helpers.py. The helper lowercases the string, normalizes whitespace, and forwards it to a database wrapper run_query in db.py. The wrapper builds the SQL string with f-string interpolation: f"SELECT * FROM users WHERE name = '{user_input}'". The result is classic SQL injection. The taint source is the request parameter. The taint sink is the SQL string. The path is three files.
What Claude Code saw on review. Reading views.py in isolation, Claude saw a normal Flask route that called a helper. Reading helpers.py, Claude saw a normal string transformation. Reading db.py, Claude saw a database wrapper that did SQL string building, but without knowing the input came from an untrusted source, Claude treated the f-string as a code-quality issue at most. The full taint flow required holding all three files at once. The context window had room for one and a half.
What BrassCoders surfaced. Pysa’s interprocedural taint model treats Flask request.args.get as a TaintSource[UserControlled], treats f-string interpolation reaching db.execute as a TaintSink[SQL], and traces every flow from source to sink across function calls. The finding landed in .brass/ai_instructions.yaml as a critical_issues entry with the full path and line numbers for each step.
What Interprocedural Taint Analysis Actually Does
BrassCoders ships Meta’s Pyre/Pysa as the interprocedural taint engine for Python — the same engine Meta uses internally on its Python codebase. Pysa traces every data flow from every defined taint source to every defined taint sink across function and file boundaries.
The mechanics. Pysa builds a call graph of the codebase, identifies every callsite where a tainted argument propagates into a function parameter, and walks the graph forward. When tainted data reaches a sink, Pysa emits a finding. The walk is deterministic. The same code produces the same flow report, every time. There is no LLM in the loop, no probabilistic ranking, no attention decay.
The trade-off. Pysa needs a model — a file that declares which functions are sources, which are sinks, and which propagate taint. BrassCoders ships a curated model at src/brass/scanners/pysa_model.pysa covering common Python web frameworks (Flask, Django, FastAPI), common ORM patterns, and common privacy sinks (logging, JSON serialization, file writes). The model is the load-bearing artifact; BrassCoders maintains it as part of the OSS core.
Meta’s Pyre documentation describes the engine; the Pysa documentation covers the taint model format. BrassCoders builds on top by shipping a working model out of the box, so the typical user does not have to configure anything for the common cases.
The Limits Of Context Windows
BrassCoders treats LLM context-window decay as a hard ceiling, not a temporary engineering constraint. Even with a 1M-token window (Claude Opus, Gemini), the attention budget is finite, and the middle-of-context region stays low-attention.
The math. A 1M-token window holds roughly 4,000 pages of source code. The model’s effective attention across that window is not uniform; attention concentrates at the boundaries. The middle 50% receives a fraction of the boundary attention. A bug whose taint flow lives in the middle 50% of a large repository read into context is statistically likely to be missed, regardless of total context size.
The implication. Larger context windows help with some categories of analysis (style consistency across files, refactor suggestions, codebase navigation) and do not help with interprocedural taint. The categories that need exhaustive search across deterministic rules need a deterministic engine. The category does not get easier with bigger models; it gets harder, because bigger models are typically used on bigger codebases.
This is why the deterministic complement matters more, not less, as AI assistants get more capable. The categories where AI assistants are weak — cross-file taint, missing decorators, hallucinated imports — are structural to how LLMs reason. They are not solved by capability improvements. They are solved by pairing the LLM with a deterministic engine that has no context window.
How To Add This Detection To Your Workflow
BrassCoders ships the interprocedural taint detection as part of the OSS core, runnable with one command after install. The command runs Pyre/Pysa under the hood with the bundled taint model, walks every flow across the codebase, and writes the findings to .brass/ai_instructions.yaml.
Install and scan:
pipx install brasscoders
brasscoders scan /path/to/python/project
Then hand the YAML to your AI assistant:
Read .brass/ai_instructions.yaml in this project.
Address the critical_issues in order.
For each one, propose a diff and explain the fix.
The AI assistant now works against a deterministic finding list rather than reviewing a diff blind. Each finding has a file path, a line number, a severity, and a finding type. There is no ambiguity about what to look at next.
The OSS core handles the Python coverage. JavaScript/TypeScript projects get Semgrep-based intraprocedural taint plus pattern detection — useful coverage, but not the full interprocedural treatment Python gets. The AI Blind Spots pillar lays out the language coverage matrix and the honest gaps. The fix for TypeScript taint is on the BrassCoders roadmap; the current honest answer is that Python is the language with the strongest cross-file detection today.
The regression that shouldn’t have shipped — the SQL injection that crossed three files, the missing @login_required on the new admin route, the credential inlined past a comment boundary — these are the categories where pairing AI review with a deterministic taint engine pays off. The pairing works on every codebase BrassCoders ships against.
Frequently Asked Questions
Why can't Claude Code or Cursor catch cross-file taint?
Their reasoning is bounded by the context window. The window holds the file being edited plus one or two related files; a taint flow that crosses three or more files passes through the window's low-attention region. Liu et al. 2023 (Lost in the Middle) documented the attention decay; larger windows redistribute the same attention budget rather than fix the decay.
What is interprocedural taint analysis?
Static analysis that traces data flows across function and file boundaries from defined sources (request parameters, file reads, network input) to defined sinks (database queries, shell commands, HTML renders). BrassCoders ships Meta's Pyre/Pysa as the interprocedural taint engine for Python. Pysa is the same engine Meta uses internally on its Python codebase.
Does BrassCoders support TypeScript taint analysis?
Partial. BrassCoders runs Semgrep with TypeScript rules for intraprocedural taint matching. Full interprocedural taint for TypeScript is a known limitation — Pyre/Pysa is Python-only. For TypeScript-heavy codebases that need deep taint coverage, pair BrassCoders with a TypeScript-specific SAST like CodeQL.
How big does the codebase need to be for this to matter?
Any codebase with more than ~10 files where any user input reaches any data sink. The taint-crosses-files pattern shows up in small Flask apps and large monoliths equally. The likelihood scales with the number of helper functions between request handler and database query.
What's the false-positive rate for Pysa?
Pysa's false-positive rate depends entirely on the source-and-sink model. With a curated model (the one BrassCoders ships), most findings are real or near-real. With the default model, false positives can be high. BrassCoders maintains the source-sink model as part of the OSS core; the file is at src/brass/scanners/pysa_model.pysa.