circle-ir 3.177.0 → 3.179.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.
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Note-level finding coalescer — cognium-dev #143.
3
+ *
4
+ * Groups `SastFinding[]` by `(file, line)` and folds any group of two or
5
+ * more `level === 'note'` findings into a single record. The primary
6
+ * finding is picked deterministically (lexicographic `rule_id`) and
7
+ * the co-located rule_ids are attached as `labels[]`. Every other
8
+ * (`level === 'warning' | 'error'`) finding passes through untouched.
9
+ *
10
+ * Empirical basis (from the OWASP-Benchmark instrumentation capture on
11
+ * cognium-dev#145, comment 2026-07-03):
12
+ * - 5,481 `(file, line)` locations (31.3% of files) were hit by ≥ 2
13
+ * distinct advisory rules.
14
+ * - Most-common pairs:
15
+ * missing-public-doc + naming-convention × 2,740
16
+ * missing-csp-frame-ancestors + missing-x-frame-options × 2,740
17
+ * unused-variable + variable-shadowing (co-located)
18
+ * - HIGH-severity: 0 co-locations in the same capture.
19
+ *
20
+ * Design invariants:
21
+ * - Additive: consumers that key on `rule_id` continue to work
22
+ * unchanged. Consumers that surface every co-located rule read
23
+ * both `rule_id` and `labels`.
24
+ * - Level-gated: only fires when EVERY finding in the group has
25
+ * `level === 'note'`. If any group member is `warning` or `error`,
26
+ * the group passes through un-coalesced — visibility of higher-
27
+ * severity findings is never diminished.
28
+ * - Deterministic: sort by `rule_id` inside a group before picking
29
+ * the primary, so the same input always produces the same output
30
+ * (test-friendly, diff-friendly, cache-friendly).
31
+ * - Message preservation: the primary finding's `message` is kept
32
+ * verbatim. The `labels[]` field is the sole signal that more
33
+ * rules co-located there.
34
+ *
35
+ * This is the MVP of the reopen. Follow-ups on #143 include a full
36
+ * instrumentation rerun on multi-severity data + a broader
37
+ * medium-severity coalesce policy — both deferred until the data
38
+ * capture is redone.
39
+ */
40
+ /**
41
+ * Coalesce note-level findings at the same `(file, line)` location.
42
+ * Returns a new array; the input is not mutated.
43
+ */
44
+ export function coalesceNoteLevelFindings(findings) {
45
+ if (findings.length < 2)
46
+ return [...findings];
47
+ // Bucket by (file, line). We keep original insertion order so
48
+ // higher-level findings interleaved with note-level findings are
49
+ // preserved in-place; only the note-level subset is folded.
50
+ const groups = new Map();
51
+ const order = [];
52
+ for (const f of findings) {
53
+ const key = `${f.file}\0${f.line}`;
54
+ const bucket = groups.get(key);
55
+ if (bucket) {
56
+ bucket.push(f);
57
+ }
58
+ else {
59
+ groups.set(key, [f]);
60
+ order.push(key);
61
+ }
62
+ }
63
+ const out = [];
64
+ for (const key of order) {
65
+ const bucket = groups.get(key);
66
+ if (bucket.length === 1) {
67
+ out.push(bucket[0]);
68
+ continue;
69
+ }
70
+ // Split by level. Only same-key finding groups where EVERY entry
71
+ // is `note` get coalesced; mixed-level groups pass through
72
+ // un-coalesced (visibility of warnings / errors preserved).
73
+ const allNote = bucket.every((f) => f.level === 'note');
74
+ if (!allNote) {
75
+ for (const f of bucket)
76
+ out.push(f);
77
+ continue;
78
+ }
79
+ // Also skip when every entry has the SAME rule_id — those are
80
+ // duplicate emissions, not multi-rule collisions. Passing them
81
+ // through preserves the existing dedup behaviour handled elsewhere
82
+ // (e.g. taint-matcher's sinkMap dedup). Coalescer's job is only
83
+ // multi-rule folding.
84
+ const uniqueRuleIds = new Set(bucket.map((f) => f.rule_id));
85
+ if (uniqueRuleIds.size < 2) {
86
+ for (const f of bucket)
87
+ out.push(f);
88
+ continue;
89
+ }
90
+ // Deterministic primary pick: lexicographic rule_id.
91
+ const sorted = [...bucket].sort((a, b) => a.rule_id.localeCompare(b.rule_id));
92
+ const primary = sorted[0];
93
+ const additional = sorted
94
+ .slice(1)
95
+ .map((f) => f.rule_id)
96
+ // Preserve `labels[]` carried on the primary or others from a
97
+ // prior coalesce (idempotent when re-run).
98
+ .concat(...sorted.map((f) => f.labels ?? []));
99
+ // Dedup labels; drop the primary's rule_id if it accidentally
100
+ // appears in a prior labels[] entry.
101
+ const uniqueLabels = Array.from(new Set(additional)).filter((l) => l !== primary.rule_id);
102
+ out.push({ ...primary, labels: uniqueLabels });
103
+ }
104
+ return out;
105
+ }
106
+ //# sourceMappingURL=note-coalescer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"note-coalescer.js","sourceRoot":"","sources":["../../src/analysis/note-coalescer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * DeserializationSafetyGatePass — cognium-dev #258
3
+ *
4
+ * Drops `deserialization` sinks whose surrounding library configuration
5
+ * renders them non-exploitable. Java-only in the MVP scope; runs after
6
+ * `SinkSemanticsPass` (so upstream registry gates have already fired)
7
+ * and before `TaintPropagationPass` (so flow generators never see the
8
+ * dropped sinks).
9
+ *
10
+ * Three sub-gates:
11
+ *
12
+ * Gate A — Fastjson `*_noneautotype` build (manifest-based).
13
+ * Reads `AnalyzerOptions.dependencyContext.java.pomXml`, resolves
14
+ * the effective Fastjson coordinate, and drops
15
+ * `JSON.parseObject` / `JSON.parse` deserialization sinks when the
16
+ * version literally matches the hardened classifier — UNLESS the
17
+ * file itself re-enables autotype via `setAutoTypeSupport(true)`
18
+ * (in which case the pinned build's protection is defeated and the
19
+ * sink continues to fire).
20
+ *
21
+ * Gate B — Jackson polymorphism not enabled (in-file scan).
22
+ * `ObjectMapper.readValue(json, targetType)` is safe on Jackson
23
+ * ≥ 2.10 unless the file enables polymorphic type handling via
24
+ * `enableDefaultTyping` / `activateDefaultTyping` or applies
25
+ * `@JsonTypeInfo` somewhere. When none of those signals appear in
26
+ * the file, drop `ObjectMapper.readValue` deserialization sinks.
27
+ *
28
+ * Gate C — SnakeYAML `SafeConstructor` (in-file scan).
29
+ * `new Yaml(new SafeConstructor())` gives a Yaml instance whose
30
+ * `.load(...)` cannot instantiate arbitrary classes. When the file
31
+ * builds any Yaml with SafeConstructor, drop `Yaml.load` /
32
+ * `Yaml.loadAs` / `Yaml.loadAll` deserialization sinks.
33
+ *
34
+ * Each sub-gate is defensive: on missing signal it defaults to *do
35
+ * not drop* (the sink continues to fire). So a resolver bug can only
36
+ * ever regress toward the current over-firing behaviour, never toward
37
+ * a false negative.
38
+ *
39
+ * See `docs/PASSES.md` for the canonical pass registry entry.
40
+ */
41
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
42
+ import type { DependencyContext } from '../../analyzer.js';
43
+ export interface DeserializationSafetyGateResult {
44
+ /** Sinks dropped by Gate A (Fastjson noneautotype). */
45
+ droppedFastjson: number;
46
+ /** Sinks dropped by Gate B (Jackson polymorphism not enabled). */
47
+ droppedJackson: number;
48
+ /** Sinks dropped by Gate C (SnakeYAML SafeConstructor). */
49
+ droppedSnakeYaml: number;
50
+ /** Sinks dropped by Gate D (Python PyYAML ≥ 6.0 default-safe). */
51
+ droppedPyYaml: number;
52
+ }
53
+ export declare class DeserializationSafetyGatePass implements AnalysisPass<DeserializationSafetyGateResult> {
54
+ private readonly dependencyContext?;
55
+ readonly name = "deserialization-safety-gate";
56
+ readonly category: "security";
57
+ constructor(dependencyContext?: DependencyContext | undefined);
58
+ run(ctx: PassContext): DeserializationSafetyGateResult;
59
+ }
60
+ //# sourceMappingURL=deserialization-safety-gate-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deserialization-safety-gate-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/deserialization-safety-gate-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAG9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAY3D,MAAM,WAAW,+BAA+B;IAC9C,uDAAuD;IACvD,eAAe,EAAE,MAAM,CAAC;IACxB,kEAAkE;IAClE,cAAc,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,gBAAgB,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,aAAa,EAAE,MAAM,CAAC;CACvB;AAeD,qBAAa,6BACX,YAAW,YAAY,CAAC,+BAA+B,CAAC;IAK5C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IAH/C,QAAQ,CAAC,IAAI,iCAAiC;IAC9C,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;gBAEX,iBAAiB,CAAC,EAAE,iBAAiB,YAAA;IAElE,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,+BAA+B;CAiIvD"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * DeserializationSafetyGatePass — cognium-dev #258
3
+ *
4
+ * Drops `deserialization` sinks whose surrounding library configuration
5
+ * renders them non-exploitable. Java-only in the MVP scope; runs after
6
+ * `SinkSemanticsPass` (so upstream registry gates have already fired)
7
+ * and before `TaintPropagationPass` (so flow generators never see the
8
+ * dropped sinks).
9
+ *
10
+ * Three sub-gates:
11
+ *
12
+ * Gate A — Fastjson `*_noneautotype` build (manifest-based).
13
+ * Reads `AnalyzerOptions.dependencyContext.java.pomXml`, resolves
14
+ * the effective Fastjson coordinate, and drops
15
+ * `JSON.parseObject` / `JSON.parse` deserialization sinks when the
16
+ * version literally matches the hardened classifier — UNLESS the
17
+ * file itself re-enables autotype via `setAutoTypeSupport(true)`
18
+ * (in which case the pinned build's protection is defeated and the
19
+ * sink continues to fire).
20
+ *
21
+ * Gate B — Jackson polymorphism not enabled (in-file scan).
22
+ * `ObjectMapper.readValue(json, targetType)` is safe on Jackson
23
+ * ≥ 2.10 unless the file enables polymorphic type handling via
24
+ * `enableDefaultTyping` / `activateDefaultTyping` or applies
25
+ * `@JsonTypeInfo` somewhere. When none of those signals appear in
26
+ * the file, drop `ObjectMapper.readValue` deserialization sinks.
27
+ *
28
+ * Gate C — SnakeYAML `SafeConstructor` (in-file scan).
29
+ * `new Yaml(new SafeConstructor())` gives a Yaml instance whose
30
+ * `.load(...)` cannot instantiate arbitrary classes. When the file
31
+ * builds any Yaml with SafeConstructor, drop `Yaml.load` /
32
+ * `Yaml.loadAs` / `Yaml.loadAll` deserialization sinks.
33
+ *
34
+ * Each sub-gate is defensive: on missing signal it defaults to *do
35
+ * not drop* (the sink continues to fire). So a resolver bug can only
36
+ * ever regress toward the current over-firing behaviour, never toward
37
+ * a false negative.
38
+ *
39
+ * See `docs/PASSES.md` for the canonical pass registry entry.
40
+ */
41
+ import { resolveFastjsonFromPom, resolveFastjsonFromGradle, resolvePyYamlFromRequirements, resolvePyYamlFromPyproject, fileHasUnsafePyYamlLoader, fileReenablesFastjsonAutotype, fileEnablesJacksonPolymorphism, fileConfiguresSnakeYamlSafely, } from '../dependency-versions.js';
42
+ const FASTJSON_METHODS = new Set(['parseObject', 'parse']);
43
+ const FASTJSON_CLASSES = new Set(['JSON', 'JSONObject']);
44
+ const JACKSON_METHODS = new Set(['readValue', 'convertValue', 'treeToValue']);
45
+ const JACKSON_CLASSES = new Set(['ObjectMapper', 'ObjectReader']);
46
+ const SNAKEYAML_METHODS = new Set(['load', 'loadAs', 'loadAll']);
47
+ const SNAKEYAML_CLASSES = new Set(['Yaml']);
48
+ // Python PyYAML gate (Gate D — cognium-dev #261 Python slice).
49
+ const PYYAML_METHODS = new Set(['load']);
50
+ const PYYAML_CLASSES = new Set(['yaml']);
51
+ export class DeserializationSafetyGatePass {
52
+ dependencyContext;
53
+ name = 'deserialization-safety-gate';
54
+ category = 'security';
55
+ constructor(dependencyContext) {
56
+ this.dependencyContext = dependencyContext;
57
+ }
58
+ run(ctx) {
59
+ const { graph, language, code } = ctx;
60
+ // Java + Python for the MVP. Other languages (JS Deserialize,
61
+ // Rust bincode) can extend the gate on their own manifests in
62
+ // follow-up scopes.
63
+ if (language !== 'java' && language !== 'python') {
64
+ return { droppedFastjson: 0, droppedJackson: 0, droppedSnakeYaml: 0, droppedPyYaml: 0 };
65
+ }
66
+ // Same sink-source discovery as SinkSemanticsPass: prefer
67
+ // SinkFilterResult when the real pipeline has run, otherwise fall
68
+ // back to the graph's initial (usually empty) sink array so
69
+ // stand-alone unit-test harnesses can drive the gate directly.
70
+ const sinks = ctx.hasResult('sink-filter')
71
+ ? ctx.getResult('sink-filter').sinks
72
+ : graph.ir.taint.sinks;
73
+ // --- Gate A: Fastjson _noneautotype ------------------------------------
74
+ // Consult pom.xml first; fall back to build.gradle when pom does
75
+ // not resolve. Both manifests may be supplied by a caller that
76
+ // scans a multi-build project; either one indicating the hardened
77
+ // classifier is sufficient to drop the sink.
78
+ const pomXml = this.dependencyContext?.java?.pomXml;
79
+ const buildGradle = this.dependencyContext?.java?.buildGradle;
80
+ const fastjson = (pomXml ? resolveFastjsonFromPom(pomXml) : null) ??
81
+ (buildGradle ? resolveFastjsonFromGradle(buildGradle) : null);
82
+ const fastjsonHardened = fastjson?.noneAutotype === true &&
83
+ !fileReenablesFastjsonAutotype(code);
84
+ // --- Gate B: Jackson polymorphism ---------------------------------------
85
+ const jacksonSafe = !fileEnablesJacksonPolymorphism(code);
86
+ // --- Gate C: SnakeYAML SafeConstructor ----------------------------------
87
+ const snakeYamlSafe = fileConfiguresSnakeYamlSafely(code);
88
+ // --- Gate D: PyYAML ≥ 6.0 (Python) --------------------------------------
89
+ // Under pyyaml ≥ 6.0, `yaml.load(x)` without an explicit `Loader=`
90
+ // keyword arg raises TypeError (safe-by-default); callers that pass
91
+ // `Loader=SafeLoader` are safe; callers that pass an unsafe Loader
92
+ // (`Loader=Loader` / `UnsafeLoader` / `FullLoader`) are still
93
+ // dangerous regardless of the version pin — those need per-call
94
+ // inspection to preserve.
95
+ const requirementsTxt = this.dependencyContext?.python?.requirementsTxt;
96
+ const pyprojectToml = this.dependencyContext?.python?.pyprojectToml;
97
+ const pyYaml = (requirementsTxt ? resolvePyYamlFromRequirements(requirementsTxt) : null) ??
98
+ (pyprojectToml ? resolvePyYamlFromPyproject(pyprojectToml) : null);
99
+ const pyYamlSafeByDefault = pyYaml?.safeByDefault === true;
100
+ // Lazy: only split source lines when we actually need per-call
101
+ // inspection (i.e. gate D is active and could fire).
102
+ let sourceLines = null;
103
+ const getSourceLines = () => {
104
+ if (sourceLines === null)
105
+ sourceLines = code.split('\n');
106
+ return sourceLines;
107
+ };
108
+ let droppedFastjson = 0;
109
+ let droppedJackson = 0;
110
+ let droppedSnakeYaml = 0;
111
+ let droppedPyYaml = 0;
112
+ const kept = sinks.filter((sink) => {
113
+ if (sink.type !== 'deserialization')
114
+ return true;
115
+ if (!sink.method)
116
+ return true;
117
+ // Gate A — Fastjson
118
+ if (fastjsonHardened &&
119
+ FASTJSON_METHODS.has(sink.method) &&
120
+ (sink.class === undefined || FASTJSON_CLASSES.has(sink.class))) {
121
+ droppedFastjson++;
122
+ return false;
123
+ }
124
+ // Gate B — Jackson
125
+ if (jacksonSafe &&
126
+ JACKSON_METHODS.has(sink.method) &&
127
+ sink.class !== undefined &&
128
+ JACKSON_CLASSES.has(sink.class)) {
129
+ droppedJackson++;
130
+ return false;
131
+ }
132
+ // Gate C — SnakeYAML
133
+ if (snakeYamlSafe &&
134
+ SNAKEYAML_METHODS.has(sink.method) &&
135
+ sink.class !== undefined &&
136
+ SNAKEYAML_CLASSES.has(sink.class)) {
137
+ droppedSnakeYaml++;
138
+ return false;
139
+ }
140
+ // Gate D — PyYAML (Python). `sink.class` may be `undefined` for
141
+ // Python: unlike Java, Python calls do not carry receiver-type
142
+ // resolution into the CallInfo, so the taint-matcher's sinkMap
143
+ // assigns undefined for the class field. Accept undefined OR
144
+ // exact-`'yaml'` (mirrors Gate A's Fastjson check).
145
+ if (language === 'python' &&
146
+ pyYamlSafeByDefault &&
147
+ PYYAML_METHODS.has(sink.method) &&
148
+ (sink.class === undefined || PYYAML_CLASSES.has(sink.class)) &&
149
+ !fileHasUnsafePyYamlLoader(getSourceLines(), sink.line)) {
150
+ droppedPyYaml++;
151
+ return false;
152
+ }
153
+ return true;
154
+ });
155
+ const totalDropped = droppedFastjson + droppedJackson + droppedSnakeYaml + droppedPyYaml;
156
+ if (totalDropped > 0) {
157
+ // Mutate in place so downstream passes see the reduced sink set.
158
+ // Matches SinkSemanticsPass's contract.
159
+ sinks.length = 0;
160
+ sinks.push(...kept);
161
+ }
162
+ return { droppedFastjson, droppedJackson, droppedSnakeYaml, droppedPyYaml };
163
+ }
164
+ }
165
+ //# sourceMappingURL=deserialization-safety-gate-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deserialization-safety-gate-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/deserialization-safety-gate-pass.ts"],"names":[],"mappings":"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"}
@@ -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;AAgC7E;;;;;;;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;AA02ED;;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;AAgC7E;;;;;;;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;AA43ED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,WAAW,GACnB,OAAO,CAET"}
@@ -2407,6 +2407,24 @@ function calculateSinkConfidence(call, pattern) {
2407
2407
  if (pattern.class && call.receiver) {
2408
2408
  if (receiverMightBeClass(call.receiver, pattern.class)) {
2409
2409
  confidence += 0.1;
2410
+ // cognium-dev #260 — EXACT-match tiebreaker. When two patterns
2411
+ // for the same `(method, cwe)` both accept the receiver — one
2412
+ // exactly (e.g. class: 'Ctx' vs receiver 'Ctx') and one via
2413
+ // `receiverMightBeClass` fuzzy heuristics (e.g. class: 'Context'
2414
+ // vs receiver 'Ctx' via the includes/40%-length rule at
2415
+ // taint-matcher.ts:2366) — the fuzzy match used to win the
2416
+ // `sinkMap` dedup slot when it iterated first (both had the same
2417
+ // 0.9 confidence). The extra boost here guarantees the exact-
2418
+ // match pattern's `arg_positions` are the ones emitted on the
2419
+ // final sink. Fixes the fiber `c.Redirect(next)` shape whose
2420
+ // sink was emitted with the gin `class: 'Context'` pattern's
2421
+ // `arg_positions: [1]` instead of fiber `class: 'Ctx'`'s
2422
+ // `arg_positions: [0]`, dropping the taint flow silently.
2423
+ const receiverLc = call.receiver.toLowerCase();
2424
+ const classLc = pattern.class.toLowerCase();
2425
+ if (call.receiver === pattern.class || receiverLc === classLc) {
2426
+ confidence += 0.05;
2427
+ }
2410
2428
  }
2411
2429
  }
2412
2430
  // Higher confidence for critical severity patterns