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,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency-manifest helpers for the deserialization-safety-gate pass.
|
|
3
|
+
*
|
|
4
|
+
* cognium-dev #258 — dependency-version-aware sink gating. The engine
|
|
5
|
+
* never reads the filesystem (Pillar I / browser-safety); the caller
|
|
6
|
+
* (cognium-dev CLI or `analyzeProject`) reads the manifest and passes
|
|
7
|
+
* it as raw text via `AnalyzerOptions.dependencyContext`. Helpers here
|
|
8
|
+
* turn that raw text into the boolean predicates the gate needs.
|
|
9
|
+
*
|
|
10
|
+
* All parsers here are string-scoped: regex over the raw manifest
|
|
11
|
+
* rather than a full XML/JSON tree. That is deliberate — the gate only
|
|
12
|
+
* needs a handful of narrow signals (Fastjson version, presence of a
|
|
13
|
+
* safe classifier), and every existing runtime dep in circle-ir is
|
|
14
|
+
* either `web-tree-sitter` or `yaml`. Pulling in an XML parser purely
|
|
15
|
+
* for one Fastjson property would violate the minimal-dependencies
|
|
16
|
+
* guardrail.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Result of extracting the effective Fastjson coordinate from a
|
|
20
|
+
* `pom.xml` string. `version` is the raw value as declared (including
|
|
21
|
+
* any classifier suffix); `noneAutotype` is true when the version
|
|
22
|
+
* literally matches Alibaba's `_noneautotype` hardened build family
|
|
23
|
+
* (any patch level, e.g. `1.2.83_noneautotype`, `1.2.85_noneautotype`).
|
|
24
|
+
*/
|
|
25
|
+
export interface FastjsonPomResolution {
|
|
26
|
+
version: string;
|
|
27
|
+
noneAutotype: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Extract the effective Fastjson version from a `pom.xml`. Two sources
|
|
31
|
+
* are consulted:
|
|
32
|
+
*
|
|
33
|
+
* 1. A `<properties>` entry named exactly `<fastjson.version>` — the
|
|
34
|
+
* idiomatic way Maven projects centralise a dep version they
|
|
35
|
+
* reference in a `<dependencies>` block. This is where
|
|
36
|
+
* alibaba/Sentinel pins `1.2.83_noneautotype`.
|
|
37
|
+
* 2. A `<dependency>` block whose `<groupId>` is `com.alibaba` and
|
|
38
|
+
* `<artifactId>` is `fastjson` — read as a fallback when the
|
|
39
|
+
* version is declared inline rather than as a property.
|
|
40
|
+
*
|
|
41
|
+
* Returns `null` when no Fastjson coordinate can be resolved from the
|
|
42
|
+
* pom (no properties entry AND no matching dependency block, or the
|
|
43
|
+
* dependency uses a `${...}` reference the properties block doesn't
|
|
44
|
+
* define).
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveFastjsonFromPom(pomXml: string): FastjsonPomResolution | null;
|
|
47
|
+
/**
|
|
48
|
+
* Extract the effective Fastjson version from a Gradle build script
|
|
49
|
+
* (`build.gradle` Groovy DSL or `build.gradle.kts` Kotlin DSL).
|
|
50
|
+
*
|
|
51
|
+
* cognium-dev #261 (Gradle-first slice extending #258's pom.xml gate).
|
|
52
|
+
*
|
|
53
|
+
* Recognises three declaration shapes:
|
|
54
|
+
*
|
|
55
|
+
* 1. Direct literal — the classic Groovy / Kotlin single-string form:
|
|
56
|
+
* implementation 'com.alibaba:fastjson:1.2.83_noneautotype'
|
|
57
|
+
* implementation "com.alibaba:fastjson:1.2.83_noneautotype"
|
|
58
|
+
* implementation("com.alibaba:fastjson:1.2.83_noneautotype")
|
|
59
|
+
* Also `api`, `compile`, `runtimeOnly`, `testImplementation`, … — the
|
|
60
|
+
* configuration keyword is not part of the regex; only the
|
|
61
|
+
* `group:artifact:version` triple is matched, so any dependency-
|
|
62
|
+
* configuration prefix works.
|
|
63
|
+
*
|
|
64
|
+
* 2. Groovy interpolation — `implementation "com.alibaba:fastjson:${fastjsonVersion}"`
|
|
65
|
+
* with the property defined via `ext { fastjsonVersion = '1.2.83_noneautotype' }`,
|
|
66
|
+
* `def fastjsonVersion = '1.2.83_noneautotype'`, or top-level `fastjsonVersion = '…'`.
|
|
67
|
+
*
|
|
68
|
+
* 3. Kotlin interpolation — `implementation("com.alibaba:fastjson:$fastjsonVersion")`
|
|
69
|
+
* with the property defined via `val fastjsonVersion = "1.2.83_noneautotype"`
|
|
70
|
+
* or `const val fastjsonVersion = "…"`.
|
|
71
|
+
*
|
|
72
|
+
* Returns `null` on miss (no direct declaration AND no resolvable
|
|
73
|
+
* property reference). The `DeserializationSafetyGatePass` then falls
|
|
74
|
+
* through to its default "do not drop" behaviour on the sink.
|
|
75
|
+
*
|
|
76
|
+
* NOT recognised in this MVP (deferred, follow-ups on #261):
|
|
77
|
+
* - `platform(...)` / `enforcedPlatform(...)` BOM version imports
|
|
78
|
+
* - Version-catalog `libs.versions.toml` references (`libs.fastjson`)
|
|
79
|
+
* - `constraints { }` block versions
|
|
80
|
+
* - `subprojects { }` / `allprojects { }` conditional declarations
|
|
81
|
+
*/
|
|
82
|
+
export declare function resolveFastjsonFromGradle(buildGradle: string): FastjsonPomResolution | null;
|
|
83
|
+
/**
|
|
84
|
+
* Result of extracting the effective PyYAML version. `safeByDefault` is
|
|
85
|
+
* true when the parsed version is ≥ 6.0; at that point pyyaml.load()
|
|
86
|
+
* without an explicit `Loader=` keyword argument raises TypeError
|
|
87
|
+
* instead of silently invoking the unsafe default Loader.
|
|
88
|
+
*
|
|
89
|
+
* See https://github.com/yaml/pyyaml/blob/master/CHANGES for the full
|
|
90
|
+
* 6.0 breakage story. The gate consumer must ALSO check the call site
|
|
91
|
+
* for an explicit `Loader=` keyword (fileHasUnsafePyYamlLoader below)
|
|
92
|
+
* — a caller under pyyaml ≥ 6.0 that explicitly passes
|
|
93
|
+
* `Loader=yaml.Loader` or `Loader=yaml.UnsafeLoader` is still
|
|
94
|
+
* dangerous regardless of the version pin.
|
|
95
|
+
*/
|
|
96
|
+
export interface PyYamlResolution {
|
|
97
|
+
version: string;
|
|
98
|
+
safeByDefault: boolean;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Extract the effective PyYAML version from a `requirements.txt` file.
|
|
102
|
+
* Recognises the standard PEP 508 shapes (`==`, `>=`, `~=`, `>`,
|
|
103
|
+
* exact pins with trailing modifiers). Package name matched
|
|
104
|
+
* case-insensitively (both `PyYAML` and `pyyaml` are common in the
|
|
105
|
+
* wild).
|
|
106
|
+
*
|
|
107
|
+
* Returns null when no pyyaml line is found or the version string
|
|
108
|
+
* cannot be parsed as `M.m[.p]`. Non-strict on trailing environment
|
|
109
|
+
* markers (` ; python_version >= '3.6'`), which are common on
|
|
110
|
+
* requirements.txt lines.
|
|
111
|
+
*/
|
|
112
|
+
export declare function resolvePyYamlFromRequirements(requirementsTxt: string): PyYamlResolution | null;
|
|
113
|
+
/**
|
|
114
|
+
* Extract the effective PyYAML version from a `pyproject.toml`.
|
|
115
|
+
* Recognises the common Poetry and PEP 621 shapes:
|
|
116
|
+
*
|
|
117
|
+
* Poetry — under `[tool.poetry.dependencies]`:
|
|
118
|
+
* PyYAML = "6.0"
|
|
119
|
+
* pyyaml = "^6.0.1"
|
|
120
|
+
* pyyaml = { version = "6.0", extras = ["..."] }
|
|
121
|
+
*
|
|
122
|
+
* PEP 621 — under `[project]` `dependencies` array:
|
|
123
|
+
* dependencies = [ "PyYAML>=6.0", ... ]
|
|
124
|
+
*
|
|
125
|
+
* As with the requirements.txt resolver, non-strict on markers /
|
|
126
|
+
* modifiers; regex-based rather than a full TOML parser to keep the
|
|
127
|
+
* runtime-dep list minimal (Pillar I minimal-dependencies principle).
|
|
128
|
+
*/
|
|
129
|
+
export declare function resolvePyYamlFromPyproject(pyprojectToml: string): PyYamlResolution | null;
|
|
130
|
+
/**
|
|
131
|
+
* True when `version` (as `M.m[.p]`) is ≥ 6.0. Handles bare `6`,
|
|
132
|
+
* `6.0`, `6.0.1`, `7`, `10.0`, etc. Returns false on malformed input
|
|
133
|
+
* (defensive — the gate defaults to *do not drop* on missing signal).
|
|
134
|
+
*/
|
|
135
|
+
export declare function isPyYamlVersionSafeByDefault(version: string): boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Return true when the file contains an explicit unsafe `Loader=`
|
|
138
|
+
* keyword argument on a `yaml.load(...)` call at or shortly after
|
|
139
|
+
* `sinkLine`. Recognises the well-known unsafe loaders and their
|
|
140
|
+
* qualified forms:
|
|
141
|
+
*
|
|
142
|
+
* Loader=Loader Loader=yaml.Loader
|
|
143
|
+
* Loader=UnsafeLoader Loader=yaml.UnsafeLoader
|
|
144
|
+
* Loader=FullLoader Loader=yaml.FullLoader (technically safer
|
|
145
|
+
* than Loader; still
|
|
146
|
+
* permits arbitrary
|
|
147
|
+
* Python object types)
|
|
148
|
+
*
|
|
149
|
+
* SafeLoader / CSafeLoader / BaseLoader / CBaseLoader are all safe and
|
|
150
|
+
* are NOT matched here.
|
|
151
|
+
*
|
|
152
|
+
* Scans `sinkLine` through the next 9 lines to catch multi-line call
|
|
153
|
+
* shapes; if the closing `)` appears before the window ends the scan
|
|
154
|
+
* stops there. Regex-based (no AST inspection) to stay consistent with
|
|
155
|
+
* the sink-filter-pass conventions and avoid pulling parse state into
|
|
156
|
+
* the gate.
|
|
157
|
+
*/
|
|
158
|
+
export declare function fileHasUnsafePyYamlLoader(sourceLines: string[], sinkLine: number): boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Return true when the given source text contains an in-file call that
|
|
161
|
+
* re-enables Fastjson autotype. Even a `_noneautotype` build does not
|
|
162
|
+
* protect against code that programmatically re-enables the feature
|
|
163
|
+
* (which the hardened build documents as impossible, but the classifier
|
|
164
|
+
* is a build-time strip, not a runtime lock). Defense-in-depth for the
|
|
165
|
+
* `resolveFastjsonFromPom` gate.
|
|
166
|
+
*/
|
|
167
|
+
export declare function fileReenablesFastjsonAutotype(source: string): boolean;
|
|
168
|
+
/**
|
|
169
|
+
* Return true when the given Java source contains a call that enables
|
|
170
|
+
* Jackson polymorphic type handling — either the legacy
|
|
171
|
+
* `enableDefaultTyping(...)` (deprecated in Jackson 2.10+ but still
|
|
172
|
+
* shipped) or the current `activateDefaultTyping(...)`.
|
|
173
|
+
*
|
|
174
|
+
* When neither is present in the file (and no `@JsonTypeInfo` is used
|
|
175
|
+
* on the target type — best-effort scan below), Jackson's default
|
|
176
|
+
* behaviour since 2.10 is safe: `ObjectMapper.readValue(json,
|
|
177
|
+
* targetType)` cannot instantiate arbitrary classes. The gate uses
|
|
178
|
+
* this to distinguish a genuine `readValue` sink from a safely-
|
|
179
|
+
* configured one.
|
|
180
|
+
*
|
|
181
|
+
* `@JsonTypeInfo` scan is intentionally file-local. A `@JsonTypeInfo`
|
|
182
|
+
* annotation on a target type in a different file would still allow
|
|
183
|
+
* polymorphic construction, but the engine treats that as unknown
|
|
184
|
+
* risk and preserves the sink (the gate only fires when the *file
|
|
185
|
+
* itself* provides positive evidence of a safe configuration).
|
|
186
|
+
*/
|
|
187
|
+
export declare function fileEnablesJacksonPolymorphism(source: string): boolean;
|
|
188
|
+
/**
|
|
189
|
+
* Return true when the given Java source contains a
|
|
190
|
+
* `new Yaml(new SafeConstructor(...))` or an equivalent hardened
|
|
191
|
+
* SnakeYAML constructor. When any `Yaml` instance in the file is
|
|
192
|
+
* built with the safe constructor family, we assume the file's
|
|
193
|
+
* `Yaml.load(...)` calls are safely configured; the gate then drops
|
|
194
|
+
* the deserialization sink for those calls.
|
|
195
|
+
*
|
|
196
|
+
* Recognised safe constructor classes (SnakeYAML 1.x + 2.x):
|
|
197
|
+
* - SafeConstructor — canonical safe loader
|
|
198
|
+
* - SafeSchema (rare, YAML 1.2) — safe schema-based loader
|
|
199
|
+
*
|
|
200
|
+
* Recognised safe factory calls (SnakeYAML 2.x LoaderOptions API):
|
|
201
|
+
* - Yaml.load() with a Constructor that extends SafeConstructor
|
|
202
|
+
* (heuristic: any `SafeConstructor`-typed variable is safe)
|
|
203
|
+
*
|
|
204
|
+
* NOTE: A file that mixes `new Yaml(new SafeConstructor())` with
|
|
205
|
+
* `new Yaml(new Constructor(SomeClass.class))` on separate call sites
|
|
206
|
+
* would over-suppress the unsafe site. In practice this is rare (SAST
|
|
207
|
+
* teams write one wrapper per file), and the current sink-filter
|
|
208
|
+
* stage 9b handles the analogous compiled-template case with the same
|
|
209
|
+
* file-scoped heuristic. If it becomes a problem, tighten to
|
|
210
|
+
* receiver-scoped: check the specific `Yaml` receiver that carries
|
|
211
|
+
* the load() call.
|
|
212
|
+
*/
|
|
213
|
+
export declare function fileConfiguresSnakeYamlSafely(source: string): boolean;
|
|
214
|
+
//# sourceMappingURL=dependency-versions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependency-versions.d.ts","sourceRoot":"","sources":["../../src/analysis/dependency-versions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CA8BnF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CAsC3F;AAMD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAC3C,eAAe,EAAE,MAAM,GACtB,gBAAgB,GAAG,IAAI,CAuBzB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CACxC,aAAa,EAAE,MAAM,GACpB,gBAAgB,GAAG,IAAI,CAiCzB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAIrE;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,EAAE,EACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAkBT;AAED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAKrE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAUtE;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAOrE"}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency-manifest helpers for the deserialization-safety-gate pass.
|
|
3
|
+
*
|
|
4
|
+
* cognium-dev #258 — dependency-version-aware sink gating. The engine
|
|
5
|
+
* never reads the filesystem (Pillar I / browser-safety); the caller
|
|
6
|
+
* (cognium-dev CLI or `analyzeProject`) reads the manifest and passes
|
|
7
|
+
* it as raw text via `AnalyzerOptions.dependencyContext`. Helpers here
|
|
8
|
+
* turn that raw text into the boolean predicates the gate needs.
|
|
9
|
+
*
|
|
10
|
+
* All parsers here are string-scoped: regex over the raw manifest
|
|
11
|
+
* rather than a full XML/JSON tree. That is deliberate — the gate only
|
|
12
|
+
* needs a handful of narrow signals (Fastjson version, presence of a
|
|
13
|
+
* safe classifier), and every existing runtime dep in circle-ir is
|
|
14
|
+
* either `web-tree-sitter` or `yaml`. Pulling in an XML parser purely
|
|
15
|
+
* for one Fastjson property would violate the minimal-dependencies
|
|
16
|
+
* guardrail.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Extract the effective Fastjson version from a `pom.xml`. Two sources
|
|
20
|
+
* are consulted:
|
|
21
|
+
*
|
|
22
|
+
* 1. A `<properties>` entry named exactly `<fastjson.version>` — the
|
|
23
|
+
* idiomatic way Maven projects centralise a dep version they
|
|
24
|
+
* reference in a `<dependencies>` block. This is where
|
|
25
|
+
* alibaba/Sentinel pins `1.2.83_noneautotype`.
|
|
26
|
+
* 2. A `<dependency>` block whose `<groupId>` is `com.alibaba` and
|
|
27
|
+
* `<artifactId>` is `fastjson` — read as a fallback when the
|
|
28
|
+
* version is declared inline rather than as a property.
|
|
29
|
+
*
|
|
30
|
+
* Returns `null` when no Fastjson coordinate can be resolved from the
|
|
31
|
+
* pom (no properties entry AND no matching dependency block, or the
|
|
32
|
+
* dependency uses a `${...}` reference the properties block doesn't
|
|
33
|
+
* define).
|
|
34
|
+
*/
|
|
35
|
+
export function resolveFastjsonFromPom(pomXml) {
|
|
36
|
+
if (!pomXml)
|
|
37
|
+
return null;
|
|
38
|
+
const propMatch = pomXml.match(/<fastjson\.version>\s*([^<\s]+)\s*<\/fastjson\.version>/);
|
|
39
|
+
if (propMatch) {
|
|
40
|
+
const version = propMatch[1];
|
|
41
|
+
return { version, noneAutotype: /_noneautotype/i.test(version) };
|
|
42
|
+
}
|
|
43
|
+
// Fallback: scan every <dependency>...</dependency> block for
|
|
44
|
+
// com.alibaba:fastjson (or fastjson2). Non-greedy so we don't fuse
|
|
45
|
+
// sibling blocks. Case-sensitive on artifact / group; Maven's own
|
|
46
|
+
// resolution is case-sensitive too.
|
|
47
|
+
const depRe = /<dependency>[\s\S]*?<\/dependency>/g;
|
|
48
|
+
let m;
|
|
49
|
+
while ((m = depRe.exec(pomXml)) !== null) {
|
|
50
|
+
const block = m[0];
|
|
51
|
+
const gid = block.match(/<groupId>\s*([^<\s]+)\s*<\/groupId>/)?.[1];
|
|
52
|
+
const aid = block.match(/<artifactId>\s*([^<\s]+)\s*<\/artifactId>/)?.[1];
|
|
53
|
+
if (gid !== 'com.alibaba')
|
|
54
|
+
continue;
|
|
55
|
+
if (aid !== 'fastjson' && aid !== 'fastjson2')
|
|
56
|
+
continue;
|
|
57
|
+
const ver = block.match(/<version>\s*([^<\s]+)\s*<\/version>/)?.[1];
|
|
58
|
+
if (!ver)
|
|
59
|
+
continue;
|
|
60
|
+
// `${fastjson.version}` reference the properties block did not
|
|
61
|
+
// resolve — treat as unknown so the gate defaults to fire.
|
|
62
|
+
if (/^\$\{/.test(ver))
|
|
63
|
+
return null;
|
|
64
|
+
return { version: ver, noneAutotype: /_noneautotype/i.test(ver) };
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Extract the effective Fastjson version from a Gradle build script
|
|
70
|
+
* (`build.gradle` Groovy DSL or `build.gradle.kts` Kotlin DSL).
|
|
71
|
+
*
|
|
72
|
+
* cognium-dev #261 (Gradle-first slice extending #258's pom.xml gate).
|
|
73
|
+
*
|
|
74
|
+
* Recognises three declaration shapes:
|
|
75
|
+
*
|
|
76
|
+
* 1. Direct literal — the classic Groovy / Kotlin single-string form:
|
|
77
|
+
* implementation 'com.alibaba:fastjson:1.2.83_noneautotype'
|
|
78
|
+
* implementation "com.alibaba:fastjson:1.2.83_noneautotype"
|
|
79
|
+
* implementation("com.alibaba:fastjson:1.2.83_noneautotype")
|
|
80
|
+
* Also `api`, `compile`, `runtimeOnly`, `testImplementation`, … — the
|
|
81
|
+
* configuration keyword is not part of the regex; only the
|
|
82
|
+
* `group:artifact:version` triple is matched, so any dependency-
|
|
83
|
+
* configuration prefix works.
|
|
84
|
+
*
|
|
85
|
+
* 2. Groovy interpolation — `implementation "com.alibaba:fastjson:${fastjsonVersion}"`
|
|
86
|
+
* with the property defined via `ext { fastjsonVersion = '1.2.83_noneautotype' }`,
|
|
87
|
+
* `def fastjsonVersion = '1.2.83_noneautotype'`, or top-level `fastjsonVersion = '…'`.
|
|
88
|
+
*
|
|
89
|
+
* 3. Kotlin interpolation — `implementation("com.alibaba:fastjson:$fastjsonVersion")`
|
|
90
|
+
* with the property defined via `val fastjsonVersion = "1.2.83_noneautotype"`
|
|
91
|
+
* or `const val fastjsonVersion = "…"`.
|
|
92
|
+
*
|
|
93
|
+
* Returns `null` on miss (no direct declaration AND no resolvable
|
|
94
|
+
* property reference). The `DeserializationSafetyGatePass` then falls
|
|
95
|
+
* through to its default "do not drop" behaviour on the sink.
|
|
96
|
+
*
|
|
97
|
+
* NOT recognised in this MVP (deferred, follow-ups on #261):
|
|
98
|
+
* - `platform(...)` / `enforcedPlatform(...)` BOM version imports
|
|
99
|
+
* - Version-catalog `libs.versions.toml` references (`libs.fastjson`)
|
|
100
|
+
* - `constraints { }` block versions
|
|
101
|
+
* - `subprojects { }` / `allprojects { }` conditional declarations
|
|
102
|
+
*/
|
|
103
|
+
export function resolveFastjsonFromGradle(buildGradle) {
|
|
104
|
+
if (!buildGradle)
|
|
105
|
+
return null;
|
|
106
|
+
// Shape 1 — direct literal declaration. Accepts single-quoted,
|
|
107
|
+
// double-quoted, or paren-wrapped ("Kotlin") forms. The version is
|
|
108
|
+
// any non-quote / non-`$` / non-`)` / non-whitespace run.
|
|
109
|
+
const directRe = /['"(]\s*com\.alibaba:fastjson:([^'"$\s)]+)\s*['")]/;
|
|
110
|
+
const direct = buildGradle.match(directRe);
|
|
111
|
+
if (direct) {
|
|
112
|
+
const version = direct[1];
|
|
113
|
+
return { version, noneAutotype: /_noneautotype/i.test(version) };
|
|
114
|
+
}
|
|
115
|
+
// Shapes 2 + 3 — property reference. Capture the property name (either
|
|
116
|
+
// `${name}` Groovy form or `$name` bare form; Kotlin uses the same
|
|
117
|
+
// string-template syntax as Groovy for this case).
|
|
118
|
+
const propRefRe = /['"(]\s*com\.alibaba:fastjson:\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?\s*['")]/;
|
|
119
|
+
const propRef = buildGradle.match(propRefRe);
|
|
120
|
+
if (!propRef)
|
|
121
|
+
return null;
|
|
122
|
+
const propName = propRef[1];
|
|
123
|
+
// Property definition — accept:
|
|
124
|
+
// `fastjsonVersion = 'X'` (Groovy top-level or ext)
|
|
125
|
+
// `fastjsonVersion = "X"` (Groovy interpolated)
|
|
126
|
+
// `def fastjsonVersion = 'X'` (Groovy local)
|
|
127
|
+
// `val fastjsonVersion = "X"` (Kotlin)
|
|
128
|
+
// `const val fastjsonVersion = "X"` (Kotlin)
|
|
129
|
+
// `fastjsonVersion: 'X'` (Groovy map-syntax, rare)
|
|
130
|
+
// Any leading whitespace / newlines / `=` / `:` before the quote.
|
|
131
|
+
const defRe = new RegExp(`\\b${propName}\\s*[=:]\\s*['"]([^'"\\s]+)['"]`);
|
|
132
|
+
const def = buildGradle.match(defRe);
|
|
133
|
+
if (!def)
|
|
134
|
+
return null;
|
|
135
|
+
const version = def[1];
|
|
136
|
+
return { version, noneAutotype: /_noneautotype/i.test(version) };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Extract the effective PyYAML version from a `requirements.txt` file.
|
|
140
|
+
* Recognises the standard PEP 508 shapes (`==`, `>=`, `~=`, `>`,
|
|
141
|
+
* exact pins with trailing modifiers). Package name matched
|
|
142
|
+
* case-insensitively (both `PyYAML` and `pyyaml` are common in the
|
|
143
|
+
* wild).
|
|
144
|
+
*
|
|
145
|
+
* Returns null when no pyyaml line is found or the version string
|
|
146
|
+
* cannot be parsed as `M.m[.p]`. Non-strict on trailing environment
|
|
147
|
+
* markers (` ; python_version >= '3.6'`), which are common on
|
|
148
|
+
* requirements.txt lines.
|
|
149
|
+
*/
|
|
150
|
+
export function resolvePyYamlFromRequirements(requirementsTxt) {
|
|
151
|
+
if (!requirementsTxt)
|
|
152
|
+
return null;
|
|
153
|
+
// Iterate line-by-line so a fatal parse error on one line doesn't
|
|
154
|
+
// sink the whole file. Comment lines and `-r other.txt` includes are
|
|
155
|
+
// ignored (we don't recurse into included files here — the caller
|
|
156
|
+
// controls what manifest string is passed).
|
|
157
|
+
for (const rawLine of requirementsTxt.split(/\r?\n/)) {
|
|
158
|
+
const line = rawLine.replace(/#.*$/, '').trim();
|
|
159
|
+
if (!line)
|
|
160
|
+
continue;
|
|
161
|
+
if (line.startsWith('-'))
|
|
162
|
+
continue;
|
|
163
|
+
// Match: `PyYAML==6.0`, `pyyaml>=6.0.1`, `PyYAML ~= 6.0`,
|
|
164
|
+
// `pyyaml>6`, `PyYAML==6.0 ; python_version>='3.6'`
|
|
165
|
+
const m = line.match(/^(pyyaml)\s*(==|>=|~=|>|===)\s*(\d+(?:\.\d+)*)/i);
|
|
166
|
+
if (!m)
|
|
167
|
+
continue;
|
|
168
|
+
const version = m[3];
|
|
169
|
+
return { version, safeByDefault: isPyYamlVersionSafeByDefault(version) };
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Extract the effective PyYAML version from a `pyproject.toml`.
|
|
175
|
+
* Recognises the common Poetry and PEP 621 shapes:
|
|
176
|
+
*
|
|
177
|
+
* Poetry — under `[tool.poetry.dependencies]`:
|
|
178
|
+
* PyYAML = "6.0"
|
|
179
|
+
* pyyaml = "^6.0.1"
|
|
180
|
+
* pyyaml = { version = "6.0", extras = ["..."] }
|
|
181
|
+
*
|
|
182
|
+
* PEP 621 — under `[project]` `dependencies` array:
|
|
183
|
+
* dependencies = [ "PyYAML>=6.0", ... ]
|
|
184
|
+
*
|
|
185
|
+
* As with the requirements.txt resolver, non-strict on markers /
|
|
186
|
+
* modifiers; regex-based rather than a full TOML parser to keep the
|
|
187
|
+
* runtime-dep list minimal (Pillar I minimal-dependencies principle).
|
|
188
|
+
*/
|
|
189
|
+
export function resolvePyYamlFromPyproject(pyprojectToml) {
|
|
190
|
+
if (!pyprojectToml)
|
|
191
|
+
return null;
|
|
192
|
+
// Poetry key = string form.
|
|
193
|
+
// PyYAML = "6.0" PyYAML = "^6.0" pyyaml = "~=6.0"
|
|
194
|
+
const poetryLine = pyprojectToml.match(/^\s*(pyyaml)\s*=\s*"([\^~=<>]*)(\d+(?:\.\d+)*)"/im);
|
|
195
|
+
if (poetryLine) {
|
|
196
|
+
const version = poetryLine[3];
|
|
197
|
+
return { version, safeByDefault: isPyYamlVersionSafeByDefault(version) };
|
|
198
|
+
}
|
|
199
|
+
// Poetry table form: `pyyaml = { version = "6.0", … }`
|
|
200
|
+
const poetryTable = pyprojectToml.match(/^\s*(pyyaml)\s*=\s*\{[^}]*\bversion\s*=\s*"([\^~=<>]*)(\d+(?:\.\d+)*)"/im);
|
|
201
|
+
if (poetryTable) {
|
|
202
|
+
const version = poetryTable[3];
|
|
203
|
+
return { version, safeByDefault: isPyYamlVersionSafeByDefault(version) };
|
|
204
|
+
}
|
|
205
|
+
// PEP 621 array element: within a `dependencies = [ ... ]` block, a
|
|
206
|
+
// string literal like `"PyYAML>=6.0"` or `"pyyaml==6.0.1"`.
|
|
207
|
+
const pep621 = pyprojectToml.match(/"(pyyaml)\s*(==|>=|~=|>|===)\s*(\d+(?:\.\d+)*)/i);
|
|
208
|
+
if (pep621) {
|
|
209
|
+
const version = pep621[3];
|
|
210
|
+
return { version, safeByDefault: isPyYamlVersionSafeByDefault(version) };
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* True when `version` (as `M.m[.p]`) is ≥ 6.0. Handles bare `6`,
|
|
216
|
+
* `6.0`, `6.0.1`, `7`, `10.0`, etc. Returns false on malformed input
|
|
217
|
+
* (defensive — the gate defaults to *do not drop* on missing signal).
|
|
218
|
+
*/
|
|
219
|
+
export function isPyYamlVersionSafeByDefault(version) {
|
|
220
|
+
const parts = version.split('.').map((s) => Number.parseInt(s, 10));
|
|
221
|
+
if (parts.length === 0 || !Number.isFinite(parts[0]))
|
|
222
|
+
return false;
|
|
223
|
+
return parts[0] >= 6;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Return true when the file contains an explicit unsafe `Loader=`
|
|
227
|
+
* keyword argument on a `yaml.load(...)` call at or shortly after
|
|
228
|
+
* `sinkLine`. Recognises the well-known unsafe loaders and their
|
|
229
|
+
* qualified forms:
|
|
230
|
+
*
|
|
231
|
+
* Loader=Loader Loader=yaml.Loader
|
|
232
|
+
* Loader=UnsafeLoader Loader=yaml.UnsafeLoader
|
|
233
|
+
* Loader=FullLoader Loader=yaml.FullLoader (technically safer
|
|
234
|
+
* than Loader; still
|
|
235
|
+
* permits arbitrary
|
|
236
|
+
* Python object types)
|
|
237
|
+
*
|
|
238
|
+
* SafeLoader / CSafeLoader / BaseLoader / CBaseLoader are all safe and
|
|
239
|
+
* are NOT matched here.
|
|
240
|
+
*
|
|
241
|
+
* Scans `sinkLine` through the next 9 lines to catch multi-line call
|
|
242
|
+
* shapes; if the closing `)` appears before the window ends the scan
|
|
243
|
+
* stops there. Regex-based (no AST inspection) to stay consistent with
|
|
244
|
+
* the sink-filter-pass conventions and avoid pulling parse state into
|
|
245
|
+
* the gate.
|
|
246
|
+
*/
|
|
247
|
+
export function fileHasUnsafePyYamlLoader(sourceLines, sinkLine) {
|
|
248
|
+
const start = Math.max(0, sinkLine - 1);
|
|
249
|
+
const end = Math.min(sourceLines.length, start + 10);
|
|
250
|
+
const unsafeRe = /\bLoader\s*=\s*(?:yaml\s*\.\s*)?(?:Loader|UnsafeLoader|FullLoader)\b/;
|
|
251
|
+
for (let i = start; i < end; i++) {
|
|
252
|
+
const ln = sourceLines[i] ?? '';
|
|
253
|
+
if (unsafeRe.test(ln))
|
|
254
|
+
return true;
|
|
255
|
+
// Stop once the call closes — the `)` at the top-level. Fine
|
|
256
|
+
// approximation: any bare `)` at the START of a stripped line, or
|
|
257
|
+
// trailing `)` at end of a stripped line, signals close.
|
|
258
|
+
const stripped = ln.trim();
|
|
259
|
+
if (i > start && (stripped === ')' || stripped.endsWith(')'))) {
|
|
260
|
+
// Also test the current line before we stop, in case Loader= is
|
|
261
|
+
// on the same line as the closing paren.
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Return true when the given source text contains an in-file call that
|
|
269
|
+
* re-enables Fastjson autotype. Even a `_noneautotype` build does not
|
|
270
|
+
* protect against code that programmatically re-enables the feature
|
|
271
|
+
* (which the hardened build documents as impossible, but the classifier
|
|
272
|
+
* is a build-time strip, not a runtime lock). Defense-in-depth for the
|
|
273
|
+
* `resolveFastjsonFromPom` gate.
|
|
274
|
+
*/
|
|
275
|
+
export function fileReenablesFastjsonAutotype(source) {
|
|
276
|
+
if (!source)
|
|
277
|
+
return false;
|
|
278
|
+
// ParserConfig.getGlobalInstance().setAutoTypeSupport(true)
|
|
279
|
+
// ParserConfig.setAutoTypeSupport(true) (static-import form)
|
|
280
|
+
return /\bsetAutoTypeSupport\s*\(\s*true\b/.test(source);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Return true when the given Java source contains a call that enables
|
|
284
|
+
* Jackson polymorphic type handling — either the legacy
|
|
285
|
+
* `enableDefaultTyping(...)` (deprecated in Jackson 2.10+ but still
|
|
286
|
+
* shipped) or the current `activateDefaultTyping(...)`.
|
|
287
|
+
*
|
|
288
|
+
* When neither is present in the file (and no `@JsonTypeInfo` is used
|
|
289
|
+
* on the target type — best-effort scan below), Jackson's default
|
|
290
|
+
* behaviour since 2.10 is safe: `ObjectMapper.readValue(json,
|
|
291
|
+
* targetType)` cannot instantiate arbitrary classes. The gate uses
|
|
292
|
+
* this to distinguish a genuine `readValue` sink from a safely-
|
|
293
|
+
* configured one.
|
|
294
|
+
*
|
|
295
|
+
* `@JsonTypeInfo` scan is intentionally file-local. A `@JsonTypeInfo`
|
|
296
|
+
* annotation on a target type in a different file would still allow
|
|
297
|
+
* polymorphic construction, but the engine treats that as unknown
|
|
298
|
+
* risk and preserves the sink (the gate only fires when the *file
|
|
299
|
+
* itself* provides positive evidence of a safe configuration).
|
|
300
|
+
*/
|
|
301
|
+
export function fileEnablesJacksonPolymorphism(source) {
|
|
302
|
+
if (!source)
|
|
303
|
+
return false;
|
|
304
|
+
if (/\benableDefaultTyping\s*\(/.test(source))
|
|
305
|
+
return true;
|
|
306
|
+
if (/\bactivateDefaultTyping\s*\(/.test(source))
|
|
307
|
+
return true;
|
|
308
|
+
// @JsonTypeInfo(use = Id.CLASS) / (use = Id.MINIMAL_CLASS) / (use = Id.NAME)
|
|
309
|
+
// enables polymorphic type handling on the annotated field or type.
|
|
310
|
+
// The exact `use` argument doesn't matter for the gate — any
|
|
311
|
+
// @JsonTypeInfo signals polymorphism is in play somewhere.
|
|
312
|
+
if (/@JsonTypeInfo\b/.test(source))
|
|
313
|
+
return true;
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Return true when the given Java source contains a
|
|
318
|
+
* `new Yaml(new SafeConstructor(...))` or an equivalent hardened
|
|
319
|
+
* SnakeYAML constructor. When any `Yaml` instance in the file is
|
|
320
|
+
* built with the safe constructor family, we assume the file's
|
|
321
|
+
* `Yaml.load(...)` calls are safely configured; the gate then drops
|
|
322
|
+
* the deserialization sink for those calls.
|
|
323
|
+
*
|
|
324
|
+
* Recognised safe constructor classes (SnakeYAML 1.x + 2.x):
|
|
325
|
+
* - SafeConstructor — canonical safe loader
|
|
326
|
+
* - SafeSchema (rare, YAML 1.2) — safe schema-based loader
|
|
327
|
+
*
|
|
328
|
+
* Recognised safe factory calls (SnakeYAML 2.x LoaderOptions API):
|
|
329
|
+
* - Yaml.load() with a Constructor that extends SafeConstructor
|
|
330
|
+
* (heuristic: any `SafeConstructor`-typed variable is safe)
|
|
331
|
+
*
|
|
332
|
+
* NOTE: A file that mixes `new Yaml(new SafeConstructor())` with
|
|
333
|
+
* `new Yaml(new Constructor(SomeClass.class))` on separate call sites
|
|
334
|
+
* would over-suppress the unsafe site. In practice this is rare (SAST
|
|
335
|
+
* teams write one wrapper per file), and the current sink-filter
|
|
336
|
+
* stage 9b handles the analogous compiled-template case with the same
|
|
337
|
+
* file-scoped heuristic. If it becomes a problem, tighten to
|
|
338
|
+
* receiver-scoped: check the specific `Yaml` receiver that carries
|
|
339
|
+
* the load() call.
|
|
340
|
+
*/
|
|
341
|
+
export function fileConfiguresSnakeYamlSafely(source) {
|
|
342
|
+
if (!source)
|
|
343
|
+
return false;
|
|
344
|
+
if (/\bnew\s+SafeConstructor\s*\(/.test(source))
|
|
345
|
+
return true;
|
|
346
|
+
// Explicit typed declaration form: `SafeConstructor sc = new SafeConstructor()`
|
|
347
|
+
// or `SafeConstructor sc = ...` used in a Yaml constructor.
|
|
348
|
+
if (/\bSafeConstructor\s+\w+\s*=/.test(source))
|
|
349
|
+
return true;
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
//# sourceMappingURL=dependency-versions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependency-versions.js","sourceRoot":"","sources":["../../src/analysis/dependency-versions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
import type { SastFinding } from '../types/index.js';
|
|
41
|
+
/**
|
|
42
|
+
* Coalesce note-level findings at the same `(file, line)` location.
|
|
43
|
+
* Returns a new array; the input is not mutated.
|
|
44
|
+
*/
|
|
45
|
+
export declare function coalesceNoteLevelFindings(findings: readonly SastFinding[]): SastFinding[];
|
|
46
|
+
//# sourceMappingURL=note-coalescer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"note-coalescer.d.ts","sourceRoot":"","sources":["../../src/analysis/note-coalescer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAC/B,WAAW,EAAE,CAkEf"}
|