@clear-capabilities/agentic-security-scanner 0.78.0 → 0.80.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/bin/.agentic-security/findings.json +16 -16
- package/bin/.agentic-security/last-scan.json +16 -16
- package/bin/.agentic-security/last-scan.json.sig +1 -1
- package/bin/.agentic-security/scan-history.json +51 -0
- package/bin/.agentic-security/streak.json +5 -5
- package/bin/agentic-security.js +22 -7
- package/dist/178.index.js +1 -1
- package/dist/333.index.js +283 -0
- package/dist/384.index.js +1 -1
- package/dist/476.index.js +5 -5
- package/dist/637.index.js +1 -1
- package/dist/700.index.js +138 -0
- package/dist/718.index.js +53 -0
- package/dist/838.index.js +1 -1
- package/dist/985.index.js +95 -1
- package/dist/agentic-security.mjs +83 -83
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +6 -4
- package/src/.agentic-security/findings.json +29799 -7803
- package/src/.agentic-security/last-scan.json +29799 -7803
- package/src/.agentic-security/last-scan.json.sig +1 -1
- package/src/.agentic-security/scan-history.json +5119 -2611
- package/src/.agentic-security/streak.json +6 -6
- package/src/dataflow/.agentic-security/findings.json +2879 -308
- package/src/dataflow/.agentic-security/last-scan.json +2879 -308
- package/src/dataflow/.agentic-security/last-scan.json.sig +1 -1
- package/src/dataflow/.agentic-security/scan-history.json +68 -520
- package/src/dataflow/.agentic-security/streak.json +6 -7
- package/src/dataflow/cross-service-taint.js +201 -0
- package/src/dataflow/engine.js +52 -8
- package/src/dataflow/formal-verify.js +204 -0
- package/src/dataflow/ifds-precise.js +222 -0
- package/src/dataflow/k2-summary-cache.js +153 -0
- package/src/dataflow/lib-taint-summaries.js +198 -0
- package/src/dataflow/privacy-taint.js +205 -0
- package/src/dataflow/smt-feasibility.js +189 -0
- package/src/engine.js +890 -132
- package/src/integrations/index.js +2 -1
- package/src/ir/.agentic-security/findings.json +240 -6
- package/src/ir/.agentic-security/last-scan.json +240 -6
- package/src/ir/.agentic-security/last-scan.json.sig +1 -1
- package/src/ir/.agentic-security/scan-history.json +16 -594
- package/src/ir/.agentic-security/streak.json +8 -9
- package/src/ir/callgraph.js +27 -7
- package/src/ir/cpp-preprocessor.js +142 -0
- package/src/ir/csharp-ir.js +604 -0
- package/src/ir/universal-ir.js +403 -0
- package/src/llm-validator/index.js +7 -5
- package/src/mcp/.agentic-security/findings.json +8632 -0
- package/src/mcp/.agentic-security/last-scan.json +8632 -0
- package/src/mcp/.agentic-security/last-scan.json.sig +1 -0
- package/src/mcp/.agentic-security/scan-history.json +143 -0
- package/src/mcp/.agentic-security/streak.json +20 -0
- package/src/mcp/audit.js +5 -0
- package/src/mcp/tools.js +90 -1
- package/src/posture/.agentic-security/findings.json +16809 -4367
- package/src/posture/.agentic-security/last-scan.json +16809 -4367
- package/src/posture/.agentic-security/last-scan.json.sig +1 -1
- package/src/posture/.agentic-security/scan-history.json +6689 -177
- package/src/posture/.agentic-security/streak.json +8 -7
- package/src/posture/api-contract.js +193 -0
- package/src/posture/attack-taxonomy.js +227 -0
- package/src/posture/calibration-drift.js +2 -1
- package/src/posture/calibration.js +3 -2
- package/src/posture/compliance-policy.js +218 -0
- package/src/posture/composite-risk.js +122 -0
- package/src/posture/csharp-analysis.js +330 -0
- package/src/posture/exploit-bundle.js +210 -0
- package/src/posture/federated-learning.js +172 -0
- package/src/posture/fix-history.js +8 -2
- package/src/posture/license-attributions.js +94 -0
- package/src/posture/license-graph.js +238 -0
- package/src/posture/pqc-migration-plan.js +158 -0
- package/src/posture/profile.js +4 -5
- package/src/posture/reachability-filter.js +33 -2
- package/src/posture/realtime-cve-monitor.js +214 -0
- package/src/posture/rule-overrides.js +2 -3
- package/src/posture/rule-pack-signing.js +2 -3
- package/src/posture/rule-synthesis.js +5 -6
- package/src/posture/runtime-correlation.js +174 -0
- package/src/posture/sbom-diff.js +171 -0
- package/src/posture/sca-policy.js +235 -0
- package/src/posture/sca-upgrade.js +259 -0
- package/src/posture/security-trend.js +4 -7
- package/src/posture/state-dir.js +124 -0
- package/src/posture/streak.js +3 -0
- package/src/posture/suppressions.js +5 -8
- package/src/posture/threat-model-auto.js +268 -0
- package/src/posture/triage-learning.js +170 -0
- package/src/posture/triage.js +29 -6
- package/src/posture/validator-metrics.js +3 -6
- package/src/sast/.agentic-security/findings.json +996 -32
- package/src/sast/.agentic-security/last-scan.json +996 -32
- package/src/sast/.agentic-security/last-scan.json.sig +1 -1
- package/src/sast/.agentic-security/scan-history.json +565 -32
- package/src/sast/.agentic-security/streak.json +10 -8
- package/src/sast/_secret-entropy.js +145 -0
- package/src/sast/cloud-iam.js +312 -0
- package/src/sast/cpp.js +138 -4
- package/src/sast/crypto-protocol.js +388 -0
- package/src/sast/csharp-tokenizer.js +392 -0
- package/src/sast/csharp.js +924 -138
- package/src/sast/dapp-frontend.js +200 -0
- package/src/sast/db-taint.js +24 -0
- package/src/sast/k8s-admission.js +271 -0
- package/src/sast/llm-app.js +272 -0
- package/src/sast/ml-supply-chain.js +259 -0
- package/src/sast/mobile.js +224 -0
- package/src/sast/post-quantum-crypto.js +348 -0
- package/src/sast/rust.js +26 -0
- package/src/sast/web3-advanced.js +375 -0
- package/src/sca/.agentic-security/findings.json +6044 -171
- package/src/sca/.agentic-security/last-scan.json +6044 -171
- package/src/sca/.agentic-security/last-scan.json.sig +1 -1
- package/src/sca/.agentic-security/scan-history.json +83 -6
- package/src/sca/.agentic-security/streak.json +9 -9
- package/src/sca/CLAUDE.md +161 -0
- package/src/sca/binary-metadata.js +146 -0
- package/src/sca/py-package-functions.js +118 -0
- package/src/sca/sigstore-verify.js +215 -0
- package/src/sca/vendor-detect.js +53 -0
- package/src/report/.agentic-security/findings.json +0 -80
- package/src/report/.agentic-security/last-scan.json +0 -80
- package/src/report/.agentic-security/last-scan.json.sig +0 -1
- package/src/report/.agentic-security/scan-history.json +0 -35
- package/src/report/.agentic-security/streak.json +0 -22
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
c1f9857f9226707719cde39879802be56f679e86412d6a32696ac85594786f41
|
|
@@ -1,36 +1,113 @@
|
|
|
1
1
|
[
|
|
2
2
|
{
|
|
3
|
-
"timestamp": "2026-05-
|
|
3
|
+
"timestamp": "2026-05-28T17:58:30.776Z",
|
|
4
4
|
"label": "scan",
|
|
5
|
-
"total":
|
|
5
|
+
"total": 23,
|
|
6
6
|
"critical": 0,
|
|
7
7
|
"high": 0,
|
|
8
|
-
"medium":
|
|
8
|
+
"medium": 23,
|
|
9
9
|
"low": 0,
|
|
10
10
|
"kev": 0,
|
|
11
11
|
"ids": [
|
|
12
|
+
"struct:binary-metadata.js:124:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
13
|
+
"struct:binary-metadata.js:133:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
14
|
+
"struct:binary-metadata.js:139:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
15
|
+
"struct:binary-metadata.js:47:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
16
|
+
"struct:binary-metadata.js:48:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
17
|
+
"struct:binary-metadata.js:67:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
18
|
+
"struct:binary-metadata.js:68:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
12
19
|
"struct:dep-confusion.js:56:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
13
20
|
"struct:dep-confusion.js:58:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
21
|
+
"struct:llm-function-extract.js:24:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
22
|
+
"struct:llm-function-extract.js:31:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
23
|
+
"struct:py-package-functions.js:19:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
24
|
+
"struct:py-package-functions.js:21:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
25
|
+
"struct:py-package-functions.js:22:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
26
|
+
"struct:py-package-functions.js:25:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
27
|
+
"struct:py-package-functions.js:33:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
28
|
+
"struct:py-package-functions.js:48:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
29
|
+
"struct:py-package-functions.js:56:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
30
|
+
"struct:py-package-functions.js:62:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
14
31
|
"struct:sarif-ingest.js:112:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
32
|
+
"toctou-fs:binary-metadata.js:47",
|
|
33
|
+
"toctou-fs:binary-metadata.js:67",
|
|
15
34
|
"toctou-fs:dep-confusion.js:56"
|
|
16
35
|
]
|
|
17
36
|
},
|
|
18
37
|
{
|
|
19
|
-
"timestamp": "2026-05-
|
|
38
|
+
"timestamp": "2026-05-28T17:59:33.255Z",
|
|
20
39
|
"label": "scan",
|
|
21
|
-
"total":
|
|
40
|
+
"total": 23,
|
|
22
41
|
"critical": 0,
|
|
23
42
|
"high": 0,
|
|
24
|
-
"medium":
|
|
43
|
+
"medium": 23,
|
|
25
44
|
"low": 0,
|
|
26
45
|
"kev": 0,
|
|
27
46
|
"ids": [
|
|
47
|
+
"struct:binary-metadata.js:124:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
48
|
+
"struct:binary-metadata.js:133:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
49
|
+
"struct:binary-metadata.js:139:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
50
|
+
"struct:binary-metadata.js:47:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
51
|
+
"struct:binary-metadata.js:48:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
52
|
+
"struct:binary-metadata.js:67:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
53
|
+
"struct:binary-metadata.js:68:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
28
54
|
"struct:dep-confusion.js:56:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
29
55
|
"struct:dep-confusion.js:58:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
30
56
|
"struct:llm-function-extract.js:24:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
31
57
|
"struct:llm-function-extract.js:31:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
58
|
+
"struct:py-package-functions.js:19:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
59
|
+
"struct:py-package-functions.js:21:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
60
|
+
"struct:py-package-functions.js:22:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
61
|
+
"struct:py-package-functions.js:25:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
62
|
+
"struct:py-package-functions.js:33:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
63
|
+
"struct:py-package-functions.js:48:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
64
|
+
"struct:py-package-functions.js:56:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
65
|
+
"struct:py-package-functions.js:62:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
32
66
|
"struct:sarif-ingest.js:112:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
67
|
+
"toctou-fs:binary-metadata.js:47",
|
|
68
|
+
"toctou-fs:binary-metadata.js:67",
|
|
33
69
|
"toctou-fs:dep-confusion.js:56"
|
|
34
70
|
]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"timestamp": "2026-05-29T06:29:23.737Z",
|
|
74
|
+
"label": "scan",
|
|
75
|
+
"total": 29,
|
|
76
|
+
"critical": 0,
|
|
77
|
+
"high": 1,
|
|
78
|
+
"medium": 28,
|
|
79
|
+
"low": 0,
|
|
80
|
+
"kev": 0,
|
|
81
|
+
"ids": [
|
|
82
|
+
"struct:binary-metadata.js:124:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
83
|
+
"struct:binary-metadata.js:133:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
84
|
+
"struct:binary-metadata.js:139:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
85
|
+
"struct:binary-metadata.js:47:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
86
|
+
"struct:binary-metadata.js:48:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
87
|
+
"struct:binary-metadata.js:67:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
88
|
+
"struct:binary-metadata.js:68:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
89
|
+
"struct:dep-confusion.js:56:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
90
|
+
"struct:dep-confusion.js:58:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
91
|
+
"struct:llm-function-extract.js:24:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
92
|
+
"struct:llm-function-extract.js:31:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
93
|
+
"struct:py-package-functions.js:19:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
94
|
+
"struct:py-package-functions.js:21:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
95
|
+
"struct:py-package-functions.js:22:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
96
|
+
"struct:py-package-functions.js:25:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
97
|
+
"struct:py-package-functions.js:33:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
98
|
+
"struct:py-package-functions.js:48:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
99
|
+
"struct:py-package-functions.js:56:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
100
|
+
"struct:py-package-functions.js:62:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
101
|
+
"struct:sarif-ingest.js:112:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
102
|
+
"struct:sigstore-verify.js:159:Unsafe_Deserialization_(User-Controlled_JSON)",
|
|
103
|
+
"struct:sigstore-verify.js:53:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
104
|
+
"struct:sigstore-verify.js:55:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
105
|
+
"struct:sigstore-verify.js:57:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
106
|
+
"struct:sigstore-verify.js:62:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
107
|
+
"toctou-fs:binary-metadata.js:47",
|
|
108
|
+
"toctou-fs:binary-metadata.js:67",
|
|
109
|
+
"toctou-fs:dep-confusion.js:56",
|
|
110
|
+
"toctou-fs:sigstore-verify.js:53"
|
|
111
|
+
]
|
|
35
112
|
}
|
|
36
113
|
]
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
|
-
"firstScanDate": "2026-05-
|
|
3
|
-
"lastScanDate": "2026-05-
|
|
4
|
-
"totalScans":
|
|
5
|
-
"daysCleanCritical":
|
|
6
|
-
"lastCleanDate": "2026-05-
|
|
2
|
+
"firstScanDate": "2026-05-28T17:58:30.804Z",
|
|
3
|
+
"lastScanDate": "2026-05-29T06:29:23.771Z",
|
|
4
|
+
"totalScans": 3,
|
|
5
|
+
"daysCleanCritical": 2,
|
|
6
|
+
"lastCleanDate": "2026-05-29",
|
|
7
7
|
"lastCriticalDate": null,
|
|
8
8
|
"hasEverHadCritical": false,
|
|
9
|
-
"bestDaysCleanCritical":
|
|
10
|
-
"totalFindingsAtFirstScan":
|
|
11
|
-
"totalFindingsAtLastScan":
|
|
9
|
+
"bestDaysCleanCritical": 2,
|
|
10
|
+
"totalFindingsAtFirstScan": 26,
|
|
11
|
+
"totalFindingsAtLastScan": 35,
|
|
12
12
|
"totalFixesInferred": 0,
|
|
13
|
-
"lastGrade": "A",
|
|
13
|
+
"lastGrade": "A-",
|
|
14
14
|
"bestGrade": "A",
|
|
15
15
|
"launchCheckPassedAt": null,
|
|
16
16
|
"achievements": [
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# scanner/src/sca/
|
|
2
|
+
|
|
3
|
+
Software Composition Analysis. Detects vulnerable dependencies (OSV + KEV
|
|
4
|
+
+ EPSS), dependency confusion, typosquats, vendored copies, deprecated
|
|
5
|
+
components, and EOL container base images. Reads manifests in 11
|
|
6
|
+
ecosystems and the most common lockfiles in each.
|
|
7
|
+
|
|
8
|
+
Most of the SCA *pipeline* lives in `../engine.js` (the manifest dispatch
|
|
9
|
+
in `parseManifests`, OSV/KEV/EPSS enrichment, attack-path computation).
|
|
10
|
+
This directory holds the eight specialized modules called from there.
|
|
11
|
+
|
|
12
|
+
## Modules
|
|
13
|
+
|
|
14
|
+
| Module | Purpose |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `index.js` | Re-exports six public symbols from `../engine.js` so external consumers can `import { parseManifests, queryOSV, … } from '@…/sca'`. |
|
|
17
|
+
| `binary-metadata.js` | **Opt-in via `AGENTIC_SECURITY_BINARY_SCA=1`.** Reads dependency metadata from compiled artifacts: JAR `META-INF/MANIFEST.MF` + `pom.properties`, Go binary `go.buildinfo`. Never executes the binary. JAR extraction uses `fs.mkdtemp` for an isolated scratch dir (premortem-derived: shared `/tmp` lets a hostile JAR plant a symlinked manifest that escapes the scratch). |
|
|
18
|
+
| `container.js` | Dockerfile parser. Detects EOL `FROM` base images (alpine/debian/ubuntu/node/python) against `base-images.json`, and synthesizes lightweight SCA components from `apt-get install` / `apk add` package lists. No Docker daemon required. |
|
|
19
|
+
| `dep-confusion.js` | Two related detectors. **Typosquat:** Levenshtein distance ≤ 2 against the top-1000 packages in `popular-packages.json`. **Dependency confusion:** internal-scoped names (declared in `.agentic-security/internal-scopes.yml`) appearing on the public registry. Local-first; OSV consulted only to confirm confusion findings. |
|
|
20
|
+
| `llm-function-extract.js` | **Opt-in via `AGENTIC_SECURITY_LLM_SCA=1`.** LLM-assisted extraction of vulnerable function names for CVEs that lack OSV `ecosystem_specific.vulnerable_functions` data. Cached per CVE under `~/.config/agentic-security/llm-sca-cache/`. Endpoint-dependent — degrades to no-op when unreachable. |
|
|
21
|
+
| `py-package-functions.js` | **Opt-in via `AGENTIC_SECURITY_DEEP=1`** (Python only). Locates installed Python packages via `site-packages` and parses them with the CPython `ast` module (subprocess) to *validate* that an OSV-named vulnerable function exists in the installed version. Closes the "OSV says this function is vulnerable, but the version you installed actually removed it" false-positive class. |
|
|
22
|
+
| `sarif-ingest.js` | Normalizes SARIF 2.1.0 from external scanners and merges into the unified scan. Deduplicates by fingerprint `(CWE, file, line ± 2, rule)`. Twelve tool profiles supported with default-severity + semantic-kind mapping. |
|
|
23
|
+
| `vendor-detect.js` | Detects libraries copied into `src/` (lodash, jQuery, Angular, React, etc.) via characteristic version strings and function signatures. Catches the case where a vulnerable library bypasses the lockfile because someone vendored it directly. |
|
|
24
|
+
|
|
25
|
+
## Data sources + caches
|
|
26
|
+
|
|
27
|
+
| Source | Cache location | TTL | Trigger |
|
|
28
|
+
|---|---|---:|---|
|
|
29
|
+
| OSV.dev `/v1/querybatch` | `~/.claude/agentic-security/osv-cache/` (sha256-keyed JSON blobs) | session | every SCA-enabled scan |
|
|
30
|
+
| OSV.dev `/v1/vulns/{id}` | same | session | per unique vuln id from querybatch |
|
|
31
|
+
| CISA KEV catalog | same, key `kev:catalog` | 24h | first SCA finding per scan |
|
|
32
|
+
| FIRST.org EPSS | same, key `epss:<CVE>` | session | batched (100 CVEs / request — see `engine.js:_fetchEPSSBatch`) |
|
|
33
|
+
| PyPI registry | session | session | `queryRegistries` for deprecated/yanked/inactive checks |
|
|
34
|
+
| npm registry | session | session | fallback deprecation lookup |
|
|
35
|
+
| OSV.dev licenses | repo-level fetch | per dep | license-policy enforcement |
|
|
36
|
+
|
|
37
|
+
All network access degrades gracefully when offline
|
|
38
|
+
(`AGENTIC_SECURITY_OFFLINE=1` forces this on); missing data results in
|
|
39
|
+
incomplete fields, never a hard failure.
|
|
40
|
+
|
|
41
|
+
## Manifest + lockfile dispatch
|
|
42
|
+
|
|
43
|
+
The PARSERS table in `engine.js#parseManifests` maps basename →
|
|
44
|
+
`_parseXxx` function. As of Phase 1 of the SCA improvement plan
|
|
45
|
+
(commit `f8a4c3e`):
|
|
46
|
+
|
|
47
|
+
| Ecosystem | Direct deps | Transitive deps |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| npm | `package.json` | `package-lock.json` ✓, `yarn.lock` ✓, `pnpm-lock.yaml` ✓ |
|
|
50
|
+
| pypi | `requirements.txt`, `pyproject.toml`, `Pipfile` | `poetry.lock` ✓, `Pipfile.lock` ✓ |
|
|
51
|
+
| packagist | `composer.json` | `composer.lock` ✓ |
|
|
52
|
+
| rubygems | `Gemfile` | `Gemfile.lock` ✓ |
|
|
53
|
+
| golang | `go.mod` | `go.sum` ✓ (Phase 1) |
|
|
54
|
+
| cargo | `Cargo.toml` | `Cargo.lock` ✓ |
|
|
55
|
+
| maven | `pom.xml` (+ `<properties>` substitution + `<dependencyManagement>` BOM labelling, Phase 1) | `dependency-tree.txt` ✓ (Phase 1) — output of `mvn dependency:tree -DoutputFile=…` |
|
|
56
|
+
| maven (gradle) | `build.gradle`, `build.gradle.kts` | **not transitive** — Gradle dependency graph deferred per project policy |
|
|
57
|
+
| pub (Dart/Flutter) | `pubspec.yaml` | `pubspec.lock` ✓ |
|
|
58
|
+
| system (Conan) | `conanfile.txt` (regex) | `conan.lock` ✓ (Phase 1, both Conan 1.x and 2.x) |
|
|
59
|
+
| system (vcpkg) | `vcpkg.json` | `vcpkg-configuration.json` ✓ (Phase 1, overlay registries) |
|
|
60
|
+
| system (CMake) | `CMakeLists.txt` | n/a — Conan / vcpkg are the real lockfile surface |
|
|
61
|
+
|
|
62
|
+
## Finding shape (supplyChain bucket)
|
|
63
|
+
|
|
64
|
+
Each SCA finding lives in `scan.supplyChain` (kept separate from
|
|
65
|
+
`scan.findings` which is the SAST array). Required + commonly-set fields:
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
{
|
|
69
|
+
type: 'vulnerable_dep' | 'unpinned_dep' | 'no_lockfile',
|
|
70
|
+
name, version, ecosystem, group, scope, purl, file,
|
|
71
|
+
// OSV enrichment
|
|
72
|
+
osvId, cveAliases: ['CVE-…'], description, fixedVersions: ['…'],
|
|
73
|
+
severity, cvssVector, hasKnownAttackRef,
|
|
74
|
+
osvVulnFunctions: ['module.fn', '…'],
|
|
75
|
+
// Reachability
|
|
76
|
+
reachable: true | false,
|
|
77
|
+
functionReachable: 'reachable' | 'unreachable' | 'unknown',
|
|
78
|
+
reachabilityTier: 'function-reachable' | 'import-reachable'
|
|
79
|
+
| 'build-only' | 'manifest-only' | 'transitive-only',
|
|
80
|
+
// Risk overlays (added by posture annotators)
|
|
81
|
+
kev, kevDateAdded, kevRansomware, weaponized,
|
|
82
|
+
epssScore, epssPercentile, exploitedNow,
|
|
83
|
+
toxicityScore, toxicityLabel,
|
|
84
|
+
// Composite (Phase 1)
|
|
85
|
+
compositeRisk: 0..100,
|
|
86
|
+
compositeRiskTier: 'critical' | 'high' | 'medium' | 'low' | 'minimal',
|
|
87
|
+
compositeRiskFactors: ['…'],
|
|
88
|
+
// Provenance
|
|
89
|
+
pomSource: 'direct' | 'managed' | 'dependency-tree',
|
|
90
|
+
isTransitive: true | false,
|
|
91
|
+
isUnpinned: boolean,
|
|
92
|
+
// Dedup
|
|
93
|
+
dependents: [], _transitiveDeduped: int,
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`parser` + `family` defaults are backfilled by `posture/finding-defaults.js`
|
|
98
|
+
if a detector forgets to set them.
|
|
99
|
+
|
|
100
|
+
## Conventions specific to this directory
|
|
101
|
+
|
|
102
|
+
- **No detector executes downloaded code.** Manifest parsing only.
|
|
103
|
+
`binary-metadata.js` calls the `jar` CLI tool but only with extract-only
|
|
104
|
+
flags into an isolated scratch dir.
|
|
105
|
+
- **Opt-in flags.** `AGENTIC_SECURITY_BINARY_SCA`, `AGENTIC_SECURITY_DEEP`,
|
|
106
|
+
`AGENTIC_SECURITY_LLM_SCA` all default off. Each module documents its
|
|
107
|
+
own activation gate.
|
|
108
|
+
- **No new dependencies.** Maven / Conan / vcpkg use inline regex /
|
|
109
|
+
JSON parsing — `fast-xml-parser` was deliberately not added (premortem:
|
|
110
|
+
bundle-size + audit-surface concern).
|
|
111
|
+
- **Network fan-out.** OSV `/v1/querybatch` accepts 1000-package batches;
|
|
112
|
+
EPSS accepts ~100 CVEs per `?cve=` URL; OSV vuln-details has no batch
|
|
113
|
+
endpoint so it's parallelized with a concurrency cap of 20
|
|
114
|
+
(`engine.js#queryOSV`).
|
|
115
|
+
- **Cache hits are session-storage backed.** `_osvCacheGet` /
|
|
116
|
+
`_osvCacheSet` route through a disk-backed shim
|
|
117
|
+
(`engine.js:145`) into `~/.claude/agentic-security/osv-cache/`.
|
|
118
|
+
|
|
119
|
+
## Gotchas
|
|
120
|
+
|
|
121
|
+
- **`type: 'vulnerable_dep'` lives in supplyChain, not findings.** Code
|
|
122
|
+
that iterates `scan.findings` will miss every SCA finding. The report
|
|
123
|
+
layer's `normalizeFindings()` is the only place they're merged.
|
|
124
|
+
- **`isTransitive` is detector-set when the lockfile knows.** `go.sum`,
|
|
125
|
+
`dependency-tree.txt`, `conan.lock` set it. `package-lock.json` does
|
|
126
|
+
not currently set it explicitly (treats every entry equivalently);
|
|
127
|
+
treat that as a known limitation.
|
|
128
|
+
- **Function-level reachability is regex-based.** `markUsedVulnFunctions`
|
|
129
|
+
scans for `funcName(` patterns in `fileContents`. False negatives on
|
|
130
|
+
aliased imports (`{ vuln_fn as safeName }`) and dynamic dispatch.
|
|
131
|
+
- **EOL base-image detection has a hand-curated cutoff.** `base-images.json`
|
|
132
|
+
is updated periodically; an alpine-3.16 today might not appear EOL until
|
|
133
|
+
the file is refreshed. Bias is toward false negatives.
|
|
134
|
+
- **Typosquat threshold is a single distance.** Levenshtein ≤ 2 against
|
|
135
|
+
the top-1000 list. Increasing the threshold blows up the FP rate;
|
|
136
|
+
decreasing it loses real typosquats. This is the calibrated default.
|
|
137
|
+
|
|
138
|
+
## Adding a new detector here
|
|
139
|
+
|
|
140
|
+
1. Decide whether it fits an existing module (e.g. add a new typosquat
|
|
141
|
+
variant to `dep-confusion.js`) or warrants a new file.
|
|
142
|
+
2. Re-export any public function from `index.js` IF external consumers
|
|
143
|
+
need it; otherwise keep it private.
|
|
144
|
+
3. If it emits findings, set `family: 'vulnerable-dep'` (or a new family
|
|
145
|
+
recognized by `posture/finding-defaults.js`) so downstream calibration
|
|
146
|
+
and confidence pipelines work.
|
|
147
|
+
4. Add a regression test under `../../test/`. The pattern at
|
|
148
|
+
`scanner/test/sca-deprecated.test.js` is the simplest model for a
|
|
149
|
+
network-stubbed detector.
|
|
150
|
+
5. Wire the test file into `npm run test:posture` in `../../package.json`.
|
|
151
|
+
|
|
152
|
+
## Open work (tracked in the SCA improvement plan)
|
|
153
|
+
|
|
154
|
+
- Maven Gradle dependency-graph integration (currently regex-only, no
|
|
155
|
+
transitives) — deferred from v1 due to Gradle shell-out fragility.
|
|
156
|
+
- Vulnerable-function reachability chained back to an HTTP route handler
|
|
157
|
+
for SCA (Phase 2 / Item 4 — was missing at the time of Phase 1's land).
|
|
158
|
+
- SCA-aware remediation MCP toolchain (`synthesize_sca_upgrade` /
|
|
159
|
+
`apply_sca_upgrade`) — Phase 3.
|
|
160
|
+
- `.agentic-security/sca-policy.yml` for per-CVE / per-package accept-risk +
|
|
161
|
+
SLA — Phase 4.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Binary artifact SCA metadata extraction.
|
|
2
|
+
//
|
|
3
|
+
// Reads dependency information from compiled artifacts:
|
|
4
|
+
// - Java JAR files: META-INF/MANIFEST.MF for version + classpath
|
|
5
|
+
// - Go binaries: embedded go.buildinfo for dependency tree
|
|
6
|
+
//
|
|
7
|
+
// Gated behind AGENTIC_SECURITY_BINARY_SCA=1 (opt-in).
|
|
8
|
+
// Does NOT execute binaries — only reads metadata sections.
|
|
9
|
+
|
|
10
|
+
import * as fs from 'node:fs';
|
|
11
|
+
import * as os from 'node:os';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import { execFileSync } from 'node:child_process';
|
|
14
|
+
|
|
15
|
+
export function isBinaryScaEnabled() {
|
|
16
|
+
return process.env.AGENTIC_SECURITY_BINARY_SCA === '1';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Create a per-extraction temporary directory. Two reasons we can't use
|
|
20
|
+
// /tmp directly:
|
|
21
|
+
// 1. Permissions — /tmp is shared and a hostile JAR could try to plant a
|
|
22
|
+
// symlinked META-INF/MANIFEST.MF that escapes the scratch dir.
|
|
23
|
+
// 2. Concurrency — two parallel extractions on the same jarPath would
|
|
24
|
+
// race on /tmp/META-INF/MANIFEST.MF.
|
|
25
|
+
// fs.mkdtempSync atomically allocates a unique dir under the OS temp root;
|
|
26
|
+
// the caller is responsible for cleaning it up on every exit path.
|
|
27
|
+
function _allocScratchDir() {
|
|
28
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'agentic-security-sca-'));
|
|
29
|
+
}
|
|
30
|
+
function _cleanupScratchDir(dir) {
|
|
31
|
+
if (!dir) return;
|
|
32
|
+
try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* best-effort */ }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function extractJarMetadata(jarPath) {
|
|
36
|
+
if (!jarPath || !jarPath.endsWith('.jar')) return null;
|
|
37
|
+
let scratchDir = null;
|
|
38
|
+
try {
|
|
39
|
+
const out = execFileSync('jar', ['tf', jarPath], { encoding: 'utf8', timeout: 5000 });
|
|
40
|
+
const hasManifest = out.includes('META-INF/MANIFEST.MF');
|
|
41
|
+
if (!hasManifest) return null;
|
|
42
|
+
scratchDir = _allocScratchDir();
|
|
43
|
+
execFileSync('jar', ['xf', jarPath, 'META-INF/MANIFEST.MF'], {
|
|
44
|
+
encoding: 'utf8', timeout: 5000, cwd: scratchDir,
|
|
45
|
+
});
|
|
46
|
+
const manifestPath = path.join(scratchDir, 'META-INF', 'MANIFEST.MF');
|
|
47
|
+
if (!fs.existsSync(manifestPath)) return null;
|
|
48
|
+
const content = fs.readFileSync(manifestPath, 'utf8');
|
|
49
|
+
const attrs = {};
|
|
50
|
+
for (const line of content.split('\n')) {
|
|
51
|
+
const m = line.match(/^([A-Za-z-]+):\s*(.+)$/);
|
|
52
|
+
if (m) attrs[m[1].toLowerCase()] = m[2].trim();
|
|
53
|
+
}
|
|
54
|
+
const hasPom = out.includes('pom.properties');
|
|
55
|
+
let groupId = attrs['implementation-vendor-id'] || '';
|
|
56
|
+
let artifactId = attrs['implementation-title'] || path.basename(jarPath, '.jar');
|
|
57
|
+
let version = attrs['implementation-version'] || attrs['bundle-version'] || 'unknown';
|
|
58
|
+
if (hasPom) {
|
|
59
|
+
try {
|
|
60
|
+
const pomFiles = out.split('\n').filter(l => l.includes('pom.properties'));
|
|
61
|
+
if (pomFiles.length) {
|
|
62
|
+
execFileSync('jar', ['xf', jarPath, ...pomFiles], {
|
|
63
|
+
timeout: 5000, cwd: scratchDir,
|
|
64
|
+
});
|
|
65
|
+
for (const pf of pomFiles) {
|
|
66
|
+
const pfPath = path.join(scratchDir, pf);
|
|
67
|
+
if (!fs.existsSync(pfPath)) continue;
|
|
68
|
+
const props = fs.readFileSync(pfPath, 'utf8');
|
|
69
|
+
for (const line of props.split('\n')) {
|
|
70
|
+
if (line.startsWith('groupId=')) groupId = line.split('=')[1].trim();
|
|
71
|
+
if (line.startsWith('artifactId=')) artifactId = line.split('=')[1].trim();
|
|
72
|
+
if (line.startsWith('version=')) version = line.split('=')[1].trim();
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} catch { /* pom extraction optional */ }
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
name: artifactId,
|
|
81
|
+
version,
|
|
82
|
+
group: groupId,
|
|
83
|
+
ecosystem: 'maven',
|
|
84
|
+
filePath: jarPath,
|
|
85
|
+
scope: 'required',
|
|
86
|
+
purl: `pkg:maven/${groupId}/${artifactId}@${version}`,
|
|
87
|
+
isUnpinned: false,
|
|
88
|
+
_source: 'jar-manifest',
|
|
89
|
+
};
|
|
90
|
+
} catch { return null; }
|
|
91
|
+
finally { _cleanupScratchDir(scratchDir); }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function extractGoBuildInfo(binPath) {
|
|
95
|
+
if (!binPath) return [];
|
|
96
|
+
try {
|
|
97
|
+
const out = execFileSync('go', ['version', '-m', binPath], { encoding: 'utf8', timeout: 5000 });
|
|
98
|
+
const deps = [];
|
|
99
|
+
for (const line of out.split('\n')) {
|
|
100
|
+
const m = line.match(/^\s*dep\s+([\w./-]+)\s+(v[\d.]+(?:-[\w.]+)?)/);
|
|
101
|
+
if (m) {
|
|
102
|
+
deps.push({
|
|
103
|
+
name: m[1],
|
|
104
|
+
version: m[2].replace(/^v/, ''),
|
|
105
|
+
group: '',
|
|
106
|
+
ecosystem: 'golang',
|
|
107
|
+
filePath: binPath,
|
|
108
|
+
scope: 'required',
|
|
109
|
+
purl: `pkg:golang/${m[1]}@${m[2]}`,
|
|
110
|
+
isUnpinned: false,
|
|
111
|
+
_source: 'go-buildinfo',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return deps;
|
|
116
|
+
} catch { return []; }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function scanBinaryArtifacts(fileContents, scanRoot) {
|
|
120
|
+
if (!isBinaryScaEnabled()) return [];
|
|
121
|
+
const components = [];
|
|
122
|
+
const root = scanRoot || '.';
|
|
123
|
+
try {
|
|
124
|
+
const jarFiles = fs.readdirSync(root, { recursive: true })
|
|
125
|
+
.filter(f => f.endsWith('.jar') && !f.includes('node_modules'))
|
|
126
|
+
.slice(0, 20);
|
|
127
|
+
for (const jar of jarFiles) {
|
|
128
|
+
const meta = extractJarMetadata(path.join(root, jar));
|
|
129
|
+
if (meta) components.push(meta);
|
|
130
|
+
}
|
|
131
|
+
} catch { /* jar scan optional */ }
|
|
132
|
+
try {
|
|
133
|
+
const goBins = fs.readdirSync(root, { recursive: true })
|
|
134
|
+
.filter(f => !f.includes('.') && !f.includes('node_modules') && !f.includes('/'))
|
|
135
|
+
.slice(0, 10);
|
|
136
|
+
for (const bin of goBins) {
|
|
137
|
+
const fp = path.join(root, bin);
|
|
138
|
+
try {
|
|
139
|
+
if (fs.statSync(fp).isFile() && (fs.statSync(fp).mode & 0o111)) {
|
|
140
|
+
components.push(...extractGoBuildInfo(fp));
|
|
141
|
+
}
|
|
142
|
+
} catch { /* skip non-executable */ }
|
|
143
|
+
}
|
|
144
|
+
} catch { /* go binary scan optional */ }
|
|
145
|
+
return components;
|
|
146
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Python package function extraction via the CST parser.
|
|
2
|
+
//
|
|
3
|
+
// Locates an installed Python package in site-packages or .venv,
|
|
4
|
+
// parses its source files via the Python CST parser, and returns
|
|
5
|
+
// a map of exported function names. Used by markUsedVulnFunctions
|
|
6
|
+
// to validate that OSV-named vulnerable functions actually exist
|
|
7
|
+
// in the installed version.
|
|
8
|
+
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import { execFileSync } from 'node:child_process';
|
|
12
|
+
import { parsePythonFilesBatch, probePythonAvailable } from '../ir/parser-py-cst.js';
|
|
13
|
+
|
|
14
|
+
const VENV_DIRS = ['.venv', 'venv', '.env', 'env'];
|
|
15
|
+
|
|
16
|
+
function _findSitePackages(scanRoot) {
|
|
17
|
+
for (const vdir of VENV_DIRS) {
|
|
18
|
+
const base = path.join(scanRoot || '.', vdir);
|
|
19
|
+
if (!fs.existsSync(base)) continue;
|
|
20
|
+
const lib = path.join(base, 'lib');
|
|
21
|
+
if (!fs.existsSync(lib)) continue;
|
|
22
|
+
const pydirs = fs.readdirSync(lib).filter(d => d.startsWith('python'));
|
|
23
|
+
for (const pydir of pydirs) {
|
|
24
|
+
const sp = path.join(lib, pydir, 'site-packages');
|
|
25
|
+
if (fs.existsSync(sp)) return sp;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Fallback: ask python3 directly
|
|
29
|
+
try {
|
|
30
|
+
const out = execFileSync('python3', ['-c', 'import site; print(site.getsitepackages()[0])'], {
|
|
31
|
+
encoding: 'utf8', timeout: 5000,
|
|
32
|
+
}).trim();
|
|
33
|
+
if (out && fs.existsSync(out)) return out;
|
|
34
|
+
} catch { /* no python3 or no site-packages */ }
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function _findPackageDir(sitePackages, packageName) {
|
|
39
|
+
if (!sitePackages) return null;
|
|
40
|
+
const normalized = packageName.replace(/-/g, '_').toLowerCase();
|
|
41
|
+
const candidates = [
|
|
42
|
+
normalized,
|
|
43
|
+
packageName.toLowerCase(),
|
|
44
|
+
packageName,
|
|
45
|
+
];
|
|
46
|
+
for (const name of candidates) {
|
|
47
|
+
const dir = path.join(sitePackages, name);
|
|
48
|
+
if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) return dir;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function _readPyFilesFromDir(dir, maxFiles = 50) {
|
|
54
|
+
const entries = [];
|
|
55
|
+
try {
|
|
56
|
+
const files = fs.readdirSync(dir, { recursive: true })
|
|
57
|
+
.filter(f => f.endsWith('.py'))
|
|
58
|
+
.slice(0, maxFiles);
|
|
59
|
+
for (const f of files) {
|
|
60
|
+
const fp = path.join(dir, f);
|
|
61
|
+
try {
|
|
62
|
+
const content = fs.readFileSync(fp, 'utf8');
|
|
63
|
+
if (content.length < 1_000_000) {
|
|
64
|
+
entries.push({ file: f, content });
|
|
65
|
+
}
|
|
66
|
+
} catch { /* skip unreadable files */ }
|
|
67
|
+
}
|
|
68
|
+
} catch { /* dir not readable */ }
|
|
69
|
+
return entries;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function extractPythonPackageFunctions(packageName, scanRoot) {
|
|
73
|
+
const cap = probePythonAvailable();
|
|
74
|
+
if (!cap.ok) return null;
|
|
75
|
+
|
|
76
|
+
const sitePackages = _findSitePackages(scanRoot);
|
|
77
|
+
const pkgDir = _findPackageDir(sitePackages, packageName);
|
|
78
|
+
if (!pkgDir) return null;
|
|
79
|
+
|
|
80
|
+
const pyFiles = _readPyFilesFromDir(pkgDir);
|
|
81
|
+
if (!pyFiles.length) return null;
|
|
82
|
+
|
|
83
|
+
const batch = parsePythonFilesBatch(pyFiles);
|
|
84
|
+
if (!batch || !Array.isArray(batch)) return null;
|
|
85
|
+
|
|
86
|
+
const functionMap = new Map();
|
|
87
|
+
for (const fileIR of batch) {
|
|
88
|
+
if (!fileIR || !fileIR.functions) continue;
|
|
89
|
+
for (const fn of fileIR.functions) {
|
|
90
|
+
if (fn.name && !fn.name.startsWith('_')) {
|
|
91
|
+
functionMap.set(fn.name, {
|
|
92
|
+
file: fileIR.file,
|
|
93
|
+
line: fn.line,
|
|
94
|
+
qid: fn.qid,
|
|
95
|
+
params: fn.params,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return functionMap;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function validateOsvFunctionsExist(packageName, osvFunctions, scanRoot) {
|
|
104
|
+
if (!osvFunctions || !osvFunctions.length) return { validated: [], missing: [] };
|
|
105
|
+
const fnMap = extractPythonPackageFunctions(packageName, scanRoot);
|
|
106
|
+
if (!fnMap) return { validated: osvFunctions, missing: [] };
|
|
107
|
+
const validated = [];
|
|
108
|
+
const missing = [];
|
|
109
|
+
for (const fn of osvFunctions) {
|
|
110
|
+
const shortFn = fn.includes('.') ? fn.split('.').pop() : fn;
|
|
111
|
+
if (fnMap.has(shortFn) || fnMap.has(fn)) {
|
|
112
|
+
validated.push(shortFn);
|
|
113
|
+
} else {
|
|
114
|
+
missing.push(fn);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { validated, missing };
|
|
118
|
+
}
|