circle-ir 3.178.0 → 3.180.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.
@@ -38,13 +38,16 @@
38
38
  *
39
39
  * See `docs/PASSES.md` for the canonical pass registry entry.
40
40
  */
41
- import { resolveFastjsonFromPom, fileReenablesFastjsonAutotype, fileEnablesJacksonPolymorphism, fileConfiguresSnakeYamlSafely, } from '../dependency-versions.js';
41
+ import { resolveFastjsonFromPom, resolveFastjsonFromGradle, resolveFastjsonFromGradleCatalog, resolvePyYamlFromRequirements, resolvePyYamlFromPyproject, fileHasUnsafePyYamlLoader, fileReenablesFastjsonAutotype, fileEnablesJacksonPolymorphism, fileConfiguresSnakeYamlSafely, } from '../dependency-versions.js';
42
42
  const FASTJSON_METHODS = new Set(['parseObject', 'parse']);
43
43
  const FASTJSON_CLASSES = new Set(['JSON', 'JSONObject']);
44
44
  const JACKSON_METHODS = new Set(['readValue', 'convertValue', 'treeToValue']);
45
45
  const JACKSON_CLASSES = new Set(['ObjectMapper', 'ObjectReader']);
46
46
  const SNAKEYAML_METHODS = new Set(['load', 'loadAs', 'loadAll']);
47
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']);
48
51
  export class DeserializationSafetyGatePass {
49
52
  dependencyContext;
50
53
  name = 'deserialization-safety-gate';
@@ -54,11 +57,11 @@ export class DeserializationSafetyGatePass {
54
57
  }
55
58
  run(ctx) {
56
59
  const { graph, language, code } = ctx;
57
- // Java-only for the MVP. Other languages (Python pyyaml, JS
58
- // Deserialize, Rust bincode) can extend the gate on their own
59
- // manifests in follow-up scopes.
60
- if (language !== 'java') {
61
- return { droppedFastjson: 0, droppedJackson: 0, droppedSnakeYaml: 0 };
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 };
62
65
  }
63
66
  // Same sink-source discovery as SinkSemanticsPass: prefer
64
67
  // SinkFilterResult when the real pipeline has run, otherwise fall
@@ -68,17 +71,48 @@ export class DeserializationSafetyGatePass {
68
71
  ? ctx.getResult('sink-filter').sinks
69
72
  : graph.ir.taint.sinks;
70
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.
71
78
  const pomXml = this.dependencyContext?.java?.pomXml;
72
- const fastjson = pomXml ? resolveFastjsonFromPom(pomXml) : null;
79
+ const buildGradle = this.dependencyContext?.java?.buildGradle;
80
+ const libsVersionsToml = this.dependencyContext?.java?.libsVersionsToml;
81
+ const fastjson = (pomXml ? resolveFastjsonFromPom(pomXml) : null) ??
82
+ (buildGradle ? resolveFastjsonFromGradle(buildGradle) : null) ??
83
+ (buildGradle && libsVersionsToml
84
+ ? resolveFastjsonFromGradleCatalog(buildGradle, libsVersionsToml)
85
+ : null);
73
86
  const fastjsonHardened = fastjson?.noneAutotype === true &&
74
87
  !fileReenablesFastjsonAutotype(code);
75
88
  // --- Gate B: Jackson polymorphism ---------------------------------------
76
89
  const jacksonSafe = !fileEnablesJacksonPolymorphism(code);
77
90
  // --- Gate C: SnakeYAML SafeConstructor ----------------------------------
78
91
  const snakeYamlSafe = fileConfiguresSnakeYamlSafely(code);
92
+ // --- Gate D: PyYAML ≥ 6.0 (Python) --------------------------------------
93
+ // Under pyyaml ≥ 6.0, `yaml.load(x)` without an explicit `Loader=`
94
+ // keyword arg raises TypeError (safe-by-default); callers that pass
95
+ // `Loader=SafeLoader` are safe; callers that pass an unsafe Loader
96
+ // (`Loader=Loader` / `UnsafeLoader` / `FullLoader`) are still
97
+ // dangerous regardless of the version pin — those need per-call
98
+ // inspection to preserve.
99
+ const requirementsTxt = this.dependencyContext?.python?.requirementsTxt;
100
+ const pyprojectToml = this.dependencyContext?.python?.pyprojectToml;
101
+ const pyYaml = (requirementsTxt ? resolvePyYamlFromRequirements(requirementsTxt) : null) ??
102
+ (pyprojectToml ? resolvePyYamlFromPyproject(pyprojectToml) : null);
103
+ const pyYamlSafeByDefault = pyYaml?.safeByDefault === true;
104
+ // Lazy: only split source lines when we actually need per-call
105
+ // inspection (i.e. gate D is active and could fire).
106
+ let sourceLines = null;
107
+ const getSourceLines = () => {
108
+ if (sourceLines === null)
109
+ sourceLines = code.split('\n');
110
+ return sourceLines;
111
+ };
79
112
  let droppedFastjson = 0;
80
113
  let droppedJackson = 0;
81
114
  let droppedSnakeYaml = 0;
115
+ let droppedPyYaml = 0;
82
116
  const kept = sinks.filter((sink) => {
83
117
  if (sink.type !== 'deserialization')
84
118
  return true;
@@ -107,16 +141,29 @@ export class DeserializationSafetyGatePass {
107
141
  droppedSnakeYaml++;
108
142
  return false;
109
143
  }
144
+ // Gate D — PyYAML (Python). `sink.class` may be `undefined` for
145
+ // Python: unlike Java, Python calls do not carry receiver-type
146
+ // resolution into the CallInfo, so the taint-matcher's sinkMap
147
+ // assigns undefined for the class field. Accept undefined OR
148
+ // exact-`'yaml'` (mirrors Gate A's Fastjson check).
149
+ if (language === 'python' &&
150
+ pyYamlSafeByDefault &&
151
+ PYYAML_METHODS.has(sink.method) &&
152
+ (sink.class === undefined || PYYAML_CLASSES.has(sink.class)) &&
153
+ !fileHasUnsafePyYamlLoader(getSourceLines(), sink.line)) {
154
+ droppedPyYaml++;
155
+ return false;
156
+ }
110
157
  return true;
111
158
  });
112
- const totalDropped = droppedFastjson + droppedJackson + droppedSnakeYaml;
159
+ const totalDropped = droppedFastjson + droppedJackson + droppedSnakeYaml + droppedPyYaml;
113
160
  if (totalDropped > 0) {
114
161
  // Mutate in place so downstream passes see the reduced sink set.
115
162
  // Matches SinkSemanticsPass's contract.
116
163
  sinks.length = 0;
117
164
  sinks.push(...kept);
118
165
  }
119
- return { droppedFastjson, droppedJackson, droppedSnakeYaml };
166
+ return { droppedFastjson, droppedJackson, droppedSnakeYaml, droppedPyYaml };
120
167
  }
121
168
  }
122
169
  //# sourceMappingURL=deserialization-safety-gate-pass.js.map
@@ -1 +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
+ {"version":3,"file":"deserialization-safety-gate-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/deserialization-safety-gate-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * PythonReceiverTaintFormatPass — cognium-dev #264 (Python receiver-taint).
3
+ *
4
+ * Emits `format_string` (CWE-134) findings for Python `.format(...)`
5
+ * calls whose RECEIVER is tainted. The pre-#264-Python-slice sink
6
+ * patterns key exclusively on `arg_positions`, which cannot represent
7
+ * a taint that lives on the call's receiver — Python's
8
+ * `str.format(*args)` shape puts the format template AS the receiver,
9
+ * not as an argument. Same is true of the `%`-operator shape
10
+ * (`taintedFmt % args`), which lives entirely in a binary-operator
11
+ * AST node, not a call.
12
+ *
13
+ * This pass takes the receiver-taint half of that gap by scanning
14
+ * `graph.ir.calls` for `.format(...)` calls whose receiver appears in
15
+ * the constant-propagation `tainted` set. On a hit, emits a direct
16
+ * `format_string` finding at the call site (severity `medium`, level
17
+ * `warning`).
18
+ *
19
+ * The `%`-operator half is genuinely bigger — needs a new AST scan
20
+ * over `binary_operator` nodes with format-shape RHS detection — and
21
+ * remains deferred on #264.
22
+ *
23
+ * Python-only. No-op on other languages.
24
+ *
25
+ * Pipeline slot: runs after `ConstantPropagationPass` so the
26
+ * `tainted` set is populated. Any pass position after
27
+ * `constant-propagation` is safe; placing near the sink-emission
28
+ * chain keeps like-with-like grouped.
29
+ *
30
+ * Direct-finding emission (via `ctx.addFinding`) rather than
31
+ * synthetic-sink emission because the format-string vulnerability
32
+ * here doesn't need a taint-source → sink flow trace: the
33
+ * `constant-propagation.tainted` set implicitly encodes that the
34
+ * receiver traces back to a source. Emitting a sink would require
35
+ * TaintPropagationPass to also understand receiver-taint, which
36
+ * would be a much broader engine change.
37
+ */
38
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
39
+ export interface PythonReceiverTaintFormatResult {
40
+ /** Number of `.format()` calls flagged this run. */
41
+ findingsEmitted: number;
42
+ }
43
+ export declare class PythonReceiverTaintFormatPass implements AnalysisPass<PythonReceiverTaintFormatResult> {
44
+ readonly name = "python-receiver-taint-format";
45
+ readonly category: "security";
46
+ run(ctx: PassContext): PythonReceiverTaintFormatResult;
47
+ }
48
+ //# sourceMappingURL=python-receiver-taint-format-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"python-receiver-taint-format-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/python-receiver-taint-format-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAG9E,MAAM,WAAW,+BAA+B;IAC9C,oDAAoD;IACpD,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,6BACX,YAAW,YAAY,CAAC,+BAA+B,CAAC;IAExD,QAAQ,CAAC,IAAI,kCAAkC;IAC/C,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,+BAA+B;CAsDvD"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * PythonReceiverTaintFormatPass — cognium-dev #264 (Python receiver-taint).
3
+ *
4
+ * Emits `format_string` (CWE-134) findings for Python `.format(...)`
5
+ * calls whose RECEIVER is tainted. The pre-#264-Python-slice sink
6
+ * patterns key exclusively on `arg_positions`, which cannot represent
7
+ * a taint that lives on the call's receiver — Python's
8
+ * `str.format(*args)` shape puts the format template AS the receiver,
9
+ * not as an argument. Same is true of the `%`-operator shape
10
+ * (`taintedFmt % args`), which lives entirely in a binary-operator
11
+ * AST node, not a call.
12
+ *
13
+ * This pass takes the receiver-taint half of that gap by scanning
14
+ * `graph.ir.calls` for `.format(...)` calls whose receiver appears in
15
+ * the constant-propagation `tainted` set. On a hit, emits a direct
16
+ * `format_string` finding at the call site (severity `medium`, level
17
+ * `warning`).
18
+ *
19
+ * The `%`-operator half is genuinely bigger — needs a new AST scan
20
+ * over `binary_operator` nodes with format-shape RHS detection — and
21
+ * remains deferred on #264.
22
+ *
23
+ * Python-only. No-op on other languages.
24
+ *
25
+ * Pipeline slot: runs after `ConstantPropagationPass` so the
26
+ * `tainted` set is populated. Any pass position after
27
+ * `constant-propagation` is safe; placing near the sink-emission
28
+ * chain keeps like-with-like grouped.
29
+ *
30
+ * Direct-finding emission (via `ctx.addFinding`) rather than
31
+ * synthetic-sink emission because the format-string vulnerability
32
+ * here doesn't need a taint-source → sink flow trace: the
33
+ * `constant-propagation.tainted` set implicitly encodes that the
34
+ * receiver traces back to a source. Emitting a sink would require
35
+ * TaintPropagationPass to also understand receiver-taint, which
36
+ * would be a much broader engine change.
37
+ */
38
+ export class PythonReceiverTaintFormatPass {
39
+ name = 'python-receiver-taint-format';
40
+ category = 'security';
41
+ run(ctx) {
42
+ if (ctx.language !== 'python') {
43
+ return { findingsEmitted: 0 };
44
+ }
45
+ const { calls, meta } = ctx.graph.ir;
46
+ const constProp = ctx.getResult('constant-propagation');
47
+ const tainted = constProp.tainted;
48
+ let count = 0;
49
+ for (const call of calls) {
50
+ if (call.method_name !== 'format')
51
+ continue;
52
+ const receiver = call.receiver;
53
+ if (!receiver)
54
+ continue;
55
+ // Only bare-identifier receivers can be looked up in the tainted
56
+ // set. Skip complex receivers (`obj.attr.format()`, `f(x).format()`)
57
+ // — those don't correspond to a variable name the taint tracker
58
+ // would have on file. Recall may improve later with attribute-
59
+ // chain resolution, but the direct-identifier form is the common
60
+ // shape for user-controlled templates.
61
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(receiver))
62
+ continue;
63
+ // constant-propagation stores both scoped (`method:var`) and
64
+ // unscoped forms in the tainted set. Check both.
65
+ const scopedName = call.in_method
66
+ ? `${call.in_method}:${receiver}`
67
+ : receiver;
68
+ if (!tainted.has(receiver) && !tainted.has(scopedName))
69
+ continue;
70
+ ctx.addFinding({
71
+ id: `format_string-${meta.file}-${call.location.line}-${receiver}`,
72
+ pass: this.name,
73
+ category: 'security',
74
+ rule_id: 'format_string',
75
+ cwe: 'CWE-134',
76
+ severity: 'medium',
77
+ level: 'warning',
78
+ message: `Format-string vulnerability: the format template \`${receiver}\` ` +
79
+ `is tainted (traces back to a taint source). Attacker can leak ` +
80
+ `object attributes via \`{obj.__class__.__mro__[…]}\` chains or ` +
81
+ `crash the process via unbounded specifiers. Use a literal ` +
82
+ `format string and pass user input as an argument instead.`,
83
+ file: meta.file,
84
+ line: call.location.line,
85
+ });
86
+ count++;
87
+ }
88
+ return { findingsEmitted: count };
89
+ }
90
+ }
91
+ //# sourceMappingURL=python-receiver-taint-format-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"python-receiver-taint-format-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/python-receiver-taint-format-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAUH,MAAM,OAAO,6BAA6B;IAG/B,IAAI,GAAG,8BAA8B,CAAC;IACtC,QAAQ,GAAG,UAAmB,CAAC;IAExC,GAAG,CAAC,GAAgB;QAClB,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC9B,OAAO,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;QAChC,CAAC;QAED,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACrC,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,CAC7B,sBAAsB,CACvB,CAAC;QACF,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC;QAElC,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,WAAW,KAAK,QAAQ;gBAAE,SAAS;YAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;YAC/B,IAAI,CAAC,QAAQ;gBAAE,SAAS;YAExB,iEAAiE;YACjE,qEAAqE;YACrE,gEAAgE;YAChE,+DAA+D;YAC/D,iEAAiE;YACjE,uCAAuC;YACvC,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAEzD,6DAA6D;YAC7D,iDAAiD;YACjD,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS;gBAC/B,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,IAAI,QAAQ,EAAE;gBACjC,CAAC,CAAC,QAAQ,CAAC;YACb,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;gBAAE,SAAS;YAEjE,GAAG,CAAC,UAAU,CAAC;gBACb,EAAE,EAAE,iBAAiB,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,QAAQ,EAAE;gBAClE,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,QAAQ,EAAE,UAAU;gBACpB,OAAO,EAAE,eAAe;gBACxB,GAAG,EAAE,SAAS;gBACd,QAAQ,EAAE,QAAQ;gBAClB,KAAK,EAAE,SAAS;gBAChB,OAAO,EACL,sDAAsD,QAAQ,KAAK;oBACnE,gEAAgE;oBAChE,iEAAiE;oBACjE,4DAA4D;oBAC5D,2DAA2D;gBAC7D,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI;aACzB,CAAC,CAAC;YACH,KAAK,EAAE,CAAC;QACV,CAAC;QAED,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC;IACpC,CAAC;CACF"}
@@ -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