circle-ir 3.152.0 → 3.154.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/configs/sink-semantics.json +102 -0
  2. package/configs/sinks/command.yaml +0 -10
  3. package/configs/sinks/deserialization.yaml +54 -0
  4. package/configs/sinks/path.yaml +0 -94
  5. package/configs/sinks/sql.yaml +24 -24
  6. package/dist/analysis/config-loader.d.ts.map +1 -1
  7. package/dist/analysis/config-loader.js +24 -8
  8. package/dist/analysis/config-loader.js.map +1 -1
  9. package/dist/analysis/passes/library-profile-sink-gate-pass.d.ts +43 -1
  10. package/dist/analysis/passes/library-profile-sink-gate-pass.d.ts.map +1 -1
  11. package/dist/analysis/passes/library-profile-sink-gate-pass.js +75 -0
  12. package/dist/analysis/passes/library-profile-sink-gate-pass.js.map +1 -1
  13. package/dist/analysis/passes/library-profile-xss-gate-pass.d.ts +104 -0
  14. package/dist/analysis/passes/library-profile-xss-gate-pass.d.ts.map +1 -0
  15. package/dist/analysis/passes/library-profile-xss-gate-pass.js +196 -0
  16. package/dist/analysis/passes/library-profile-xss-gate-pass.js.map +1 -0
  17. package/dist/analysis/require-entry-path.d.ts +91 -0
  18. package/dist/analysis/require-entry-path.d.ts.map +1 -0
  19. package/dist/analysis/require-entry-path.js +387 -0
  20. package/dist/analysis/require-entry-path.js.map +1 -0
  21. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  22. package/dist/analysis/taint-matcher.js +55 -1
  23. package/dist/analysis/taint-matcher.js.map +1 -1
  24. package/dist/analyzer.d.ts.map +1 -1
  25. package/dist/analyzer.js +37 -1
  26. package/dist/analyzer.js.map +1 -1
  27. package/dist/browser/circle-ir.js +437 -257
  28. package/dist/core/circle-ir-core.cjs +38 -9
  29. package/dist/core/circle-ir-core.js +38 -9
  30. package/dist/types/config.d.ts +11 -1
  31. package/dist/types/config.d.ts.map +1 -1
  32. package/dist/types/index.d.ts +25 -0
  33. package/dist/types/index.d.ts.map +1 -1
  34. package/package.json +1 -1
@@ -0,0 +1,387 @@
1
+ /**
2
+ * Require-entry-path anchor (cognium-dev#234, ships 3.153.0).
3
+ *
4
+ * Post-pipeline, project-level helper that:
5
+ * 1. Annotates high+critical (H+C) taint findings with an
6
+ * `entryPath[]` chain of methods traversed from a classified
7
+ * Tier-1 entry point (Spring MVC handler, JAX-RS resource,
8
+ * Servlet lifecycle method, Netty channel handler, `main(String[])`,
9
+ * …) down to the finding's sink method.
10
+ * 2. Drops H+C findings under `application` / `server` / `cli` /
11
+ * `plugin` / `unknown` project profiles when the reverse-BFS
12
+ * conclusively returns no such chain — i.e. the finding lives on a
13
+ * method that no HTTP / RPC / lifecycle entry point in the scan can
14
+ * reach.
15
+ *
16
+ * # Why
17
+ *
18
+ * cognium-ai#189 §1 (2026-07 Tier-2 Java cohort — hutool, Sentinel,
19
+ * plantuml, mockserver) surfaced 1942 H+C findings on hutool with
20
+ * *zero* classified HTTP/RPC entry point and no reachable path from
21
+ * `main`. #236 (source-side profile gate, 3.151.0) and #232
22
+ * (sink-side profile gate, 3.152.0) each attacked the problem via
23
+ * profile-conditional per-file drops, but both leave the residual
24
+ * signal on files whose profile is `application/*` or `unknown` even
25
+ * though the enclosing method is manifestly unreachable from any
26
+ * classified boundary.
27
+ *
28
+ * This helper closes that hole: no entry point → no path from an
29
+ * entry point → no H+C finding. Every remaining H+C finding under
30
+ * `application/*` carries a demonstrable call chain from a real
31
+ * boundary as evidence, materialised on `entryPath[]` for consumers
32
+ * (CLI, SARIF, cognium-ai) to display.
33
+ *
34
+ * # Scope
35
+ *
36
+ * - Java only (relies on `classifyEntryPointTier`, which is Java-primary).
37
+ * Non-Java files: pass-through, no annotation, no drop.
38
+ * - Project-level only. Per-file `analyze()` never runs this helper —
39
+ * the reachability question is meaningful only across a full scan.
40
+ * - Only H+C findings from taint passes are candidates for drop. Metric
41
+ * findings, `medium` / `low` taint findings, and findings without a
42
+ * resolved containing method are always preserved.
43
+ *
44
+ * # Interaction with #236 / #232
45
+ *
46
+ * Both #236 and #232 fire when `projectProfile` starts with `library/`:
47
+ * the source / sink is dropped BEFORE the flow is materialised. This
48
+ * helper therefore no-ops under `library/*` (an already-dropped flow
49
+ * never reaches us) but still ANNOTATES findings with `entryPath[]`
50
+ * whenever a chain is available, so downstream consumers can see the
51
+ * anchor regardless of the drop decision.
52
+ *
53
+ * # Reference
54
+ *
55
+ * - cognium-dev#234 — this ticket.
56
+ * - cognium-dev#128 — entry-point tier classifier.
57
+ * - cognium-dev#236 (3.151.0) — source-side library-profile gate.
58
+ * - cognium-dev#232 (3.152.0) — sink-side library-profile gate.
59
+ * - cognium-ai#189 — Tier-2 Java cohort audit.
60
+ * - `docs/ARCHITECTURE.md` ADR-010.
61
+ */
62
+ import { classifyEntryPointTier } from './entry-point-detection.js';
63
+ // ---------------------------------------------------------------------------
64
+ // Public rule id + constants
65
+ // ---------------------------------------------------------------------------
66
+ /** Rule identifier used for `disabledPasses` lookups. */
67
+ export const RULE_ID_REQUIRE_ENTRY_PATH = 'require-entry-path';
68
+ /**
69
+ * BFS visit budget. Chosen empirically to comfortably cover the
70
+ * largest-repo call graphs we see on the Tier-2 cohort (`plantuml`
71
+ * ~7k methods, `sentinel` ~6k) while still guaranteeing the pass
72
+ * terminates in bounded time. Findings whose BFS hits the budget are
73
+ * treated as `unknown` and preserved — never dropped on a bailout.
74
+ */
75
+ const MAX_VISITED_METHODS = 2000;
76
+ /**
77
+ * Apply the entry-path gate + annotation to every file's findings in
78
+ * `fileAnalyses`, mutating each `analysis.findings` array in place.
79
+ *
80
+ * The mutation is idempotent — running twice produces the same result
81
+ * (BFS is deterministic; annotation always overwrites prior fields
82
+ * with the same values).
83
+ */
84
+ export function applyRequireEntryPath(fileAnalyses, options = {}) {
85
+ const disabledSet = normalizeDisabled(options.disabledPasses);
86
+ if (disabledSet.has(RULE_ID_REQUIRE_ENTRY_PATH))
87
+ return;
88
+ // Build the project-wide method index + reverse-caller adjacency once.
89
+ const graph = buildProjectMethodGraph(fileAnalyses);
90
+ if (graph.methodsByKey.size === 0)
91
+ return;
92
+ // Classify entry points once — reused for every finding.
93
+ const entryPointKeys = collectEntryPointKeys(graph);
94
+ const profileResolver = makeProfileResolver(options.projectProfile);
95
+ for (const fa of fileAnalyses) {
96
+ const findings = fa.analysis.findings;
97
+ if (!findings || findings.length === 0)
98
+ continue;
99
+ const kept = [];
100
+ for (const finding of findings) {
101
+ const decision = classifyFinding(finding, fa.analysis, graph, entryPointKeys, profileResolver(fa.file));
102
+ switch (decision.action) {
103
+ case 'keep':
104
+ kept.push(finding);
105
+ break;
106
+ case 'annotate':
107
+ kept.push({
108
+ ...finding,
109
+ entryPath: decision.entryPath,
110
+ entryPathTier: decision.tier,
111
+ });
112
+ break;
113
+ case 'drop':
114
+ // Findings dropped by the entry-path gate carry no
115
+ // side-channel signal — downstream consumers see them
116
+ // as if they were never emitted.
117
+ break;
118
+ }
119
+ }
120
+ fa.analysis.findings = kept.length > 0 ? kept : undefined;
121
+ }
122
+ }
123
+ function buildProjectMethodGraph(fileAnalyses) {
124
+ const methodsByKey = new Map();
125
+ const methodsByName = new Map();
126
+ const callersOf = new Map();
127
+ // Pass 1 — index every method.
128
+ for (const fa of fileAnalyses) {
129
+ const language = (fa.analysis.meta.language ?? '').toLowerCase();
130
+ for (const type of fa.analysis.types ?? []) {
131
+ for (const method of type.methods ?? []) {
132
+ const key = makeMethodKey(fa.file, type.name, method.name, method.start_line);
133
+ methodsByKey.set(key, {
134
+ key,
135
+ file: fa.file,
136
+ className: type.name,
137
+ method,
138
+ enclosingType: type,
139
+ language,
140
+ });
141
+ const bucket = methodsByName.get(method.name);
142
+ if (bucket)
143
+ bucket.push(key);
144
+ else
145
+ methodsByName.set(method.name, [key]);
146
+ }
147
+ }
148
+ }
149
+ // Pass 2 — index every call and resolve callee(s) into edges.
150
+ for (const fa of fileAnalyses) {
151
+ const calls = fa.analysis.calls ?? [];
152
+ for (const call of calls) {
153
+ if (!call.in_method)
154
+ continue;
155
+ const callerKey = resolveCallerKey(fa, call.in_method, call.location.line);
156
+ if (!callerKey)
157
+ continue;
158
+ const calleeKeys = resolveCalleeKeys(call, methodsByKey, methodsByName);
159
+ if (calleeKeys.length === 0)
160
+ continue;
161
+ const code = call.receiver
162
+ ? `${call.receiver}.${call.method_name}(...)`
163
+ : `${call.method_name}(...)`;
164
+ for (const calleeKey of calleeKeys) {
165
+ const edge = {
166
+ callerKey,
167
+ calleeKey,
168
+ callSiteLine: call.location.line,
169
+ code,
170
+ };
171
+ const bucket = callersOf.get(calleeKey);
172
+ if (bucket)
173
+ bucket.push(edge);
174
+ else
175
+ callersOf.set(calleeKey, [edge]);
176
+ }
177
+ }
178
+ }
179
+ return { methodsByKey, methodsByName, callersOf };
180
+ }
181
+ function makeMethodKey(file, className, methodName, startLine) {
182
+ return `${file}|${className}#${methodName}@${startLine}`;
183
+ }
184
+ function resolveCallerKey(fa, inMethod, callLine) {
185
+ // `in_method` is the simple method name of the enclosing method.
186
+ // Disambiguate by finding the type whose method range contains
187
+ // `callLine` (handles overloaded methods sharing the same name).
188
+ for (const type of fa.analysis.types ?? []) {
189
+ for (const method of type.methods ?? []) {
190
+ if (method.name !== inMethod)
191
+ continue;
192
+ if (callLine >= method.start_line && callLine <= method.end_line) {
193
+ return makeMethodKey(fa.file, type.name, method.name, method.start_line);
194
+ }
195
+ }
196
+ }
197
+ return null;
198
+ }
199
+ function resolveCalleeKeys(call, methodsByKey, methodsByName) {
200
+ const candidates = methodsByName.get(call.method_name);
201
+ if (!candidates || candidates.length === 0)
202
+ return [];
203
+ // Prefer receiver-type match when we have it — cuts the fan-out on
204
+ // common names like `execute` / `run` / `handle` from ~50 to ~1.
205
+ if (call.receiver_type) {
206
+ const simple = call.receiver_type.replace(/<.*$/, '').trim();
207
+ const matches = [];
208
+ for (const key of candidates) {
209
+ const rec = methodsByKey.get(key);
210
+ if (rec?.className === simple)
211
+ matches.push(key);
212
+ }
213
+ if (matches.length > 0)
214
+ return matches;
215
+ }
216
+ // Fallback — name-only. Duplicates fan out the BFS but the visit
217
+ // budget bounds it. Wrong callees do not corrupt correctness: a
218
+ // spurious edge only expands the reachable set, never contracts it,
219
+ // and this helper only DROPS on empty-reachable.
220
+ return candidates;
221
+ }
222
+ // ---------------------------------------------------------------------------
223
+ // Entry-point classification
224
+ // ---------------------------------------------------------------------------
225
+ function collectEntryPointKeys(graph) {
226
+ const entryPoints = new Set();
227
+ for (const rec of graph.methodsByKey.values()) {
228
+ const tier = classifyEntryPointTier(rec.method, rec.enclosingType, {
229
+ types: [rec.enclosingType],
230
+ language: rec.language,
231
+ });
232
+ if (tier === 'TIER_1_ENTRY_POINT')
233
+ entryPoints.add(rec.key);
234
+ }
235
+ return entryPoints;
236
+ }
237
+ function classifyFinding(finding, ir, graph, entryPointKeys, profile) {
238
+ // Only taint findings from the security category are in scope.
239
+ if (finding.category !== 'security')
240
+ return { action: 'keep' };
241
+ // Only H+C findings are candidates for drop; lower-severity findings
242
+ // are preserved regardless of reachability (may still be annotated
243
+ // when we can).
244
+ const isHighOrCritical = finding.severity === 'high' || finding.severity === 'critical';
245
+ // Java-only. The classifier is Java-primary (`classifyEntryPointTier`
246
+ // returns `TIER_UNKNOWN` for every other language), so applying the
247
+ // reachability drop to non-Java findings would strip legitimate
248
+ // signal on Python / Node / Go / Rust — pass through unchanged.
249
+ const language = (ir.meta.language ?? '').toLowerCase();
250
+ if (language !== 'java')
251
+ return { action: 'keep' };
252
+ // Resolve containing method by (file, line-range) lookup.
253
+ const containing = findContainingMethod(finding, ir, graph);
254
+ if (!containing) {
255
+ // Sink lives in a field initializer, static block, or another
256
+ // no-method context — cannot classify. Never drop.
257
+ return { action: 'keep' };
258
+ }
259
+ // Reverse BFS from the containing method.
260
+ const bfs = reverseBfsToEntryPoint(containing.key, graph, entryPointKeys);
261
+ if (bfs.status === 'hit') {
262
+ const entryPath = reconstructPath(bfs.entryKey, containing.key, bfs.parent, graph, finding);
263
+ return {
264
+ action: 'annotate',
265
+ entryPath,
266
+ tier: 'tier1-entry-point',
267
+ };
268
+ }
269
+ if (bfs.status === 'budget') {
270
+ // Depth bailout — insufficient evidence, preserve.
271
+ return { action: 'keep' };
272
+ }
273
+ // bfs.status === 'miss' — no entry point reaches this method.
274
+ if (!isHighOrCritical)
275
+ return { action: 'keep' };
276
+ if (!shouldDropUnderProfile(profile))
277
+ return { action: 'keep' };
278
+ return { action: 'drop' };
279
+ }
280
+ function findContainingMethod(finding, ir, graph) {
281
+ const line = finding.line;
282
+ for (const type of ir.types ?? []) {
283
+ for (const method of type.methods ?? []) {
284
+ if (line >= method.start_line && line <= method.end_line) {
285
+ const key = makeMethodKey(finding.file, type.name, method.name, method.start_line);
286
+ const rec = graph.methodsByKey.get(key);
287
+ if (rec)
288
+ return rec;
289
+ }
290
+ }
291
+ }
292
+ return null;
293
+ }
294
+ function reverseBfsToEntryPoint(startKey, graph, entryPointKeys) {
295
+ const parent = new Map();
296
+ const visited = new Set([startKey]);
297
+ const queue = [startKey];
298
+ // Corner case: the sink method is itself an entry point.
299
+ if (entryPointKeys.has(startKey)) {
300
+ return { status: 'hit', entryKey: startKey, parent };
301
+ }
302
+ while (queue.length > 0) {
303
+ if (visited.size > MAX_VISITED_METHODS) {
304
+ return { status: 'budget', entryKey: null, parent };
305
+ }
306
+ const current = queue.shift();
307
+ const incoming = graph.callersOf.get(current) ?? [];
308
+ // Deterministic ordering — sort by caller key for reproducibility.
309
+ incoming.sort((a, b) => a.callerKey.localeCompare(b.callerKey));
310
+ for (const edge of incoming) {
311
+ if (visited.has(edge.callerKey))
312
+ continue;
313
+ visited.add(edge.callerKey);
314
+ parent.set(edge.callerKey, edge);
315
+ if (entryPointKeys.has(edge.callerKey)) {
316
+ return { status: 'hit', entryKey: edge.callerKey, parent };
317
+ }
318
+ queue.push(edge.callerKey);
319
+ }
320
+ }
321
+ return { status: 'miss', entryKey: null, parent };
322
+ }
323
+ function reconstructPath(entryKey, sinkKey, parent, graph, finding) {
324
+ // Walk from entry down to sink by iteratively following the parent
325
+ // map forward. `parent.get(x)` yields the edge whose `callerKey === x`
326
+ // (i.e. the call FROM x to its callee on the path).
327
+ const hops = [];
328
+ let cursor = entryKey;
329
+ const guard = new Set();
330
+ while (cursor !== sinkKey) {
331
+ if (guard.has(cursor))
332
+ break; // paranoia — should never cycle
333
+ guard.add(cursor);
334
+ const rec = graph.methodsByKey.get(cursor);
335
+ const edge = parent.get(cursor);
336
+ if (!rec || !edge)
337
+ break;
338
+ hops.push({
339
+ file: rec.file,
340
+ method: `${rec.className}.${rec.method.name}`,
341
+ line: edge.callSiteLine,
342
+ code: edge.code,
343
+ variable: '',
344
+ });
345
+ cursor = edge.calleeKey;
346
+ }
347
+ // Terminal hop — the sink method itself, at the finding's line.
348
+ const sinkRec = graph.methodsByKey.get(sinkKey);
349
+ if (sinkRec) {
350
+ hops.push({
351
+ file: sinkRec.file,
352
+ method: `${sinkRec.className}.${sinkRec.method.name}`,
353
+ line: finding.line,
354
+ code: finding.message,
355
+ variable: '',
356
+ });
357
+ }
358
+ return hops;
359
+ }
360
+ // ---------------------------------------------------------------------------
361
+ // Profile predicate
362
+ // ---------------------------------------------------------------------------
363
+ function shouldDropUnderProfile(profile) {
364
+ // #236 / #232 already own the `library/*` drop path — this helper
365
+ // must not double-drop findings that survived those gates on
366
+ // library files, so we only fire on non-library profiles.
367
+ if (profile === 'unknown')
368
+ return true;
369
+ if (profile.startsWith('library/'))
370
+ return false;
371
+ return true;
372
+ }
373
+ function makeProfileResolver(input) {
374
+ if (input === undefined)
375
+ return () => 'unknown';
376
+ if (typeof input === 'string')
377
+ return () => input;
378
+ return (file) => input.get(file) ?? 'unknown';
379
+ }
380
+ function normalizeDisabled(input) {
381
+ if (!input)
382
+ return new Set();
383
+ if (input instanceof Set)
384
+ return input;
385
+ return new Set(input);
386
+ }
387
+ //# sourceMappingURL=require-entry-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-entry-path.js","sourceRoot":"","sources":["../../src/analysis/require-entry-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AAUH,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEpE,8EAA8E;AAC9E,6BAA6B;AAC7B,8EAA8E;AAE9E,yDAAyD;AACzD,MAAM,CAAC,MAAM,0BAA0B,GAAG,oBAAoB,CAAC;AAE/D;;;;;;GAMG;AACH,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAqBjC;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CACnC,YAAiE,EACjE,UAAwC,EAAE;IAE1C,MAAM,WAAW,GAAG,iBAAiB,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAC9D,IAAI,WAAW,CAAC,GAAG,CAAC,0BAA0B,CAAC;QAAE,OAAO;IAExD,uEAAuE;IACvE,MAAM,KAAK,GAAG,uBAAuB,CAAC,YAAY,CAAC,CAAC;IACpD,IAAI,KAAK,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO;IAE1C,yDAAyD;IACzD,MAAM,cAAc,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC;IAEpD,MAAM,eAAe,GAAG,mBAAmB,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAEpE,KAAK,MAAM,EAAE,IAAI,YAAY,EAAE,CAAC;QAC9B,MAAM,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACtC,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACjD,MAAM,IAAI,GAAkB,EAAE,CAAC;QAC/B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,MAAM,QAAQ,GAAG,eAAe,CAC9B,OAAO,EACP,EAAE,CAAC,QAAQ,EACX,KAAK,EACL,cAAc,EACd,eAAe,CAAC,EAAE,CAAC,IAAI,CAAC,CACzB,CAAC;YACF,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC;gBACxB,KAAK,MAAM;oBACT,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;oBACnB,MAAM;gBACR,KAAK,UAAU;oBACb,IAAI,CAAC,IAAI,CAAC;wBACR,GAAG,OAAO;wBACV,SAAS,EAAE,QAAQ,CAAC,SAAS;wBAC7B,aAAa,EAAE,QAAQ,CAAC,IAAI;qBAC7B,CAAC,CAAC;oBACH,MAAM;gBACR,KAAK,MAAM;oBACT,mDAAmD;oBACnD,sDAAsD;oBACtD,iCAAiC;oBACjC,MAAM;YACV,CAAC;QACH,CAAC;QACD,EAAE,CAAC,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5D,CAAC;AACH,CAAC;AA+BD,SAAS,uBAAuB,CAC9B,YAAiE;IAEjE,MAAM,YAAY,GAAG,IAAI,GAAG,EAAwB,CAAC;IACrD,MAAM,aAAa,GAAG,IAAI,GAAG,EAAoB,CAAC;IAClD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAsB,CAAC;IAEhD,+BAA+B;IAC/B,KAAK,MAAM,EAAE,IAAI,YAAY,EAAE,CAAC;QAC9B,MAAM,QAAQ,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QACjE,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;YAC3C,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;gBACxC,MAAM,GAAG,GAAG,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;gBAC9E,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE;oBACpB,GAAG;oBACH,IAAI,EAAE,EAAE,CAAC,IAAI;oBACb,SAAS,EAAE,IAAI,CAAC,IAAI;oBACpB,MAAM;oBACN,aAAa,EAAE,IAAI;oBACnB,QAAQ;iBACT,CAAC,CAAC;gBACH,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBAC9C,IAAI,MAAM;oBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;;oBACxB,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC;IACH,CAAC;IAED,8DAA8D;IAC9D,KAAK,MAAM,EAAE,IAAI,YAAY,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC;QACtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,SAAS;gBAAE,SAAS;YAC9B,MAAM,SAAS,GAAG,gBAAgB,CAAC,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC3E,IAAI,CAAC,SAAS;gBAAE,SAAS;YAEzB,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC;YACxE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YAEtC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ;gBACxB,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,WAAW,OAAO;gBAC7C,CAAC,CAAC,GAAG,IAAI,CAAC,WAAW,OAAO,CAAC;YAE/B,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;gBACnC,MAAM,IAAI,GAAa;oBACrB,SAAS;oBACT,SAAS;oBACT,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI;oBAChC,IAAI;iBACL,CAAC;gBACF,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACxC,IAAI,MAAM;oBAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;;oBACzB,SAAS,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC;AACpD,CAAC;AAED,SAAS,aAAa,CACpB,IAAY,EACZ,SAAiB,EACjB,UAAkB,EAClB,SAAiB;IAEjB,OAAO,GAAG,IAAI,IAAI,SAAS,IAAI,UAAU,IAAI,SAAS,EAAE,CAAC;AAC3D,CAAC;AAED,SAAS,gBAAgB,CACvB,EAAwC,EACxC,QAAgB,EAChB,QAAgB;IAEhB,iEAAiE;IACjE,+DAA+D;IAC/D,iEAAiE;IACjE,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;QAC3C,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;YACxC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ;gBAAE,SAAS;YACvC,IAAI,QAAQ,IAAI,MAAM,CAAC,UAAU,IAAI,QAAQ,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;gBACjE,OAAO,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;YAC3E,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,iBAAiB,CACxB,IAA4D,EAC5D,YAAuC,EACvC,aAAoC;IAEpC,MAAM,UAAU,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACvD,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEtD,mEAAmE;IACnE,iEAAiE;IACjE,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7D,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;YAC7B,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAClC,IAAI,GAAG,EAAE,SAAS,KAAK,MAAM;gBAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnD,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,OAAO,CAAC;IACzC,CAAC;IAED,iEAAiE;IACjE,gEAAgE;IAChE,oEAAoE;IACpE,iDAAiD;IACjD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,8EAA8E;AAC9E,6BAA6B;AAC7B,8EAA8E;AAE9E,SAAS,qBAAqB,CAAC,KAAyB;IACtD,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IACtC,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC;QAC9C,MAAM,IAAI,GAAG,sBAAsB,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,aAAa,EAAE;YACjE,KAAK,EAAE,CAAC,GAAG,CAAC,aAAa,CAAC;YAC1B,QAAQ,EAAE,GAAG,CAAC,QAAQ;SACvB,CAAC,CAAC;QACH,IAAI,IAAI,KAAK,oBAAoB;YAAE,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAWD,SAAS,eAAe,CACtB,OAAoB,EACpB,EAAY,EACZ,KAAyB,EACzB,cAA2B,EAC3B,OAAuB;IAEvB,+DAA+D;IAC/D,IAAI,OAAO,CAAC,QAAQ,KAAK,UAAU;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAE/D,qEAAqE;IACrE,mEAAmE;IACnE,gBAAgB;IAChB,MAAM,gBAAgB,GAAG,OAAO,CAAC,QAAQ,KAAK,MAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,UAAU,CAAC;IAExF,sEAAsE;IACtE,oEAAoE;IACpE,gEAAgE;IAChE,gEAAgE;IAChE,MAAM,QAAQ,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACxD,IAAI,QAAQ,KAAK,MAAM;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAEnD,0DAA0D;IAC1D,MAAM,UAAU,GAAG,oBAAoB,CAAC,OAAO,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;IAC5D,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,8DAA8D;QAC9D,mDAAmD;QACnD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC5B,CAAC;IAED,0CAA0C;IAC1C,MAAM,GAAG,GAAG,sBAAsB,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC;IAC1E,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;QACzB,MAAM,SAAS,GAAG,eAAe,CAAC,GAAG,CAAC,QAAS,EAAE,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QAC7F,OAAO;YACL,MAAM,EAAE,UAAU;YAClB,SAAS;YACT,IAAI,EAAE,mBAAmB;SAC1B,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC5B,mDAAmD;QACnD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC5B,CAAC;IAED,8DAA8D;IAC9D,IAAI,CAAC,gBAAgB;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjD,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAEhE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC;AAED,SAAS,oBAAoB,CAC3B,OAAoB,EACpB,EAAY,EACZ,KAAyB;IAEzB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAC1B,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;QAClC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;YACxC,IAAI,IAAI,IAAI,MAAM,CAAC,UAAU,IAAI,IAAI,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;gBACzD,MAAM,GAAG,GAAG,aAAa,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;gBACnF,MAAM,GAAG,GAAG,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACxC,IAAI,GAAG;oBAAE,OAAO,GAAG,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAYD,SAAS,sBAAsB,CAC7B,QAAgB,EAChB,KAAyB,EACzB,cAA2B;IAE3B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC3C,MAAM,OAAO,GAAG,IAAI,GAAG,CAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAa,CAAC,QAAQ,CAAC,CAAC;IAEnC,yDAAyD;IACzD,IAAI,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACvD,CAAC;IAED,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,IAAI,OAAO,CAAC,IAAI,GAAG,mBAAmB,EAAE,CAAC;YACvC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QACtD,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,EAAG,CAAC;QAC/B,MAAM,QAAQ,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACpD,mEAAmE;QACnE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QAChE,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC;gBAAE,SAAS;YAC1C,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC5B,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YACjC,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;gBACvC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;YAC7D,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AACpD,CAAC;AAED,SAAS,eAAe,CACtB,QAAgB,EAChB,OAAe,EACf,MAA6B,EAC7B,KAAyB,EACzB,OAAoB;IAEpB,mEAAmE;IACnE,uEAAuE;IACvE,oDAAoD;IACpD,MAAM,IAAI,GAAe,EAAE,CAAC;IAC5B,IAAI,MAAM,GAAG,QAAQ,CAAC;IACtB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,OAAO,MAAM,KAAK,OAAO,EAAE,CAAC;QAC1B,IAAI,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,MAAM,CAAC,gCAAgC;QAC9D,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAClB,MAAM,GAAG,GAAG,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAChC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI;YAAE,MAAM;QACzB,IAAI,CAAC,IAAI,CAAC;YACR,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,MAAM,EAAE,GAAG,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE;YAC7C,IAAI,EAAE,IAAI,CAAC,YAAY;YACvB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,QAAQ,EAAE,EAAE;SACb,CAAC,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC;IAC1B,CAAC;IAED,gEAAgE;IAChE,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAChD,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,CAAC,IAAI,CAAC;YACR,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE;YACrD,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,IAAI,EAAE,OAAO,CAAC,OAAO;YACrB,QAAQ,EAAE,EAAE;SACb,CAAC,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E,SAAS,sBAAsB,CAAC,OAAuB;IACrD,kEAAkE;IAClE,6DAA6D;IAC7D,0DAA0D;IAC1D,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,KAAK,CAAC;IACjD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,mBAAmB,CAC1B,KAA+D;IAE/D,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC;IAChD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC;IAClD,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC;AAChD,CAAC;AAED,SAAS,iBAAiB,CACxB,KAA8D;IAE9D,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,GAAG,EAAU,CAAC;IACrC,IAAI,KAAK,YAAY,GAAG;QAAE,OAAO,KAAK,CAAC;IACvC,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;AACxB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"taint-matcher.d.ts","sourceRoot":"","sources":["../../src/analysis/taint-matcher.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAgB,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAkB,KAAK,EAAwB,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAClK,OAAO,KAAK,EAAE,WAAW,EAAiB,WAAW,EAAoB,MAAM,oBAAoB,CAAC;AACpG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AA2B7E;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,QAAQ,EAAE,EACjB,KAAK,EAAE,QAAQ,EAAE,EACjB,MAAM,GAAE,WAAgC,EACxC,aAAa,CAAC,EAAE,qBAAqB,EACrC,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,IAAI,CAAC,EAAE,MAAM,GACZ,KAAK,CASP;AAqID;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,WAAW,EAAE,EACtB,KAAK,EAAE,SAAS,EAAE,EAClB,IAAI,EAAE,MAAM,GACX,IAAI,CAYN;AAknED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,WAAW,GACnB,OAAO,CAET"}
1
+ {"version":3,"file":"taint-matcher.d.ts","sourceRoot":"","sources":["../../src/analysis/taint-matcher.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAgB,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAkB,KAAK,EAAwB,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAClK,OAAO,KAAK,EAAE,WAAW,EAAiB,WAAW,EAAoB,MAAM,oBAAoB,CAAC;AACpG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AA2B7E;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,QAAQ,EAAE,EACjB,KAAK,EAAE,QAAQ,EAAE,EACjB,MAAM,GAAE,WAAgC,EACxC,aAAa,CAAC,EAAE,qBAAqB,EACrC,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,IAAI,CAAC,EAAE,MAAM,GACZ,KAAK,CASP;AAqID;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,WAAW,EAAE,EACtB,KAAK,EAAE,SAAS,EAAE,EAClB,IAAI,EAAE,MAAM,GACX,IAAI,CAYN;AA2qED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,WAAW,GACnB,OAAO,CAET"}
@@ -955,7 +955,51 @@ function argIsClassLiteral(call, position) {
955
955
  const expr = (arg.literal ?? arg.expression ?? '').trim();
956
956
  if (!expr)
957
957
  return false;
958
- return CLASS_LITERAL_RE.test(expr);
958
+ if (CLASS_LITERAL_RE.test(expr))
959
+ return true;
960
+ // Added 3.153.0 (cognium-dev #233): recognise Jackson `TypeReference<T>`
961
+ // and Gson `TypeToken<T>` typed generics — the anonymous inner class
962
+ // captures the generic type at compile time exactly like `Foo.class`
963
+ // does for the raw type. Only the empty-body form matches; bodied
964
+ // subclasses (`new TypeReference<...>() { @Override … }`) are treated
965
+ // as dynamic to preserve recall on suspicious overrides.
966
+ return TYPE_TOKEN_RE.test(expr);
967
+ }
968
+ /**
969
+ * Matches `new TypeReference<...>() {}` (Jackson) and
970
+ * `new TypeToken<...>() {}` (Gson) with an empty anonymous body — the
971
+ * two canonical shapes for expressing a compile-time-fixed generic
972
+ * type at a Java call site.
973
+ *
974
+ * Whitespace is tolerated between tokens; nested generics
975
+ * (`<Map<String, List<User>>>`) are captured non-greedily and the
976
+ * body must be empty (`{}` with only whitespace inside).
977
+ */
978
+ const TYPE_TOKEN_RE = /^new\s+(?:TypeReference|TypeToken)\s*<[\s\S]*>\s*\(\s*\)\s*\{\s*\}$/;
979
+ /**
980
+ * Returns true when the call's argument at `position` is a compile-time
981
+ * string literal (not a variable / expression / concatenation).
982
+ *
983
+ * Used by `SinkPattern.safe_if_string_literal_at` — added 3.153.0 for
984
+ * JDBC `JdbcTemplate` query-family sinks that carry `?` placeholders
985
+ * (parameterised queries cannot be tainted at the SQL layer).
986
+ */
987
+ function argIsStringLiteral(call, position) {
988
+ const arg = call.arguments.find(a => a.position === position);
989
+ if (!arg)
990
+ return false;
991
+ // Prefer `arg.literal` (the extractor's normalised literal value)
992
+ // when populated; fall back to `arg.expression` for languages whose
993
+ // extractor does not split literals out separately.
994
+ if (arg.literal !== undefined && arg.literal !== null && arg.literal !== '')
995
+ return true;
996
+ const expr = (arg.expression ?? '').trim();
997
+ if (!expr)
998
+ return false;
999
+ // Double-quoted, single-quoted (Groovy / Bash), or Java text-block (`"""…"""`).
1000
+ return ((expr.startsWith('"') && expr.endsWith('"') && expr.length >= 2) ||
1001
+ (expr.startsWith("'") && expr.endsWith("'") && expr.length >= 2) ||
1002
+ (expr.startsWith('"""') && expr.endsWith('"""')));
959
1003
  }
960
1004
  /**
961
1005
  * CWE-78 (OS Command Injection) receiver-class allowlist (#129).
@@ -1384,6 +1428,16 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
1384
1428
  argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
1385
1429
  continue;
1386
1430
  }
1431
+ // Skip parameterised SQL query overloads whose SQL is a compile-time
1432
+ // string literal (e.g. `JdbcTemplate.queryForObject("SELECT ... WHERE
1433
+ // id = ?", args, RowMapper)`) — `?` placeholders are bound by the
1434
+ // driver, so a literal at the query position cannot carry taint.
1435
+ // Non-literal overloads (variable, concat, `String.format`) still
1436
+ // match. Added 3.153.0 (cognium-dev #233).
1437
+ if (pattern.safe_if_string_literal_at !== undefined &&
1438
+ argIsStringLiteral(call, pattern.safe_if_string_literal_at)) {
1439
+ continue;
1440
+ }
1387
1441
  // #129 — CWE-78 receiver-class allowlist gate.
1388
1442
  // Suppress command_injection emissions on receivers known NOT
1389
1443
  // to invoke OS commands. Constructors check method_name (which