The prompt said: "fetch this config from the API and use it to initialize the service." The AI generated code that calls the API, parses the JSON, and passes the values directly to initialization. No validation. The code works correctly on valid API responses — which is what every test scenario covered. It fails, in a security-relevant way, when the API returns values an attacker can influence.
The problem isn't a missing code pattern. It's a missing design decision: which sources are trusted, and what validation happens when data crosses from untrusted to trusted?
What a Trust Boundary Is
A trust boundary is the line between parts of a system where data is verified and parts where it isn't. BrassCoders can flag structural indicators that a boundary is missing — user-controlled input in a SQL query, a user-supplied path in a file operation — because those have recognizable shapes. What it cannot determine is whether data from an external API response, a configuration endpoint, or a deserialized payload is being treated as trusted at the right point in the call chain. That's a design question, not a pattern question.
CWE-501 (Trust Boundary Violation) from MITRE defines the category: "the product mixes trusted and untrusted data in the same data structure or structured variable." The violation happens at the boundary crossing — the moment untrusted data enters code that treats it as verified — and that crossing is often invisible in the source because the data looks like any other local variable once it's been assigned.
How AI Assistants Misplace the Boundary
AI-generated code works on the happy path. BrassCoders's published N=15 AI-code-findings corpus found real security issues in 9 of 15 AI-generated Python files — all from neutral prompts, none from prompts that requested vulnerable code. The trust boundary failures in that class don't appear as structural bugs; they appear as code that correctly processes inputs the developer controls and silently trusts inputs an attacker can influence.
An AI assistant generating an HTTP client that fetches external configuration was shown: make the request, parse the response, use the data. The model fills in the code path it was prompted for. Validation of the response fields belongs at the boundary — before parsing, or immediately after — but the prompt described the interior of the function, not the seam where untrusted data enters. The model produces internally correct code with an externally wrong trust assumption.
This is why the trust boundary problem persists even when teams use AI assistants for security review. An AI asked to review a function's code often finds the function well-formed. The trust boundary question is about the function's context: what are the provenance guarantees on its inputs, and does the caller enforce them? That's architectural reasoning, not code reading.
Three Ways the Boundary Fails in AI Code
BrassCoders scans the code that results from trust boundary design decisions — catching the SQL injection and the path traversal where they exist — but the three design patterns that produce trust boundary failures in AI-generated code leave no structural marker: external responses treated as structured data, configuration values passed without verification, and sanitized-flag drift across function calls.
External API responses treated as structured data. An AI-generated integration layer fetches a vendor API response, parses the JSON, and passes the parsed values to downstream functions. The downstream functions trust their inputs. When the vendor API returns unexpected values — or when an attacker influences the response through a misconfigured or compromised upstream — the downstream functions execute on data they assumed was already validated. The code path is correct; the trust assumption is wrong.
Configuration values passed without verification. Environment variables and configuration files sit at a trust boundary. They're not direct user input, but they're writable by anyone with deployment access, and AI-generated code that reads config values and passes them directly to subprocess calls, file path construction, or query parameters skips the validation the boundary requires. The AI was shown: read the value, use the value. The step between — verify the value — was in the design, not the prompt.
Sanitized-flag drift across function calls. AI-generated code commonly applies validation in one function and passes the result to a second that has no knowledge of whether validation happened. Three function calls later, the original source is invisible in the local variable name. The OWASP Testing Guide on input validation identifies this as a primary failure mode: validation happened, but at the wrong layer, and the data's untrusted origin is no longer visible at the point of use.
What BrassCoders Catches Near Trust Boundaries
BrassCoders flags the structural indicators that trust boundaries are missing or misplaced. SQL injection findings (Bandit B608) mark places where user-supplied data flows into a query without parameterization. Command injection findings (Bandit B602/B603) mark subprocess calls where user-supplied strings reach the shell. The phantom-import scanner catches external package references that don't exist — a trust assumption about the package ecosystem that wasn't verified.
Pyre/Pysa, the taint analyzer BrassCoders runs, handles some taint tracking across function boundaries — tracing data from sources to security-sensitive sinks. Its coverage depends on annotated entry points and sink functions for the specific application. What no scanner in the set can determine: whether the data in a config_value variable came from a trusted internal store or an external API response. The variable name doesn't carry provenance, and provenance is the trust question.
What Enforces the Boundary
Trust boundaries are enforced at design time and tested at runtime — BrassCoders clears the structural findings from the code, and the trust boundary architecture lives in a threat model that predates the code.
Enforcing a trust boundary requires identifying the source, defining the validation contract at the entry point, and verifying that the contract holds in integration tests. An external API response gets schema validation before its values are used — not inside the downstream function, but at the seam where untrusted data enters the system. That seam is the boundary. An allowlist on redirect targets enforces a boundary against open redirect payloads. A type assertion before the subprocess call enforces a boundary against command injection. Each of those is a design decision before it's a line of code.
OWASP A04:2021 (Insecure Design) names the class: design-level flaws, including missing trust boundary controls, are distinct from implementation bugs and require design-level fixes. No additional scanning changes a design that assumed external data was safe. The fix is the schema validation at the seam, the allowlist on the redirect target, the type assertion before the subprocess call — in the architecture, before the code is written.
pip install brasscoders
brasscoders --offline scan /path/to/your/project
BrassCoders catches the injection bug where user input hit the query string. The trust boundary that let external config values reach a subprocess call without validation is the design decision — the one the threat model has to make before the code exists, and the test suite has to verify after it's written.
Top comments (0)