You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
map: a traced package must also establish the API before it can be a DB sink
Review of this branch caught a false-candidate path, and checking it against main showed the class is
older than this branch: package provenance was being treated as API provenance.
`.query()` / `.execute()` / `.from().insert()` are generic method names. Any receiver that resolved to a
real package was admitted as a SQL sink, so an `@apollo/client` instance — a genuine dependency with
nothing to do with SQL — produced `attribution: 'import'` and a precise SQL-injection candidate for a
GraphQL call. That rule would block legitimate traffic and mitigate nothing, which is the worst shape a
generated rule can have.
main: const local = new ApolloClient(); local.query(req.body.sql) -> candidate (already live)
main: import { client } from './lib/gql'; client.query(...) -> no sink
here: both shapes -> candidate
So the imported-client hop widened an existing hole rather than opening one. The fix is at the root: a DB
recognizer now requires the resolved package to establish a database API (`isDbPackage`, covering the
inference list plus real drivers not in it, and subpath imports such as `drizzle-orm/node-postgres`).
A traced package that is NOT a DB provider keeps its inventory entry — a `.query()` on an unknown client
is worth a human's attention — but it is marked `apiUnconfirmed`, which means: no rule, no `provider:
'sql'` claim, and no `candidateFamily` either. Advertising the sql-injection family on a GraphQL call
would mis-classify it for any consumer that reads the family without checking `ruleGeneratable`.
The refusal says which of the two things is missing, since they ask a reviewer to check different things:
"not a known db provider: it does not establish a db API (method name alone is not evidence)" vs the
existing untraceable-receiver and inferred-package reasons.
Coverage: both Apollo shapes (imported and same-file) plus controls for `pg` and a `drizzle-orm` subpath,
and a new adversarial corpus case — a non-DB client with a `.query()` method is precisely a lookalike, so
it belongs in that category. Verified 5 assertions fail with the gate disabled. 927 tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
// from the receiver, so `res.locals.db.query(x)` in a file that happens to import `pg` looks
212
217
// identical to a real pool — and `res.locals.db` may be any app object. Such sinks stay in the
213
218
// inventory for review; they just cannot compile a rule that blocks live traffic on a guess.
219
+
if(sink.apiUnconfirmed){
220
+
reasons.push(`sink package "${sink.package}" is not a known ${sink.kind} provider: it does not establish a ${sink.kind} API (method name alone is not evidence)`);
? `sink package "${sink.package}" was inferred from the file's other imports, not from the receiver (${sink.kind}.${sink.op??'?'}): the receiver may be any app object`
for(constclsof['collide with dangerous API names','untraceable receivers','shadowing dangerous globals','two request namespaces','sibling expressions','different namespaces']){
450
+
for(constclsof['collide with dangerous API names','untraceable receivers','shadowing dangerous globals','two request namespaces','sibling expressions','different namespaces','does not establish the API']){
0 commit comments