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.
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +16 -0
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/dependency-versions.d.ts +214 -0
- package/dist/analysis/dependency-versions.d.ts.map +1 -0
- package/dist/analysis/dependency-versions.js +352 -0
- package/dist/analysis/dependency-versions.js.map +1 -0
- package/dist/analysis/note-coalescer.d.ts +46 -0
- package/dist/analysis/note-coalescer.d.ts.map +1 -0
- package/dist/analysis/note-coalescer.js +106 -0
- package/dist/analysis/note-coalescer.js.map +1 -0
- package/dist/analysis/passes/deserialization-safety-gate-pass.d.ts +60 -0
- package/dist/analysis/passes/deserialization-safety-gate-pass.d.ts.map +1 -0
- package/dist/analysis/passes/deserialization-safety-gate-pass.js +165 -0
- package/dist/analysis/passes/deserialization-safety-gate-pass.js.map +1 -0
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +18 -0
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts +56 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +21 -1
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +259 -1
- package/dist/core/circle-ir-core.cjs +21 -0
- package/dist/core/circle-ir-core.js +21 -0
- package/dist/types/index.d.ts +24 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -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;
|
|
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
|