Build1 publisher3 min readPublished
An AI reviewer called injectable SQL safe because it could not read the helper
A developer's OWASP Benchmark test showed the model's failure was optimism, not ignorance. His fix flips the default on unknown functions, which moves the error rather than removing it.
The Engineer · Build desk
Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

What happened
- EdgeGuard is an open-source VS Code extension built to investigate code from an adversarial perspective, to try to break code before production does.
- The author began testing EdgeGuard against the OWASP Benchmark for Java, and reported the AI was too optimistic.
- The sample code was: String param = request.getParameter("id"); String bar = DatabaseHelper.doSomething(param); String sql = "SELECT * FROM USERS WHERE ID='" + bar + "'";
- The LLM could see the untrusted HTTP input and the SQL construction, but could not see what doSomething() actually did.
- The model made the optimistic assumption that doSomething() was probably an internal helper that sanitizes the input.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
A developer testing an open-source VS Code extension called EdgeGuard against the OWASP Benchmark for Java found that the model's failure mode was not ignorance of SQL injection but optimism about code it could not see [1][2][7]. When tainted HTTP input passed through a helper function whose body was not in the prompt, the model assumed sanitisation and returned SAFE while the value still reached a SQL sink [4][5][6].
The example in the write-up is three lines: a parameter read from an HTTP request, a call to `DatabaseHelper.doSomething(param)`, and the result concatenated into a `SELECT` string [3]. According to the author, the model could see the untrusted input and it could see the query construction, but not what `doSomething()` did, so it filled the gap with the guess that this was probably an internal helper that sanitises input [4][5]. The result was a false negative on a live data flow [6].
The remedy described is a change of default rather than a change of information. EdgeGuard now supplies explicit security assumptions and asks the model to preserve taint unless there is evidence of sanitisation [8], with the specific rule that data entering an unknown function stays tainted [9]. Output moved from a SAFE or VULNERABLE label to a numbered evidence chain: input received, taint preserved across the unknown helper, tainted value concatenated into SQL, conclusion of potential injection [10][11].
That is cheap and directionally correct, and it is worth being clear about what it buys. Nothing in the described change resolves the callee. The tool has traded a false-negative bias for a false-positive bias, and every function body absent from the prompt is now assumed hostile. Whether that is an improvement depends entirely on how large the "unknown function" category remains once a call graph is actually walked.
The second half of the problem is cost. Sending every method to an LLM is wasteful because most of a large codebase is getters, setters, `toString()`, `equals()`, CRUD, data mapping and utilities, and it runs into API rate limits and API cost [12][13]. EdgeGuard adds a local static screening stage that parses code inside VS Code and flags functions with database sinks, file-system access, process execution, network boundaries, user-controlled input, dangerous APIs or missing validation [14][15]. The author's framing is that this makes the LLM a reasoning engine rather than the first line of analysis [16].
At project scale, a workspace scan of the OWASP Benchmark Java project discovered 7,536 functions across 2,771 files [17] and reported 2,145 potential defects [18], including SQL injection, command injection and unsafe input-to-sink flows [19]. That is roughly 28 percent of all functions in the project carrying a finding [20].
The write-up reports those counts but no precision or recall against the benchmark's own labels [21], which is the number that decides whether 2,145 items is a security queue or a backlog nobody will read. Two things to watch: whether the taint-preservation rule gets paired with real callee resolution, so the unknown-function category shrinks; and the recall of the local screening filter, because any function the local parser skips never reaches the model at all, and its blind spots become the product's blind spots [14].