@clear-capabilities/agentic-security-scanner 0.118.2 → 0.119.1
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/CHANGELOG.md +256 -0
- package/bin/agentic-security.js +78 -1
- package/dist/178.index.js +1 -1
- package/dist/384.index.js +1 -1
- package/dist/415.index.js +90 -0
- package/dist/444.index.js +89 -0
- package/dist/503.index.js +288 -0
- package/dist/637.index.js +15 -1
- package/dist/838.index.js +1 -1
- package/dist/985.index.js +5 -1
- package/dist/agentic-security.mjs +18 -18
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +13 -8
- package/src/dataflow/CLAUDE.md +1 -1
- package/src/dataflow/catalog.js +50 -11
- package/src/dataflow/engine.js +76 -1
- package/src/dataflow/implicit-flow.js +4 -2
- package/src/dataflow/index.js +9 -4
- package/src/dataflow/proof-gate.js +5 -0
- package/src/dataflow/summaries.js +19 -1
- package/src/engine.js +95 -7
- package/src/ir/parser-go.js +8 -0
- package/src/mcp/tools.js +6 -2
- package/src/posture/fix-history.js +24 -0
- package/src/posture/provenance.js +39 -0
- package/src/posture/sca-verdict.js +130 -0
- package/src/posture/secret-history.js +72 -0
- package/src/pr-delta.js +13 -0
- package/src/report/index.js +50 -0
- package/src/sast/agent-untrusted-flow.js +107 -0
- package/src/sast/api-authz.js +71 -0
- package/src/sast/cross-service.js +98 -0
- package/src/sast/event-entrypoint.js +90 -0
- package/src/sast/iac-terraform.js +107 -0
- package/src/sast/rbac-consistency.js +113 -0
- package/src/sast/wrong-context-sanitizer.js +55 -0
- package/src/sca/image-packages.js +74 -0
- package/src/sca/import-reachability.js +271 -0
- package/src/sca/install-script-analysis.js +82 -0
- package/bin/.agentic-security/findings.json +0 -1907
- package/bin/.agentic-security/last-scan.json +0 -1907
- package/bin/.agentic-security/last-scan.json.sig +0 -1
- package/bin/.agentic-security/scan-history.json +0 -166
- package/bin/.agentic-security/streak.json +0 -20
- package/src/.agentic-security/dpia.md +0 -26
- package/src/.agentic-security/findings.json +0 -117732
- package/src/.agentic-security/last-scan.json +0 -117732
- package/src/.agentic-security/last-scan.json.sig +0 -1
- package/src/.agentic-security/pqc-migration-plan.json +0 -80
- package/src/.agentic-security/pqc-migration-plan.md +0 -32
- package/src/.agentic-security/sbom-history/0775d96ba4cf814520d02760cfb3010760c7684f.json +0 -6
- package/src/.agentic-security/sbom-history/29300441d3460832137a8e51a92b74068cad2f15.json +0 -6
- package/src/.agentic-security/sbom-history/3852ad2aa0e50951bed0c930c29d1861aa13d048.json +0 -6
- package/src/.agentic-security/sbom-history/3d96e2daec97e3226c9679bac6e64a89ca10bfe9.json +0 -6
- package/src/.agentic-security/sbom-history/5f0b136622fce09b469a1a6137c04d5b18652ea4.json +0 -6
- package/src/.agentic-security/sbom-history/64e30595d447b62344d0d9200760269df2095332.json +0 -6
- package/src/.agentic-security/sbom-history/71f3241902c9e7437764e3caa046ba8a3bcdd32a.json +0 -6
- package/src/.agentic-security/sbom-history/99c372cdc33ecc9dde92960b4ffadf7775860e61.json +0 -6
- package/src/.agentic-security/sbom-history/9c93b15e8cf3ff2ddb8cdae92371b82e178b463c.json +0 -6
- package/src/.agentic-security/sbom-history/a3544abf1863d71776b61302ca74d9ead2b26a9f.json +0 -6
- package/src/.agentic-security/sbom-history/dca15abdec43c508aa342db86dc5db8d7f01fbbe.json +0 -6
- package/src/.agentic-security/scan-history.json +0 -12946
- package/src/.agentic-security/streak.json +0 -21
- package/src/.agentic-security/threat-model.json +0 -3702
- package/src/.agentic-security/threat-model.md +0 -118
- package/src/dataflow/.agentic-security/dpia.md +0 -26
- package/src/dataflow/.agentic-security/findings.json +0 -6086
- package/src/dataflow/.agentic-security/last-scan.json +0 -6086
- package/src/dataflow/.agentic-security/last-scan.json.sig +0 -1
- package/src/dataflow/.agentic-security/sbom-history/15d7c4d20f9d968ebec29445c9e9b3fc6f74e6a1.json +0 -6
- package/src/dataflow/.agentic-security/sbom-history/3852ad2aa0e50951bed0c930c29d1861aa13d048.json +0 -6
- package/src/dataflow/.agentic-security/sbom-history/73166872888d9ddcbe98645af818ecc7d8917b07.json +0 -6
- package/src/dataflow/.agentic-security/sbom-history/8869fe64035bec0152d92ece961e9369d8637076.json +0 -6
- package/src/dataflow/.agentic-security/sbom-history/9c93b15e8cf3ff2ddb8cdae92371b82e178b463c.json +0 -6
- package/src/dataflow/.agentic-security/sbom-history/b7ac2dada4a9216ccfe8969fc57f988c83f46975.json +0 -6
- package/src/dataflow/.agentic-security/scan-history.json +0 -250
- package/src/dataflow/.agentic-security/streak.json +0 -21
- package/src/dataflow/.agentic-security/threat-model.json +0 -161
- package/src/dataflow/.agentic-security/threat-model.md +0 -43
- package/src/ir/.agentic-security/dpia.md +0 -26
- package/src/ir/.agentic-security/exploit-bundles.json +0 -77
- package/src/ir/.agentic-security/findings.json +0 -4011
- package/src/ir/.agentic-security/last-scan.json +0 -4011
- package/src/ir/.agentic-security/last-scan.json.sig +0 -1
- package/src/ir/.agentic-security/sbom-history/73166872888d9ddcbe98645af818ecc7d8917b07.json +0 -6
- package/src/ir/.agentic-security/sbom-history/b5f468a158f6a145aedd8680841c106b9809f16b.json +0 -6
- package/src/ir/.agentic-security/scan-history.json +0 -193
- package/src/ir/.agentic-security/streak.json +0 -20
- package/src/ir/.agentic-security/threat-model.json +0 -143
- package/src/ir/.agentic-security/threat-model.md +0 -39
- package/src/mcp/.agentic-security/findings.json +0 -8632
- package/src/mcp/.agentic-security/last-scan.json +0 -8632
- package/src/mcp/.agentic-security/last-scan.json.sig +0 -1
- package/src/mcp/.agentic-security/scan-history.json +0 -331
- package/src/mcp/.agentic-security/streak.json +0 -20
- package/src/posture/.agentic-security/dpia.md +0 -26
- package/src/posture/.agentic-security/findings.json +0 -77181
- package/src/posture/.agentic-security/last-scan.json +0 -77181
- package/src/posture/.agentic-security/last-scan.json.sig +0 -1
- package/src/posture/.agentic-security/pqc-migration-plan.json +0 -65
- package/src/posture/.agentic-security/pqc-migration-plan.md +0 -30
- package/src/posture/.agentic-security/sbom-history/0724c133d2943c87555695b5b2adcfcfc3373a2d.json +0 -6
- package/src/posture/.agentic-security/sbom-history/7d45b5e03804aac084b4a2b4dc8c6f10107d2005.json +0 -6
- package/src/posture/.agentic-security/sbom-history/99c372cdc33ecc9dde92960b4ffadf7775860e61.json +0 -6
- package/src/posture/.agentic-security/sbom-history/b7ac2dada4a9216ccfe8969fc57f988c83f46975.json +0 -6
- package/src/posture/.agentic-security/scan-history.json +0 -8904
- package/src/posture/.agentic-security/streak.json +0 -21
- package/src/posture/.agentic-security/threat-model.json +0 -2038
- package/src/posture/.agentic-security/threat-model.md +0 -73
- package/src/posture/compliance-frameworks/.agentic-security/findings.json +0 -80
- package/src/posture/compliance-frameworks/.agentic-security/last-scan.json +0 -80
- package/src/posture/compliance-frameworks/.agentic-security/last-scan.json.sig +0 -1
- package/src/posture/compliance-frameworks/.agentic-security/scan-history.json +0 -90
- package/src/posture/compliance-frameworks/.agentic-security/streak.json +0 -22
- package/src/report/.agentic-security/dpia.md +0 -26
- package/src/report/.agentic-security/sbom-history/7d45b5e03804aac084b4a2b4dc8c6f10107d2005.json +0 -6
- package/src/report/.agentic-security/sbom-history/f94be839aa6a1011f53da016416a561698b8f338.json +0 -6
- package/src/report/.agentic-security/threat-model.json +0 -7
- package/src/report/.agentic-security/threat-model.md +0 -22
- package/src/sast/.agentic-security/dpia.md +0 -26
- package/src/sast/.agentic-security/findings.json +0 -6154
- package/src/sast/.agentic-security/last-scan.json +0 -6154
- package/src/sast/.agentic-security/last-scan.json.sig +0 -1
- package/src/sast/.agentic-security/pqc-migration-plan.json +0 -65
- package/src/sast/.agentic-security/pqc-migration-plan.md +0 -30
- package/src/sast/.agentic-security/sbom-history/043a7b401c68f5a298c83d4c42e94f118ff2a89a.json +0 -6
- package/src/sast/.agentic-security/sbom-history/0775d96ba4cf814520d02760cfb3010760c7684f.json +0 -6
- package/src/sast/.agentic-security/sbom-history/20d77ad7363a82777d506dedbbb1ecfed52f094a.json +0 -6
- package/src/sast/.agentic-security/sbom-history/29300441d3460832137a8e51a92b74068cad2f15.json +0 -6
- package/src/sast/.agentic-security/sbom-history/3852ad2aa0e50951bed0c930c29d1861aa13d048.json +0 -6
- package/src/sast/.agentic-security/sbom-history/3c6b3205f04e26f7654943d465281d00e0fa395e.json +0 -6
- package/src/sast/.agentic-security/sbom-history/3d96e2daec97e3226c9679bac6e64a89ca10bfe9.json +0 -6
- package/src/sast/.agentic-security/sbom-history/5282502e090013219eb006f7e02d8c983bc29c8e.json +0 -6
- package/src/sast/.agentic-security/sbom-history/5f0b136622fce09b469a1a6137c04d5b18652ea4.json +0 -6
- package/src/sast/.agentic-security/sbom-history/64e30595d447b62344d0d9200760269df2095332.json +0 -6
- package/src/sast/.agentic-security/sbom-history/71f3241902c9e7437764e3caa046ba8a3bcdd32a.json +0 -6
- package/src/sast/.agentic-security/sbom-history/73514b485d4770f3c5ee57d87338bccd1c61d8b7.json +0 -6
- package/src/sast/.agentic-security/sbom-history/96fa584fc3134512237d58aac63d6f3778b0cbcb.json +0 -6
- package/src/sast/.agentic-security/sbom-history/99c372cdc33ecc9dde92960b4ffadf7775860e61.json +0 -6
- package/src/sast/.agentic-security/sbom-history/a3544abf1863d71776b61302ca74d9ead2b26a9f.json +0 -6
- package/src/sast/.agentic-security/sbom-history/a4a34ab8b3d758de2adc8c3dc3e42d4cb0db00a3.json +0 -6
- package/src/sast/.agentic-security/sbom-history/ac236ec3602cfc8fb133f389ec3687e409084673.json +0 -6
- package/src/sast/.agentic-security/sbom-history/b57b3e1292f1f9f0de6c5a34a436e330c78008a3.json +0 -6
- package/src/sast/.agentic-security/sbom-history/dca15abdec43c508aa342db86dc5db8d7f01fbbe.json +0 -6
- package/src/sast/.agentic-security/sbom-history/dcbfd4935922dd165093c08a1003c338eed937c5.json +0 -6
- package/src/sast/.agentic-security/sbom-history/fa017f725615b0293814344c8ef7dfeb06775ca0.json +0 -6
- package/src/sast/.agentic-security/scan-history.json +0 -941
- package/src/sast/.agentic-security/streak.json +0 -22
- package/src/sast/.agentic-security/threat-model.json +0 -816
- package/src/sast/.agentic-security/threat-model.md +0 -91
- package/src/sca/.agentic-security/findings.json +0 -7460
- package/src/sca/.agentic-security/last-scan.json +0 -7460
- package/src/sca/.agentic-security/last-scan.json.sig +0 -1
- package/src/sca/.agentic-security/scan-history.json +0 -113
- package/src/sca/.agentic-security/streak.json +0 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,261 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.119.1 — packaging: exclude internal scan-state from the npm tarball
|
|
4
|
+
|
|
5
|
+
Packaging-only hotfix for the `@clear-capabilities/agentic-security-scanner` npm
|
|
6
|
+
package; no functional change to the scanner or the plugin.
|
|
7
|
+
|
|
8
|
+
- The published tarball had ballooned to 23.5 MB / 565 files because the `files`
|
|
9
|
+
allowlist pulled in `src/**/.agentic-security/` and `bin/.agentic-security/` —
|
|
10
|
+
the dogfooded self-scan state (findings.json, last-scan.json, scan-history,
|
|
11
|
+
threat-model). `npm pack` runs from `scanner/` and does not consult the
|
|
12
|
+
repo-root `.gitignore`, and a root `.npmignore` is ignored when a `files` field
|
|
13
|
+
is present, so the carve-out is done with negation entries in `files`
|
|
14
|
+
(`!**/.agentic-security`, `!**/.agentic-security/**`). The tarball is now
|
|
15
|
+
7.9 MB / 393 files with no scan-state.
|
|
16
|
+
|
|
17
|
+
## 0.119.0 — SAST/SCA capability program (PRD R1–R25, ex-R14)
|
|
18
|
+
|
|
19
|
+
Implements the `docs/SAST_SCA_IMPROVEMENT_PRD.md` backlog — 24 of 25
|
|
20
|
+
recommendations (DAST-lite/R14 intentionally out). Every change shipped with a
|
|
21
|
+
fixture + test and the corpus gate held at 185/185.
|
|
22
|
+
|
|
23
|
+
**Core engine**
|
|
24
|
+
- **R1**: deep interprocedural taint (IR + field-/value-context-sensitive) is now
|
|
25
|
+
ON by default for local/interactive CLI scans, budgeted, CI-off-by-default
|
|
26
|
+
(`--no-deep` / `AGENTIC_SECURITY_DEEP=0` opt-out).
|
|
27
|
+
- **R23**: fixed the incremental cache cold-start no-op (it never persisted a
|
|
28
|
+
baseline), with an end-to-end regression test.
|
|
29
|
+
- **R4**: collection-element taint (`arr.push(tainted); sink(arr[i])`) on by
|
|
30
|
+
default; implicit/control-dependence flow wired in opt-in
|
|
31
|
+
(`AGENTIC_SECURITY_IMPLICIT_FLOW=1`).
|
|
32
|
+
- **R2**: bounded k=1 call-string context sensitivity, opt-in
|
|
33
|
+
(`AGENTIC_SECURITY_KCFA_CALLSTRING=1`); default-off keys are byte-identical.
|
|
34
|
+
- **R3**: deep-flow parity extended to **Go + Kotlin** — call-shaped sources
|
|
35
|
+
(`r.FormValue()`), string callees (`db.Query`), Go string-concat lowering,
|
|
36
|
+
Go `database/sql` sinks. (PHP/Ruby/C# parser concat-lowering remains.)
|
|
37
|
+
- **R17**: per-finding corroboration ("one issue, many signals") + rank tiebreak.
|
|
38
|
+
- **R13**: "provably safe" is a first-class verdict (`--hide-proven-safe`).
|
|
39
|
+
- **R5**: context-aware sanitizer adequacy (HTML-escape before a shell/SQL sink).
|
|
40
|
+
- **R6**: non-HTTP entrypoint taint (queue consumers / scheduled tasks / serverless).
|
|
41
|
+
|
|
42
|
+
**SCA / supply chain**
|
|
43
|
+
- **R7**: import-aware function-level reachability (JS/TS + Python) — resolves
|
|
44
|
+
aliased/namespace imports, gated to files that import the package.
|
|
45
|
+
- **R12**: deterministic decision-first verdict per dependency
|
|
46
|
+
(AUTO_MERGE_PATCH / WAIT_FOR_PATCH / MANUAL_REVIEW / ACCEPT_RISK / WONT_FIX).
|
|
47
|
+
- **R11**: OpenVEX export (`--format vex`) from reachability verdicts.
|
|
48
|
+
- **R9**: static malicious install-script analysis (download-exec / base64-exec /
|
|
49
|
+
env-exfil lifecycle hooks).
|
|
50
|
+
- **R10**: Gradle transitive dependency graph.
|
|
51
|
+
- **R8**: container-image OS-package inventory (dpkg/apk).
|
|
52
|
+
|
|
53
|
+
**Detection surfaces, validation, workflow**
|
|
54
|
+
- **R20**: agent-loop taint (untrusted RAG/tool content → high-privilege tool).
|
|
55
|
+
- **R19**: route-level BOLA/BFLA over the API inventory.
|
|
56
|
+
- **R21**: RBAC role-tier authorization consistency.
|
|
57
|
+
- **R22**: cross-service dataflow (client-call → matched route edge inference).
|
|
58
|
+
- **R18**: semantic IaC (Terraform with variable resolution).
|
|
59
|
+
- **R16**: independent-eval harness (per-family precision/recall/F1 + gate).
|
|
60
|
+
- **R24**: PR-native net-new CI gate (`--fail-on-new`).
|
|
61
|
+
- **R15**: git-history secret sweep (`--secret-history`).
|
|
62
|
+
- **R25**: auto-fix acceptance-rate metric (surfaced in `apply_fix`).
|
|
63
|
+
|
|
64
|
+
Several latent bugs were found and fixed along the way (the dead `implicit-flow`
|
|
65
|
+
module, an `Object.assign`-taint path silently dying on a `const` binding, the
|
|
66
|
+
IR-TAINT conversion dropping the `implicit` flag). Each opt-in/partial item and
|
|
67
|
+
its remainder is tracked in the PRD rollup.
|
|
68
|
+
|
|
69
|
+
## 0.118.2 — Scanner F1 benchmark: restore F1 to 91.3% (3 FPs removed)
|
|
70
|
+
|
|
71
|
+
The "Scanner F1 benchmark" CI job (`test/benchmark/bench.js`, separate from the
|
|
72
|
+
cve-replay corpus) was failing. Root-caused three false positives and fixed all
|
|
73
|
+
three at the detector level (no baseline edit):
|
|
74
|
+
|
|
75
|
+
- **ReDoS double-flag** (introduced in v0.118.0): `scanRegexReDoS` (the
|
|
76
|
+
multi-language NFA detector) ran on all languages including JS, double-flagging
|
|
77
|
+
regexes already covered by `scanReDoS`. Restricted to the languages
|
|
78
|
+
`scanReDoS` misses string-form regexes in (java/cs/kt/py/php); js/rb stay with
|
|
79
|
+
`scanReDoS`.
|
|
80
|
+
- **rule-library-shape SQLi-in-string** (pre-existing): `js-framework-structural`
|
|
81
|
+
matched a `db.query(\`…\`)` that was the *content* of a rule-definition string.
|
|
82
|
+
Added a same-line string-literal guard.
|
|
83
|
+
- **secret double-count** (pre-existing): a split API key was reported twice —
|
|
84
|
+
once by `secret-concat` (CWE-798) and once as a High-Entropy Credential
|
|
85
|
+
Candidate. Drop the entropy candidate when a named secret detector already
|
|
86
|
+
flagged the same `file:line`.
|
|
87
|
+
- **Measured: bench F1 90.3% → 91.3%, FP 20 → 17;** cve-replay corpus unchanged
|
|
88
|
+
at 185/185 F1=1.000; full gate green.
|
|
89
|
+
|
|
90
|
+
## 0.118.1 — README: Language coverage section
|
|
91
|
+
|
|
92
|
+
Documents the 8 first-class languages (JS/TS, Python, Java, Kotlin, Go, Ruby,
|
|
93
|
+
PHP, C#) and the cross-language vuln-class coverage built out over v0.108–0.118
|
|
94
|
+
(SQLi, command/code/LDAP/XPath injection, XSS, SSRF, XXE, deserialization,
|
|
95
|
+
secrets, weak hashing/ciphers, static IV, CSRF, open redirect, response
|
|
96
|
+
splitting, ReDoS), measured by the blind, regression-gated 185-entry CVE-replay
|
|
97
|
+
corpus at F1=1.000. Docs + version only; no src/bundle/corpus change.
|
|
98
|
+
|
|
99
|
+
## 0.118.0 — Multi-CWE gap fill: 167→185 corpus, matrix to 152/160 cells
|
|
100
|
+
|
|
101
|
+
Closes the remaining detector-backed gaps across five CWEs in one batch, via
|
|
102
|
+
detector extensions (each verified pre:TP post:TN; no corpus regression).
|
|
103
|
+
|
|
104
|
+
- **`redos-nfa.js`** (CWE-1333): the multi-language NFA detector (was dormant)
|
|
105
|
+
now wired in for Java/C#/Kotlin, plus PHP/Ruby string-form regex extractors.
|
|
106
|
+
Go intentionally not scanned — its `regexp` package is RE2 (linear-time).
|
|
107
|
+
- **`weak-password-hash.js`** (CWE-916): added Ruby, C#, Kotlin, PHP MD5/SHA1
|
|
108
|
+
forms. Password-context-gated; bcrypt/argon2 nearby suppresses.
|
|
109
|
+
- **`open-redirect.js`** (CWE-601): added Go, Ruby, C#, Kotlin sinks, with
|
|
110
|
+
allow-list recognition and literal-target exemption.
|
|
111
|
+
- **`ruby.js`** (CWE-502): `Marshal.load`/`YAML.load` on a non-literal arg.
|
|
112
|
+
**`go-structural.js`** (CWE-502): `encoding/gob` decode of untrusted bytes.
|
|
113
|
+
**`php.js`** (CWE-918): cURL / fopen-wrapper fetch of a `$_GET`-controlled URL.
|
|
114
|
+
- **18 new corpus entries.** Not filled (intentional): CWE-1321 prototype
|
|
115
|
+
pollution in 7 non-JS languages, and CWE-1333 in Go (RE2) — 8 cells empty by
|
|
116
|
+
design. **Corpus 167 → 185; matrix 134 → 152/160 (95%).** F1=1.000; gate green.
|
|
117
|
+
|
|
118
|
+
## 0.117.0 — Cross-language XPath injection (CWE-643) + 7 corpus entries
|
|
119
|
+
|
|
120
|
+
Extends `xpath-injection.js` from Java/Python/JS to PHP, Go, Ruby, C#, Kotlin
|
|
121
|
+
(`DOMXPath`, `SelectNodes`/interp, Nokogiri `.xpath`, xmlpath/htmlquery,
|
|
122
|
+
`javax.xml.xpath`). All string-literal matching is embedded-quote-tolerant — an
|
|
123
|
+
expression like `"//user[@name='" + x` embeds the attribute delimiter quote
|
|
124
|
+
(also fixed a latent miss in the existing Java/Python patterns). Two issues
|
|
125
|
+
caught pre-commit (a Kotlin post:FP, a test-author `${u}` JS-interpolated before
|
|
126
|
+
the scanner). **Corpus 160 → 167; matrix 127 → 134/160.** F1=1.000; gate green.
|
|
127
|
+
|
|
128
|
+
## 0.116.0 — Cross-language CSRF detection (CWE-352) + 3 corpus entries
|
|
129
|
+
|
|
130
|
+
Extends `csrf.js` to Go (gin/echo/fiber/chi, net/http mux), Rails routes, and
|
|
131
|
+
ASP.NET MVC `[HttpPost]` action attrs. Defence recognition added (gorilla/csrf,
|
|
132
|
+
nosurf, `protect_from_forgery`, `[ValidateAntiForgeryToken]`/IAntiforgery).
|
|
133
|
+
Token-auth exemption: `[ApiController]` + explicit Bearer is CSRF-safe; bare
|
|
134
|
+
`[Authorize]` is not (cookie auth is the ASP.NET default). **Corpus 157 → 160;
|
|
135
|
+
matrix 124 → 127/160.** F1=1.000; gate green.
|
|
136
|
+
|
|
137
|
+
## 0.115.0 — Java reflected XSS (CWE-79): fills last XSS cell
|
|
138
|
+
|
|
139
|
+
Adds Java to `xss-reflected-multilang.js` — servlet `response.getWriter()`
|
|
140
|
+
write/print + `PrintWriter`, building an HTML string by concat. Static literals
|
|
141
|
+
and OWASP/Commons/ESAPI encoders excluded. The reflected-XSS row (CWE-79) is now
|
|
142
|
+
complete across all 8 languages. **Corpus 156 → 157; matrix 123 → 124/160.**
|
|
143
|
+
F1=1.000; gate green.
|
|
144
|
+
|
|
145
|
+
## 0.114.0 — Cross-language weak-cipher detection (CWE-327) + 3 corpus entries
|
|
146
|
+
|
|
147
|
+
Extends `crypto-protocol.js` weak-cipher detection to the Go (`crypto/des`,
|
|
148
|
+
`crypto/rc4`), PHP (`openssl_encrypt`/legacy `mcrypt_*` weak algos), and Ruby
|
|
149
|
+
(`OpenSSL::Cipher` DES/RC4/Blowfish) idioms. AES-GCM stays clean. **Corpus
|
|
150
|
+
153 → 156; matrix 120 → 123/160.** F1=1.000; gate green.
|
|
151
|
+
|
|
152
|
+
## 0.113.0 — Cross-language static-IV detection (CWE-329) + 7 corpus entries
|
|
153
|
+
|
|
154
|
+
Extends `crypto-protocol.js` static-IV detection to JVM (`IvParameterSpec`/
|
|
155
|
+
`GCMParameterSpec` from a zero array), C# (zero `.IV`), PHP (empty/`str_repeat`
|
|
156
|
+
IV), Ruby (fixed-literal IV), and Go (zero `make([]byte,…)` with a CBC/CTR mode,
|
|
157
|
+
suppressed when filled from `crypto/rand`). CSPRNG-derived IVs stay clean across
|
|
158
|
+
all languages. **Corpus 146 → 153; matrix 113 → 120/160.** F1=1.000; gate green.
|
|
159
|
+
|
|
160
|
+
## 0.112.0 — Cross-language HTTP response splitting (CWE-113) + 7 corpus entries
|
|
161
|
+
|
|
162
|
+
Extends `response-splitting.js` across all 8 languages (added Go, Ruby, C#,
|
|
163
|
+
Kotlin header sinks) and fixes two latent bugs: the PHP value was read from the
|
|
164
|
+
header-NAME capture group (never fired), and the request-scope param heuristic
|
|
165
|
+
is generalized from Java to Java/Kotlin/C#. Sanitizer recognition extended
|
|
166
|
+
(Python chained `.replace`, Ruby `gsub`/`delete`, Go `NewReplacer`/`ReplaceAll`,
|
|
167
|
+
PHP `str_replace`) so fixed forms stay clean. Biggest single matrix gain this
|
|
168
|
+
session. **Corpus 139 → 146; matrix 106 → 113/160.** F1=1.000; gate green.
|
|
169
|
+
|
|
170
|
+
## 0.111.0 — Cross-language XXE detector (CWE-611) + 3 corpus entries
|
|
171
|
+
|
|
172
|
+
Extends `xxe.js` (Java/Python) to PHP, Go, Ruby. Each non-JVM XML stack is
|
|
173
|
+
XXE-safe by default, so the detector flags the explicit external-entity opt-in
|
|
174
|
+
(PHP `LIBXML_NOENT`/`DTDLOAD`, Go `Strict=false`/custom Entity map, Nokogiri
|
|
175
|
+
`noent`/`dtdload`/`replace_entities`) rather than the default-safe parse.
|
|
176
|
+
**Corpus 136 → 139; matrix 103 → 106/160.** F1=1.000; gate green.
|
|
177
|
+
|
|
178
|
+
## 0.110.0 — Cross-language code-injection detector (CWE-94) + 4 corpus entries
|
|
179
|
+
|
|
180
|
+
New `code-injection-multilang.js` closes the CWE-94 gap in Java/C#/Go/Kotlin
|
|
181
|
+
(JS/Python/Ruby `eval` stay with the flow engine). Flags dynamic
|
|
182
|
+
code/expression evaluators on a non-literal argument — Java/Kotlin
|
|
183
|
+
(`ScriptEngine.eval`, GroovyShell, SpEL, MVEL, OGNL), C# (Roslyn `CSharpScript`,
|
|
184
|
+
`DataTable.Compute`), Go (yaegi/gomacro `interp.Eval`, `text/template` parse of a
|
|
185
|
+
non-literal body). A literal argument does not match. **Corpus 132 → 136; matrix
|
|
186
|
+
99 → 103/160.** F1=1.000; gate green.
|
|
187
|
+
|
|
188
|
+
## 0.109.0 — Cross-language LDAP injection detector (CWE-90) + 6 corpus entries
|
|
189
|
+
|
|
190
|
+
Extends `ldap-injection.js` from JS/Java/Python to PHP, Go, C#, Ruby, Kotlin.
|
|
191
|
+
Per-language filter patterns gated by an LDAP attribute set (uid/cn/mail/…) so
|
|
192
|
+
it doesn't fire on generic `key=value`. Two precision guards (both fix real FPs
|
|
193
|
+
found while building): a backtrack-proof call-guard so an escaped call at the
|
|
194
|
+
concat isn't flagged, and a file-level escape-API guard (`ldap_escape`,
|
|
195
|
+
`EscapeFilter`, `escape_filter_chars`, …) for escape-then-use spanning lines.
|
|
196
|
+
**Corpus 126 → 132; matrix 93 → 99/160.** F1=1.000; gate green.
|
|
197
|
+
|
|
198
|
+
## 0.108.1 — Corpus scale-up batch 3 — 117→126, matrix 84→93 cells
|
|
199
|
+
|
|
200
|
+
9 verified harvest entries across previously-empty cells the scanner already
|
|
201
|
+
detects (no detector change): CWE-798 {java,go,ruby,php}, CWE-916 {java,go,php},
|
|
202
|
+
CWE-918 {ruby}, CWE-601 {java}. Two discipline notes encoded in the generator so
|
|
203
|
+
they don't recur: placeholder `AKIA…EXAMPLE` keys read as pre:FN (the secrets
|
|
204
|
+
scanner correctly skips placeholders — regenerated with realistic keys), and a
|
|
205
|
+
guarded open-redirect "fixed" variant read as post:FP (the detector doesn't yet
|
|
206
|
+
recognize allow-list guards — post now uses literal targets). **126/126
|
|
207
|
+
F1=1.000; gate exit 0.**
|
|
208
|
+
|
|
209
|
+
## 0.108.0 — Corpus scale-up to 117 + baseline CI gate + cross-language XSS
|
|
210
|
+
|
|
211
|
+
Squashes the v0.106.0–v0.107.3 development churn (7 commits, several corrective)
|
|
212
|
+
into one coherent release.
|
|
213
|
+
|
|
214
|
+
- **Corpus 101 → 117 entries**, all pre:TP post:TN, F1=1.000, verified blind
|
|
215
|
+
(manifest outside the scanned trees; identical under
|
|
216
|
+
`AGENTIC_SECURITY_BLIND_BENCH=1`). Batch 2 harvest (11) + cross-language
|
|
217
|
+
reflected XSS (5).
|
|
218
|
+
- **`xss-reflected-multilang.js`** (new): taint-independent reflected XSS for
|
|
219
|
+
Go/Ruby/PHP/C#/Kotlin (input into an HTML response via concat/interp), each
|
|
220
|
+
with a per-language output-encoder exclusion. JS/Python XSS stays with the
|
|
221
|
+
flow engine.
|
|
222
|
+
- **Corpus baseline CI gate** (the process fix): `corpus-baseline.json` records
|
|
223
|
+
the verdict for every entry; `runner.mjs --check-baseline`/`--update-baseline`
|
|
224
|
+
+ npm scripts. The gate fails on any drift — a regressed entry, a removed
|
|
225
|
+
entry, or a new entry that does not pass. First `.github/workflows/ci.yml`
|
|
226
|
+
(repo had no CI): runs `npm test`, verifies the committed bundle matches
|
|
227
|
+
source, runs the corpus gate.
|
|
228
|
+
- **CLAUDE.md gains the "Verification discipline" section.** 117/117, gate exit 0.
|
|
229
|
+
|
|
230
|
+
## 0.105.0 — Corpus scale-up batch 1 (Tier 4) — 88→101, matrix 64→77 cells
|
|
231
|
+
|
|
232
|
+
First batch of the cve-replay scale-up toward 500, prioritized by empty
|
|
233
|
+
CWE×language cells. 13 new capability entries across 9 previously-empty cells
|
|
234
|
+
(CWE-338 weak PRNG, CWE-94, CWE-352, CWE-22, CWE-502), each verified pre:TP
|
|
235
|
+
post:TN. Two additive detector extensions unlock them: `weak-randomness.js`
|
|
236
|
+
gains snake_case carriers + Java/Kotlin/C# PRNG patterns (CWE-338; SecureRandom
|
|
237
|
+
stays clean), and `php.js`/`ruby.js` gain structural path traversal (CWE-22).
|
|
238
|
+
**Corpus stays F1=1.000; matrix 64 → 77/160.**
|
|
239
|
+
|
|
240
|
+
## 0.104.0 — Close remaining corpus FN/FP — cve-replay F1 0.929 → 1.000
|
|
241
|
+
|
|
242
|
+
Implements the full remaining recall+precision backlog. Aggregate cve-replay now
|
|
243
|
+
TP=88 FP=0 FN=0 TN=88 (F1=1.000); every language and CWE slice at F1=1.000.
|
|
244
|
+
|
|
245
|
+
- **`secret-concat.js`** (new): language-agnostic hardcoded-secret split across
|
|
246
|
+
concatenated literals — reassembles `'AKIA'+'IOSF…'`/`'ghp'+'_…'`/`'sk'+'_live_…'`
|
|
247
|
+
and matches provider prefixes (CWE-798).
|
|
248
|
+
- **`crypto-protocol.js`**: pyca/cryptography zero/static IV (CWE-329).
|
|
249
|
+
**`weak-randomness.js`**: camelCase security carrier; reclassified CWE-330 →
|
|
250
|
+
CWE-338. **`php.js`**: structural SQLi covers deprecated `mysql_query`.
|
|
251
|
+
**`js-framework-structural.js`**: libxmljs XXE (CWE-611).
|
|
252
|
+
**`python-structural.js`**: embedded-quote-tolerant literal matching +
|
|
253
|
+
`open()`/`send_file` path traversal (CWE-22). **`csrf.js`**: Symfony support.
|
|
254
|
+
- **Precision (`engine.js` `dropGuardedFindings`)**: reflected-XSS
|
|
255
|
+
output-encoding guard (drop CWE-79 when the value passed through a *captured*
|
|
256
|
+
HTML escaper); `dedupeFindingsWithEvidence` prefers an interprocedural flow
|
|
257
|
+
finding over a flat structural match at the same sink.
|
|
258
|
+
|
|
3
259
|
## 0.103.0 — Go recall: structural detectors (PRD Tier 1)
|
|
4
260
|
|
|
5
261
|
- **`go-structural.js`** (new): SQL injection via a `database/sql` query method
|
package/bin/agentic-security.js
CHANGED
|
@@ -9,7 +9,7 @@ const __require = createRequire(import.meta.url);
|
|
|
9
9
|
const PKG_VERSION = __require('../package.json').version;
|
|
10
10
|
import { signLastScan as _signLastScan, verifyLastScan as _verifyLastScanShared } from '../src/posture/integrity.js';
|
|
11
11
|
import { runScan } from '../src/runScan.js';
|
|
12
|
-
import { toJSON, toMarkdown, toSARIF, toSTIX, toCSV, toJUnit, toCLI, toCLIByProfile, toShipVerdict, toProTable, toHTML, toSummary, exitCodeFor, normalizeFindings } from '../src/report/index.js';
|
|
12
|
+
import { toJSON, toMarkdown, toSARIF, toSTIX, toCSV, toJUnit, toCLI, toCLIByProfile, toShipVerdict, toProTable, toHTML, toSummary, toVex, exitCodeFor, normalizeFindings } from '../src/report/index.js';
|
|
13
13
|
import { toCycloneDX, toSPDX } from '../src/posture/sbom.js';
|
|
14
14
|
import { toPBOM } from '../src/sast/pipeline.js';
|
|
15
15
|
import { buildAIBOM, aibomToMarkdown } from '../src/posture/aibom.js';
|
|
@@ -82,6 +82,8 @@ Options:
|
|
|
82
82
|
--pack <name> Focus on a curated rule pack (repeatable): owasp-top-10 | cwe-top-25 | llm-security | supply-chain
|
|
83
83
|
--baseline <ref> Diff against a git ref; only findings new vs. that ref count (ci subcommand)
|
|
84
84
|
--fail-on critical|high|medium|low|none ci-mode exit policy (default: critical)
|
|
85
|
+
--fail-on-new (ci) block ONLY on findings this PR introduced vs
|
|
86
|
+
the baseline ref — never the pre-existing backlog
|
|
85
87
|
--policy <file.rego> ci-mode policy-as-code gate; deny[] rules fail the build (FR-SDLC-9)
|
|
86
88
|
--columns standard|mitre|capec|owasp Pro-mode column set (default: standard)
|
|
87
89
|
--confidence <0..1> Override per-profile confidence threshold
|
|
@@ -109,6 +111,10 @@ Options:
|
|
|
109
111
|
--incremental Reuse taint summaries from prior scans (speeds up deep mode in CI)
|
|
110
112
|
--set-baseline Save current findings as baseline (suppresses pre-existing issues)
|
|
111
113
|
--since-baseline Only show findings NOT in the saved baseline
|
|
114
|
+
--hide-proven-safe Drop findings discharged by a flow proof (provably safe)
|
|
115
|
+
--secret-history Also sweep recent git history for committed secrets
|
|
116
|
+
(removed from HEAD but recoverable from .git)
|
|
117
|
+
--history-depth <n> Commits to sweep with --secret-history (default 50)
|
|
112
118
|
--no-epss Skip EPSS exploit-prediction enrichment (default: enabled)
|
|
113
119
|
--no-blast-radius Skip blast-radius / cost framing (default: enabled)
|
|
114
120
|
--verbose Include fix bodies + taxonomy in CLI output
|
|
@@ -330,6 +336,27 @@ async function cmdScan(args) {
|
|
|
330
336
|
process.env.AGENTIC_SECURITY_INCREMENTAL = '1';
|
|
331
337
|
}
|
|
332
338
|
|
|
339
|
+
// R1 (PRD §5): deep interprocedural taint (IR + field-sensitive, value-context-
|
|
340
|
+
// sensitive dataflow — src/dataflow/) is ON by default for local/interactive CLI
|
|
341
|
+
// scans, bounded by the existing wall-clock budget (AGENTIC_SECURITY_DEEP_TIMEOUT_MS,
|
|
342
|
+
// default 5 min) and function cap (AGENTIC_SECURITY_DEEP_FN_LIMIT, default 5000). It
|
|
343
|
+
// is the engine's strongest analysis and was previously gated off, so the default
|
|
344
|
+
// scan ran pattern/structural-only. Precedence (highest first):
|
|
345
|
+
// --no-deep / AGENTIC_SECURITY_DEEP=0 → stays off (degrade to structural-only)
|
|
346
|
+
// --deep / AGENTIC_SECURITY_DEEP=1 → stays on (even in CI? no — see engine gate)
|
|
347
|
+
// unset + not in CI → default ON here
|
|
348
|
+
// unset + in CI → left unset; the engine keeps deep off in CI
|
|
349
|
+
// unless AGENTIC_SECURITY_DEEP_IN_CI=1
|
|
350
|
+
// The in-process test/bench harnesses call runScan() directly (not this CLI entry),
|
|
351
|
+
// so they are unaffected and remain deterministic regression gates.
|
|
352
|
+
if (args.flags['no-deep']) process.env.AGENTIC_SECURITY_DEEP = '0';
|
|
353
|
+
else if (args.flags['deep']) process.env.AGENTIC_SECURITY_DEEP = '1';
|
|
354
|
+
else if (process.env.AGENTIC_SECURITY_DEEP == null || process.env.AGENTIC_SECURITY_DEEP === '') {
|
|
355
|
+
const inCi = !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI ||
|
|
356
|
+
process.env.BUILDKITE || process.env.CIRCLECI || process.env.JENKINS_URL);
|
|
357
|
+
if (!inCi) process.env.AGENTIC_SECURITY_DEEP = '1';
|
|
358
|
+
}
|
|
359
|
+
|
|
333
360
|
// --pr [ref] : friendlier alias for --changed-since that auto-detects the PR
|
|
334
361
|
// base ref (GitHub/GitLab/Buildkite/Bitbucket env vars) when no value is given.
|
|
335
362
|
let changedSince = args.flags['changed-since'] || null;
|
|
@@ -374,6 +401,32 @@ async function cmdScan(args) {
|
|
|
374
401
|
} catch { /* baseline file unreadable, skip */ }
|
|
375
402
|
}
|
|
376
403
|
|
|
404
|
+
// R13 (PRD §5): --hide-proven-safe drops findings a flow proof discharged
|
|
405
|
+
// (parameterizer on every path / sanitizer excludes metacharacters) for a
|
|
406
|
+
// clean report. They are kept (demoted) by default so nothing is hidden silently.
|
|
407
|
+
if (args.flags['hide-proven-safe']) {
|
|
408
|
+
const before = (scan.findings || []).length;
|
|
409
|
+
scan.findings = (scan.findings || []).filter(f => !f.provablySafe);
|
|
410
|
+
process.stderr.write(`[proof] hid ${before - (scan.findings || []).length} provably-safe finding(s)\n`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// R15 (PRD §5): --secret-history sweeps recent git history for secrets that
|
|
414
|
+
// may have been removed from HEAD but remain recoverable from .git.
|
|
415
|
+
if (args.flags['secret-history']) {
|
|
416
|
+
try {
|
|
417
|
+
const { sweepGitHistory } = await import('../src/posture/secret-history.js');
|
|
418
|
+
const { scanCredentials } = await import('../src/secrets/index.js');
|
|
419
|
+
const depth = parseInt(args.flags['history-depth'] || '50', 10);
|
|
420
|
+
const hist = sweepGitHistory(targetAbs, scanCredentials, { maxCommits: depth });
|
|
421
|
+
if (hist.length) {
|
|
422
|
+
scan.secrets = [...(scan.secrets || []), ...hist];
|
|
423
|
+
process.stderr.write(`[secret-history] ${hist.length} secret(s) found in the last ${depth} commits — rotate even if removed from HEAD\n`);
|
|
424
|
+
} else {
|
|
425
|
+
process.stderr.write(`[secret-history] no secrets found in the last ${depth} commits\n`);
|
|
426
|
+
}
|
|
427
|
+
} catch (e) { if (process.env.AGENTIC_SECURITY_DEBUG === '1') process.stderr.write(`[secret-history] ${e && e.message}\n`); }
|
|
428
|
+
}
|
|
429
|
+
|
|
377
430
|
// 0.9.0 Feat-18: --scorecard flag enables OSSF Scorecard enrichment
|
|
378
431
|
if (args.flags['scorecard']) process.env.AGENTIC_SECURITY_SCORECARD = '1';
|
|
379
432
|
|
|
@@ -481,6 +534,7 @@ async function cmdScan(args) {
|
|
|
481
534
|
else if (format === 'html') body = toHTML(scan, meta);
|
|
482
535
|
else if (format === 'cyclonedx' || format === 'sbom') body = JSON.stringify(toCycloneDX(scan, meta), null, 2);
|
|
483
536
|
else if (format === 'spdx') body = JSON.stringify(toSPDX(scan, meta), null, 2);
|
|
537
|
+
else if (format === 'vex' || format === 'openvex') body = JSON.stringify(toVex(scan, meta), null, 2);
|
|
484
538
|
else if (format === 'pbom') body = JSON.stringify(toPBOM(scan.fc || {}, meta), null, 2);
|
|
485
539
|
else if (format === 'aibom') body = JSON.stringify(buildAIBOM(scan, scan.fc || {}, meta), null, 2);
|
|
486
540
|
else if (format === 'aibom-md') body = aibomToMarkdown(buildAIBOM(scan, scan.fc || {}, meta));
|
|
@@ -637,6 +691,29 @@ async function cmdCi(args) {
|
|
|
637
691
|
}
|
|
638
692
|
process.stderr.write(`[ci] policy gate PASSED (${r.runner}, 0 denials)\n`);
|
|
639
693
|
}
|
|
694
|
+
// R24 (PRD §5): PR-native net-new gate. With --fail-on-new and a baseline
|
|
695
|
+
// ref, block ONLY on findings this PR INTRODUCED (vs the base ref), never on
|
|
696
|
+
// the pre-existing backlog — the posture teams actually leave enabled.
|
|
697
|
+
if (args.flags['fail-on-new']) {
|
|
698
|
+
const baseRef = baseline;
|
|
699
|
+
if (!baseRef) {
|
|
700
|
+
process.stderr.write('[ci] --fail-on-new requires a baseline ref (--baseline <ref> or a CI base-branch env var)\n');
|
|
701
|
+
return 1;
|
|
702
|
+
}
|
|
703
|
+
try {
|
|
704
|
+
const { computePrDelta, netNewGate } = await import('../src/pr-delta.js');
|
|
705
|
+
const delta = await computePrDelta(target, { baseRef });
|
|
706
|
+
const gate = netNewGate(delta, failOn);
|
|
707
|
+
process.stderr.write(
|
|
708
|
+
`[ci] net-new vs ${baseRef}: +${delta.introduced.length} introduced · -${delta.resolved.length} resolved · ${delta.persistent.length} pre-existing (not gated)\n` +
|
|
709
|
+
`[ci] net-new gate fail-on=${failOn} → ${gate.fail ? 'FAIL' : 'pass'}${gate.blocked.length ? ' (' + gate.blocked.length + ' blocking)' : ''}\n`);
|
|
710
|
+
for (const f of gate.blocked.slice(0, 20)) process.stderr.write(` + [${(f.severity || '').toUpperCase()}] ${f.vuln || f.type} ${f.file}:${f.line}\n`);
|
|
711
|
+
return gate.fail ? 1 : 0;
|
|
712
|
+
} catch (e) {
|
|
713
|
+
process.stderr.write(`[ci] net-new gate error: ${(e && e.message) || e} — falling back to full --fail-on gate\n`);
|
|
714
|
+
// Fall through to the full gate rather than passing silently.
|
|
715
|
+
}
|
|
716
|
+
}
|
|
640
717
|
return ciExitCode(scanCode, failOn);
|
|
641
718
|
}
|
|
642
719
|
|
package/dist/178.index.js
CHANGED
|
@@ -13,7 +13,7 @@ export const modules = {
|
|
|
13
13
|
/* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1421);
|
|
14
14
|
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3024);
|
|
15
15
|
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6760);
|
|
16
|
-
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(
|
|
16
|
+
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5099);
|
|
17
17
|
// Time-travel + counterfactual scanning (v0.68).
|
|
18
18
|
//
|
|
19
19
|
// Two new modes that exploit the pure-input shape of runFullScan:
|
package/dist/384.index.js
CHANGED
|
@@ -8,7 +8,7 @@ export const modules = {
|
|
|
8
8
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
9
9
|
/* harmony export */ scanCredentials: () => (/* reexport safe */ _engine_js__WEBPACK_IMPORTED_MODULE_0__.Sv)
|
|
10
10
|
/* harmony export */ });
|
|
11
|
-
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
|
|
11
|
+
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5099);
|
|
12
12
|
// Secrets submodule view of the engine — credential + entropy + TODO scanning.
|
|
13
13
|
|
|
14
14
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export const id = 415;
|
|
2
|
+
export const ids = [415];
|
|
3
|
+
export const modules = {
|
|
4
|
+
|
|
5
|
+
/***/ 5796:
|
|
6
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
7
|
+
|
|
8
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
9
|
+
/* harmony export */ extractImagePackages: () => (/* binding */ extractImagePackages)
|
|
10
|
+
/* harmony export */ });
|
|
11
|
+
/* unused harmony exports parseDpkgStatus, parseApkInstalled */
|
|
12
|
+
// R8 (PRD §5) — container-image OS-package inventory.
|
|
13
|
+
//
|
|
14
|
+
// container.js reads the Dockerfile (FROM + apt/apk install lines). This reads
|
|
15
|
+
// the ACTUAL installed-package databases from an extracted image filesystem
|
|
16
|
+
// (or the host), catching packages baked into the base image that the
|
|
17
|
+
// Dockerfile never names. Components flow into the existing OSV pipeline + SBOM.
|
|
18
|
+
//
|
|
19
|
+
// Pure parsers (unit-tested) + a path-matching extractor. No daemon; reads the
|
|
20
|
+
// package-DB files that appear when an image layer/tarball is unpacked.
|
|
21
|
+
|
|
22
|
+
// Debian/Ubuntu: /var/lib/dpkg/status — RFC822-style blocks separated by blank
|
|
23
|
+
// lines. Only "install ok installed" entries count (not removed/config-files).
|
|
24
|
+
function parseDpkgStatus(text) {
|
|
25
|
+
const out = [];
|
|
26
|
+
for (const block of String(text || '').split(/\n\s*\n/)) {
|
|
27
|
+
const name = (block.match(/^Package:\s*(.+)$/m) || [])[1];
|
|
28
|
+
const version = (block.match(/^Version:\s*(.+)$/m) || [])[1];
|
|
29
|
+
const status = (block.match(/^Status:\s*(.+)$/m) || [])[1] || '';
|
|
30
|
+
if (!name || !version) continue;
|
|
31
|
+
if (!/\binstalled\b/.test(status)) continue;
|
|
32
|
+
out.push({ name: name.trim(), version: version.trim(), ecosystem: 'Debian' });
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Alpine: /lib/apk/db/installed — blocks separated by blank lines; P:=name V:=version.
|
|
38
|
+
function parseApkInstalled(text) {
|
|
39
|
+
const out = [];
|
|
40
|
+
for (const block of String(text || '').split(/\n\s*\n/)) {
|
|
41
|
+
const name = (block.match(/^P:(.+)$/m) || [])[1];
|
|
42
|
+
const version = (block.match(/^V:(.+)$/m) || [])[1];
|
|
43
|
+
if (!name || !version) continue;
|
|
44
|
+
out.push({ name: name.trim(), version: version.trim(), ecosystem: 'Alpine' });
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const _PURL = { Debian: (n, v) => `pkg:deb/debian/${n}@${v}`, Alpine: (n, v) => `pkg:apk/alpine/${n}@${v}` };
|
|
50
|
+
|
|
51
|
+
// File-path tells for an unpacked image. Match on path suffix so it works
|
|
52
|
+
// whether the scan root is the rootfs or a subdirectory of it.
|
|
53
|
+
function _dbKind(path) {
|
|
54
|
+
const p = path.replace(/\\/g, '/');
|
|
55
|
+
if (/(?:^|\/)var\/lib\/dpkg\/status$/.test(p)) return 'dpkg';
|
|
56
|
+
if (/(?:^|\/)lib\/apk\/db\/installed$/.test(p)) return 'apk';
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Extract OS-package components from any image package-DBs present in the scanned
|
|
62
|
+
* files. Returns components in the engine's component shape so they ride the
|
|
63
|
+
* existing OSV/SBOM pipeline. De-dups by ecosystem:name:version.
|
|
64
|
+
*/
|
|
65
|
+
function extractImagePackages(fileContents) {
|
|
66
|
+
const comps = [];
|
|
67
|
+
const seen = new Set();
|
|
68
|
+
for (const [file, content] of Object.entries(fileContents || {})) {
|
|
69
|
+
const kind = _dbKind(file);
|
|
70
|
+
if (!kind) continue;
|
|
71
|
+
const pkgs = kind === 'dpkg' ? parseDpkgStatus(content) : parseApkInstalled(content);
|
|
72
|
+
for (const p of pkgs) {
|
|
73
|
+
const key = `${p.ecosystem}:${p.name}:${p.version}`;
|
|
74
|
+
if (seen.has(key)) continue;
|
|
75
|
+
seen.add(key);
|
|
76
|
+
comps.push({
|
|
77
|
+
name: p.name, version: p.version, group: '', ecosystem: p.ecosystem,
|
|
78
|
+
purl: (_PURL[p.ecosystem] || (() => null))(p.name, p.version),
|
|
79
|
+
filePath: file, isUnpinned: false, isTransitive: false, isOsPackage: true,
|
|
80
|
+
scope: 'required', reachable: true,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return comps;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
/***/ })
|
|
89
|
+
|
|
90
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export const id = 444;
|
|
2
|
+
export const ids = [444];
|
|
3
|
+
export const modules = {
|
|
4
|
+
|
|
5
|
+
/***/ 1444:
|
|
6
|
+
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
7
|
+
|
|
8
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
9
|
+
/* harmony export */ sweepGitHistory: () => (/* binding */ sweepGitHistory)
|
|
10
|
+
/* harmony export */ });
|
|
11
|
+
/* unused harmony exports extractAddedLines, scanHistoryDiff */
|
|
12
|
+
/* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1421);
|
|
13
|
+
// R15 (PRD §5) — git-history secret sweep.
|
|
14
|
+
//
|
|
15
|
+
// A secret removed from HEAD but present in any past commit is still
|
|
16
|
+
// recoverable from `.git` and must be rotated — the most dangerous secret case,
|
|
17
|
+
// and one a working-tree-only scan misses entirely. This sweeps recent history
|
|
18
|
+
// (bounded), feeding the ADDED lines of each commit through the same credential
|
|
19
|
+
// detector the file scan uses.
|
|
20
|
+
//
|
|
21
|
+
// The detector is INJECTED (detectFn) rather than imported, so this module has
|
|
22
|
+
// no dependency back into engine.js (avoids a circular import) and the parsing
|
|
23
|
+
// stays pure + unit-testable. Live-credential validation (is the key active?)
|
|
24
|
+
// needs network and is deferred — see the rollup.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
// Pull the post-image (added) lines out of a unified diff: lines starting with
|
|
29
|
+
// a single '+' (not the '+++' file header). Returns reconstructed text.
|
|
30
|
+
function extractAddedLines(diffText) {
|
|
31
|
+
const out = [];
|
|
32
|
+
for (const line of String(diffText || '').split('\n')) {
|
|
33
|
+
if (line.startsWith('+') && !line.startsWith('+++')) out.push(line.slice(1));
|
|
34
|
+
}
|
|
35
|
+
return out.join('\n');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Pure: run the injected credential detector over a commit's added lines.
|
|
39
|
+
// detectFn has the scanCredentials(fp, raw) shape and returns Finding[].
|
|
40
|
+
function scanHistoryDiff(diffText, commit, detectFn) {
|
|
41
|
+
if (typeof detectFn !== 'function') return [];
|
|
42
|
+
const added = extractAddedLines(diffText);
|
|
43
|
+
if (!added.trim()) return [];
|
|
44
|
+
let findings = [];
|
|
45
|
+
try { findings = detectFn(`git-history@${commit}`, added) || []; } catch { return []; }
|
|
46
|
+
return findings.map((f) => ({
|
|
47
|
+
...f,
|
|
48
|
+
id: `secret-history:${commit}:${f.id || f.vuln || 'secret'}`,
|
|
49
|
+
file: `git-history@${commit}`,
|
|
50
|
+
line: 0,
|
|
51
|
+
commit,
|
|
52
|
+
_historical: true,
|
|
53
|
+
vuln: `${f.vuln || 'Hardcoded Secret'} (in git history)`,
|
|
54
|
+
description: `${f.description || 'A credential was committed.'} Found in commit ${commit}; even if removed from HEAD it remains recoverable from git and must be rotated.`,
|
|
55
|
+
remediation: 'Rotate the credential now, then purge it from history (git filter-repo / BFG) and move it to a secrets manager. Removing it from HEAD alone is insufficient.',
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Sweep up to `maxCommits` of recent history for secrets. Best-effort: returns
|
|
61
|
+
* [] when `scanRoot` is not a git repo or git is unavailable. Dedups a secret
|
|
62
|
+
* that recurs across commits to its earliest sighting.
|
|
63
|
+
*/
|
|
64
|
+
function sweepGitHistory(scanRoot, detectFn, { maxCommits = 50, timeoutMs = 20000 } = {}) {
|
|
65
|
+
if (!scanRoot || typeof detectFn !== 'function') return [];
|
|
66
|
+
let out;
|
|
67
|
+
try {
|
|
68
|
+
out = (0,node_child_process__WEBPACK_IMPORTED_MODULE_0__.execFileSync)('git', ['-C', scanRoot, 'log', '-p', '-n', String(maxCommits), '--no-color', '--no-merges', '--no-textconv'],
|
|
69
|
+
{ encoding: 'utf8', maxBuffer: 96 * 1024 * 1024, timeout: timeoutMs, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
70
|
+
} catch { return []; }
|
|
71
|
+
const parts = out.split(/^commit ([0-9a-f]{7,40})/m); // [pre, sha, body, sha, body, ...]
|
|
72
|
+
const findings = [];
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
for (let i = 1; i < parts.length; i += 2) {
|
|
75
|
+
const sha = (parts[i] || '').slice(0, 12);
|
|
76
|
+
for (const f of scanHistoryDiff(parts[i + 1] || '', sha, detectFn)) {
|
|
77
|
+
const key = `${f.vuln}:${(f.snippet || f.match || '').slice(0, 40)}`;
|
|
78
|
+
if (seen.has(key)) continue;
|
|
79
|
+
seen.add(key);
|
|
80
|
+
findings.push(f);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return findings;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
/***/ })
|
|
88
|
+
|
|
89
|
+
};
|