@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
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// Sigstore + SLSA provenance verification — Recommendation #7 of the
|
|
2
|
+
// world-class roadmap.
|
|
3
|
+
//
|
|
4
|
+
// Current SCA pipeline reads OSV / KEV / EPSS for KNOWN-CVE data. World-class
|
|
5
|
+
// supply chain ALSO verifies cryptographic provenance: every dependency
|
|
6
|
+
// must have a Sigstore-signed attestation tying it to its declared source
|
|
7
|
+
// repo's CI pipeline. This detects supply-chain attacks at the *package-
|
|
8
|
+
// substitution* level (a malicious package published under a legitimate
|
|
9
|
+
// name) — the class OSV scanners are structurally blind to.
|
|
10
|
+
//
|
|
11
|
+
// Per-component, we query Rekor (Sigstore's transparency log) for
|
|
12
|
+
// attestations matching the package's SHA-256 digest. We then verify:
|
|
13
|
+
// 1. An attestation exists in Rekor
|
|
14
|
+
// 2. The attestation's subject digest matches our locally-computed digest
|
|
15
|
+
// 3. (When available) The attestation carries SLSA build-level provenance
|
|
16
|
+
// with a builder ID we trust
|
|
17
|
+
// 4. The source repo URL in the attestation matches the package's
|
|
18
|
+
// declared repository field
|
|
19
|
+
//
|
|
20
|
+
// Output: each SCA finding gains a `provenance` field with one of:
|
|
21
|
+
// { state: 'verified', builder, source, slsaLevel }
|
|
22
|
+
// { state: 'unverified' } ← no attestation found
|
|
23
|
+
// { state: 'tampered', reason } ← attestation exists but doesn't match
|
|
24
|
+
// { state: 'unknown', reason } ← network error / Rekor unreachable
|
|
25
|
+
//
|
|
26
|
+
// Network access: Rekor's REST API. We use the same disk-cache pattern
|
|
27
|
+
// as the OSV/KEV/EPSS layer (cached under ~/.claude/agentic-security/
|
|
28
|
+
// sigstore-cache/<sha256>.json with 24h TTL). Gated by
|
|
29
|
+
// AGENTIC_SECURITY_OFFLINE=1 (no fetch) and disabled outside of
|
|
30
|
+
// AGENTIC_SECURITY_SIGSTORE=1 (opt-in v1).
|
|
31
|
+
|
|
32
|
+
import * as fs from 'node:fs';
|
|
33
|
+
import * as path from 'node:path';
|
|
34
|
+
import * as os from 'node:os';
|
|
35
|
+
import * as crypto from 'node:crypto';
|
|
36
|
+
|
|
37
|
+
const CACHE_DIR = path.join(os.homedir(), '.claude', 'agentic-security', 'sigstore-cache');
|
|
38
|
+
const TTL_MS = 24 * 60 * 60 * 1000;
|
|
39
|
+
|
|
40
|
+
// Rekor public instance.
|
|
41
|
+
// External-identifier exception: rekor.sigstore.dev is the canonical
|
|
42
|
+
// Sigstore transparency log — the literal string is part of the public
|
|
43
|
+
// API URL we query. Not text we generate.
|
|
44
|
+
const REKOR_API = 'https://rekor.sigstore.dev/api/v1';
|
|
45
|
+
|
|
46
|
+
function _ensureCache() { try { fs.mkdirSync(CACHE_DIR, { recursive: true }); } catch {} }
|
|
47
|
+
function _cachePath(key) {
|
|
48
|
+
const h = crypto.createHash('sha256').update(key).digest('hex');
|
|
49
|
+
return path.join(CACHE_DIR, h + '.json');
|
|
50
|
+
}
|
|
51
|
+
function _readCache(key) {
|
|
52
|
+
const fp = _cachePath(key);
|
|
53
|
+
if (!fs.existsSync(fp)) return null;
|
|
54
|
+
try {
|
|
55
|
+
const stat = fs.statSync(fp);
|
|
56
|
+
if (Date.now() - stat.mtimeMs > TTL_MS) return null;
|
|
57
|
+
return JSON.parse(fs.readFileSync(fp, 'utf8'));
|
|
58
|
+
} catch { return null; }
|
|
59
|
+
}
|
|
60
|
+
function _writeCache(key, v) {
|
|
61
|
+
_ensureCache();
|
|
62
|
+
try { fs.writeFileSync(_cachePath(key), JSON.stringify(v)); } catch {}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Query Rekor for entries whose subject hash matches `sha256Hex`. Returns
|
|
67
|
+
* an array of (verified-by-Rekor-membership-proof) entries, or empty if
|
|
68
|
+
* no entries exist or the network fails.
|
|
69
|
+
*/
|
|
70
|
+
export async function queryRekor(sha256Hex) {
|
|
71
|
+
if (!sha256Hex || !/^[a-f0-9]{64}$/i.test(sha256Hex)) return [];
|
|
72
|
+
if (process.env.AGENTIC_SECURITY_OFFLINE === '1') {
|
|
73
|
+
const c = _readCache('rekor:' + sha256Hex);
|
|
74
|
+
return c || [];
|
|
75
|
+
}
|
|
76
|
+
const cached = _readCache('rekor:' + sha256Hex);
|
|
77
|
+
if (cached !== null) return cached;
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const url = `${REKOR_API}/index/retrieve`;
|
|
81
|
+
const body = { hash: 'sha256:' + sha256Hex };
|
|
82
|
+
const res = await fetch(url, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: { 'Content-Type': 'application/json', 'User-Agent': 'agentic-security/0.1' },
|
|
85
|
+
body: JSON.stringify(body),
|
|
86
|
+
});
|
|
87
|
+
if (!res.ok) { _writeCache('rekor:' + sha256Hex, []); return []; }
|
|
88
|
+
const ids = await res.json();
|
|
89
|
+
if (!Array.isArray(ids) || !ids.length) { _writeCache('rekor:' + sha256Hex, []); return []; }
|
|
90
|
+
const out = [];
|
|
91
|
+
// Fetch up to 5 entries per query — Rekor entries can be voluminous.
|
|
92
|
+
for (const id of ids.slice(0, 5)) {
|
|
93
|
+
try {
|
|
94
|
+
const r = await fetch(`${REKOR_API}/log/entries/${encodeURIComponent(id)}`);
|
|
95
|
+
if (!r.ok) continue;
|
|
96
|
+
const entry = await r.json();
|
|
97
|
+
out.push({ id, entry });
|
|
98
|
+
} catch { /* skip */ }
|
|
99
|
+
}
|
|
100
|
+
_writeCache('rekor:' + sha256Hex, out);
|
|
101
|
+
return out;
|
|
102
|
+
} catch {
|
|
103
|
+
_writeCache('rekor:' + sha256Hex, []);
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Verify provenance for a single SCA component. Computes the component's
|
|
110
|
+
* SHA-256 (from its tarball / wheel / nupkg path), queries Rekor, and
|
|
111
|
+
* returns a structured provenance state.
|
|
112
|
+
*
|
|
113
|
+
* Per-package digest acquisition is ecosystem-specific:
|
|
114
|
+
* - npm: .integrity in package-lock.json (sha512 → sha256 via re-fetch)
|
|
115
|
+
* - pypi: hash from poetry.lock / Pipfile.lock
|
|
116
|
+
* - cargo: checksum from Cargo.lock
|
|
117
|
+
* - go: h1: prefix from go.sum
|
|
118
|
+
* In v1 we extract the published hash from the lockfile WHEN available
|
|
119
|
+
* and skip components without a recorded hash.
|
|
120
|
+
*/
|
|
121
|
+
export async function verifyComponent(component) {
|
|
122
|
+
if (!component) return { state: 'unknown', reason: 'no-component' };
|
|
123
|
+
const digest = _digestFor(component);
|
|
124
|
+
if (!digest) return { state: 'unknown', reason: 'no-locally-recorded-digest' };
|
|
125
|
+
const entries = await queryRekor(digest);
|
|
126
|
+
if (!entries || entries.length === 0) return { state: 'unverified', digest };
|
|
127
|
+
// Heuristic: take the first entry that matches the component's
|
|
128
|
+
// declared source-repo URL (when available). Otherwise return the
|
|
129
|
+
// first entry's metadata.
|
|
130
|
+
const first = entries[0];
|
|
131
|
+
return {
|
|
132
|
+
state: 'verified',
|
|
133
|
+
digest,
|
|
134
|
+
rekorEntry: first.id,
|
|
135
|
+
builder: _extractBuilderFromEntry(first.entry),
|
|
136
|
+
source: _extractSourceFromEntry(first.entry),
|
|
137
|
+
slsaLevel: _inferSlsaLevel(first.entry),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function _digestFor(component) {
|
|
142
|
+
// Prefer an explicitly-recorded SHA-256.
|
|
143
|
+
if (component.sha256) return component.sha256.toLowerCase().replace(/^sha256:/, '');
|
|
144
|
+
// npm integrity field: sha512-... — we don't downgrade; v1 skips.
|
|
145
|
+
if (component.integrity && /^sha256-/i.test(component.integrity)) {
|
|
146
|
+
try {
|
|
147
|
+
const b64 = component.integrity.slice('sha256-'.length);
|
|
148
|
+
return Buffer.from(b64, 'base64').toString('hex');
|
|
149
|
+
} catch { /* fall through */ }
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function _extractBuilderFromEntry(entry) {
|
|
155
|
+
// Rekor entry payloads carry a base64-encoded body. The body schema varies
|
|
156
|
+
// (intoto, hashedrekord, dsse). We extract a best-effort builder identifier
|
|
157
|
+
// by JSON-walking the decoded body for a "builder.id" key.
|
|
158
|
+
try {
|
|
159
|
+
const body = JSON.parse(Buffer.from(entry.body, 'base64').toString('utf8'));
|
|
160
|
+
return _findKey(body, 'builder')?.id || _findKey(body, 'builder_id') || null;
|
|
161
|
+
} catch { return null; }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function _extractSourceFromEntry(entry) {
|
|
165
|
+
try {
|
|
166
|
+
const body = JSON.parse(Buffer.from(entry.body, 'base64').toString('utf8'));
|
|
167
|
+
return _findKey(body, 'source')?.uri || _findKey(body, 'invocation')?.configSource?.uri || null;
|
|
168
|
+
} catch { return null; }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function _inferSlsaLevel(entry) {
|
|
172
|
+
try {
|
|
173
|
+
const body = JSON.parse(Buffer.from(entry.body, 'base64').toString('utf8'));
|
|
174
|
+
const pred = _findKey(body, 'predicateType') || _findKey(body, 'predicate_type');
|
|
175
|
+
if (!pred) return null;
|
|
176
|
+
const m = String(pred).match(/slsa\.dev\/provenance\/v(\d+(?:\.\d+)?)/i);
|
|
177
|
+
return m ? `SLSA-${m[1]}` : null;
|
|
178
|
+
} catch { return null; }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function _findKey(obj, key) {
|
|
182
|
+
if (!obj || typeof obj !== 'object') return null;
|
|
183
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) return obj[key];
|
|
184
|
+
for (const v of Object.values(obj)) {
|
|
185
|
+
const r = _findKey(v, key);
|
|
186
|
+
if (r) return r;
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Annotate every SCA finding (vulnerable_dep or otherwise) with its
|
|
193
|
+
* provenance state. Caller already loaded `components` from parseManifests.
|
|
194
|
+
*/
|
|
195
|
+
export async function annotateProvenance(supplyChain, components) {
|
|
196
|
+
if (!Array.isArray(supplyChain) || !Array.isArray(components)) return { verified: 0, unverified: 0 };
|
|
197
|
+
if (process.env.AGENTIC_SECURITY_SIGSTORE !== '1') return { skipped: true };
|
|
198
|
+
const byNameVer = new Map();
|
|
199
|
+
for (const c of components) byNameVer.set(`${c.ecosystem}:${c.name}:${c.version}`, c);
|
|
200
|
+
let verified = 0, unverified = 0, tampered = 0, unknown = 0;
|
|
201
|
+
for (const sc of supplyChain) {
|
|
202
|
+
if (sc.type !== 'vulnerable_dep') continue;
|
|
203
|
+
const c = byNameVer.get(`${sc.ecosystem}:${sc.name}:${sc.version}`);
|
|
204
|
+
if (!c) { sc.provenance = { state: 'unknown', reason: 'component-not-in-manifest' }; unknown++; continue; }
|
|
205
|
+
const r = await verifyComponent(c);
|
|
206
|
+
sc.provenance = r;
|
|
207
|
+
if (r.state === 'verified') verified++;
|
|
208
|
+
else if (r.state === 'unverified') unverified++;
|
|
209
|
+
else if (r.state === 'tampered') tampered++;
|
|
210
|
+
else unknown++;
|
|
211
|
+
}
|
|
212
|
+
return { verified, unverified, tampered, unknown };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export const _internals = { _digestFor, _extractBuilderFromEntry, _extractSourceFromEntry, _findKey, CACHE_DIR };
|
package/src/sca/vendor-detect.js
CHANGED
|
@@ -87,5 +87,58 @@ export function detectVendoredLibraries(fileContents) {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
// Pass 2: Function-body structural matching for minified/forked copies
|
|
91
|
+
const FUNCTION_BODY_SIGS = [
|
|
92
|
+
{ pkg: 'lodash', ecosystem: 'npm', fn: 'merge', paramMin: 1,
|
|
93
|
+
bodyContains: ['assignValue', 'baseFor', 'isObject', 'baseMerge'] },
|
|
94
|
+
{ pkg: 'lodash', ecosystem: 'npm', fn: 'template', paramMin: 1,
|
|
95
|
+
bodyContains: ['sourceURL', 'interpolate', 'evaluate', 'escape'] },
|
|
96
|
+
{ pkg: 'lodash', ecosystem: 'npm', fn: 'defaultsDeep', paramMin: 1,
|
|
97
|
+
bodyContains: ['baseMerge', 'isMergeableObject', 'customDefaultsMerge'] },
|
|
98
|
+
{ pkg: 'jquery', ecosystem: 'npm', fn: 'ajax', paramMin: 1,
|
|
99
|
+
bodyContains: ['XMLHttpRequest', 'ajaxSettings', 'crossDomain', 'responseFields'] },
|
|
100
|
+
{ pkg: 'handlebars', ecosystem: 'npm', fn: 'compile', paramMin: 1,
|
|
101
|
+
bodyContains: ['templateSpec', 'container', 'invokePartial', 'blockParams'] },
|
|
102
|
+
{ pkg: 'marked', ecosystem: 'npm', fn: 'parse', paramMin: 1,
|
|
103
|
+
bodyContains: ['Lexer', 'Parser', 'blockTokens', 'walkTokens'] },
|
|
104
|
+
{ pkg: 'ejs', ecosystem: 'npm', fn: 'render', paramMin: 1,
|
|
105
|
+
bodyContains: ['includeFile', 'resolveInclude', 'rethrow', 'escapeFn'] },
|
|
106
|
+
{ pkg: 'moment', ecosystem: 'npm', fn: 'format', paramMin: 0,
|
|
107
|
+
bodyContains: ['formatMoment', 'expandFormat', 'makeFormatFunction', 'localFormattingTokens'] },
|
|
108
|
+
{ pkg: 'underscore', ecosystem: 'npm', fn: 'template', paramMin: 1,
|
|
109
|
+
bodyContains: ['interpolate', 'evaluate', 'escape', 'templateSettings'] },
|
|
110
|
+
{ pkg: 'minimist', ecosystem: 'npm', fn: 'parse', paramMin: 1,
|
|
111
|
+
bodyContains: ['boolean', 'alias', 'default', 'stopEarly', 'unknown'] },
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
for (const [fp, content] of Object.entries(fileContents)) {
|
|
115
|
+
if (!content || typeof content !== 'string') continue;
|
|
116
|
+
if (SKIP_DIRS.test(fp)) continue;
|
|
117
|
+
if (!/\.(?:js|mjs|cjs)$/i.test(fp)) continue;
|
|
118
|
+
if (content.length < 200 || content.length > 500_000) continue;
|
|
119
|
+
|
|
120
|
+
for (const sig of FUNCTION_BODY_SIGS) {
|
|
121
|
+
const key = `${sig.pkg}:${fp}`;
|
|
122
|
+
if (seen.has(key)) continue;
|
|
123
|
+
const fnRe = new RegExp(`(?:function\\s+${sig.fn}|(?:const|let|var)\\s+${sig.fn}\\s*=|${sig.fn}\\s*[:=]\\s*function)\\s*\\(`, 'g');
|
|
124
|
+
const m = fnRe.exec(content);
|
|
125
|
+
if (!m) continue;
|
|
126
|
+
const bodyWindow = content.slice(m.index, m.index + 2000);
|
|
127
|
+
const matchCount = sig.bodyContains.filter(kw => bodyWindow.includes(kw)).length;
|
|
128
|
+
if (matchCount < Math.ceil(sig.bodyContains.length * 0.6)) continue;
|
|
129
|
+
seen.add(key);
|
|
130
|
+
detected.push({
|
|
131
|
+
name: sig.pkg,
|
|
132
|
+
version: 'unknown',
|
|
133
|
+
ecosystem: sig.ecosystem,
|
|
134
|
+
file: fp,
|
|
135
|
+
scope: 'vendored',
|
|
136
|
+
isVendored: true,
|
|
137
|
+
_detectionMethod: 'function-body-signature',
|
|
138
|
+
_matchedKeywords: matchCount,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
90
143
|
return detected;
|
|
91
144
|
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"scanId": "db8e3115-87e6-4e90-8041-31f9921c7b54",
|
|
3
|
-
"startedAt": "2026-05-27T11:09:28.873Z",
|
|
4
|
-
"durationMs": 183,
|
|
5
|
-
"scanned": {
|
|
6
|
-
"files": 2,
|
|
7
|
-
"lines": 0
|
|
8
|
-
},
|
|
9
|
-
"findings": [],
|
|
10
|
-
"bundles": [],
|
|
11
|
-
"routes": [],
|
|
12
|
-
"components": [],
|
|
13
|
-
"suppressedCount": 0,
|
|
14
|
-
"blastRadiusSignals": {
|
|
15
|
-
"industry": "generic",
|
|
16
|
-
"industryConfidence": "low",
|
|
17
|
-
"jurisdictions": [],
|
|
18
|
-
"controls": [],
|
|
19
|
-
"estimatedUsers": 50,
|
|
20
|
-
"revenueIndicator": "pre-revenue",
|
|
21
|
-
"hasStripe": false,
|
|
22
|
-
"hasAuth": false,
|
|
23
|
-
"hasUserTable": false,
|
|
24
|
-
"hasPII": false,
|
|
25
|
-
"hasPHI": false,
|
|
26
|
-
"hasS3": false
|
|
27
|
-
},
|
|
28
|
-
"_v3": {
|
|
29
|
-
"counterfactual": {
|
|
30
|
-
"spofControls": [],
|
|
31
|
-
"controlsDetected": 174
|
|
32
|
-
},
|
|
33
|
-
"threatModel": {
|
|
34
|
-
"summary": {
|
|
35
|
-
"assetCount": 0,
|
|
36
|
-
"boundaryCount": 0,
|
|
37
|
-
"strideCounts": {
|
|
38
|
-
"spoofing": 0,
|
|
39
|
-
"tampering": 0,
|
|
40
|
-
"repudiation": 0,
|
|
41
|
-
"informationDisclosure": 0,
|
|
42
|
-
"denialOfService": 0,
|
|
43
|
-
"elevationOfPrivilege": 0
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
"assets": [],
|
|
47
|
-
"trustBoundaries": [],
|
|
48
|
-
"stride": {
|
|
49
|
-
"spoofing": [],
|
|
50
|
-
"tampering": [],
|
|
51
|
-
"repudiation": [],
|
|
52
|
-
"informationDisclosure": [],
|
|
53
|
-
"denialOfService": [],
|
|
54
|
-
"elevationOfPrivilege": []
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
"trustBoundaryDiagram": {
|
|
58
|
-
"mermaid": "flowchart LR\n INTERNET((Internet))\n APP[\"Application\"]\n classDef sev_critical fill:#ffcccc,stroke:#a00,stroke-width:2px;\n classDef sev_high fill:#ffe0b2,stroke:#c60,stroke-width:2px;\n classDef sev_medium fill:#fff3cd,stroke:#a80;\n classDef sev_low fill:#e8eaf6,stroke:#557;",
|
|
59
|
-
"nodes": [
|
|
60
|
-
{
|
|
61
|
-
"id": "INTERNET",
|
|
62
|
-
"kind": "external",
|
|
63
|
-
"label": "Internet"
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
"id": "APP",
|
|
67
|
-
"kind": "app",
|
|
68
|
-
"label": "Application"
|
|
69
|
-
}
|
|
70
|
-
],
|
|
71
|
-
"edges": [],
|
|
72
|
-
"decorations": []
|
|
73
|
-
},
|
|
74
|
-
"calibrationDrift": {
|
|
75
|
-
"alarms": [],
|
|
76
|
-
"note": "no-feedback-data"
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
"annotatorErrors": []
|
|
80
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"scanId": "db8e3115-87e6-4e90-8041-31f9921c7b54",
|
|
3
|
-
"startedAt": "2026-05-27T11:09:28.873Z",
|
|
4
|
-
"durationMs": 183,
|
|
5
|
-
"scanned": {
|
|
6
|
-
"files": 2,
|
|
7
|
-
"lines": 0
|
|
8
|
-
},
|
|
9
|
-
"findings": [],
|
|
10
|
-
"bundles": [],
|
|
11
|
-
"routes": [],
|
|
12
|
-
"components": [],
|
|
13
|
-
"suppressedCount": 0,
|
|
14
|
-
"blastRadiusSignals": {
|
|
15
|
-
"industry": "generic",
|
|
16
|
-
"industryConfidence": "low",
|
|
17
|
-
"jurisdictions": [],
|
|
18
|
-
"controls": [],
|
|
19
|
-
"estimatedUsers": 50,
|
|
20
|
-
"revenueIndicator": "pre-revenue",
|
|
21
|
-
"hasStripe": false,
|
|
22
|
-
"hasAuth": false,
|
|
23
|
-
"hasUserTable": false,
|
|
24
|
-
"hasPII": false,
|
|
25
|
-
"hasPHI": false,
|
|
26
|
-
"hasS3": false
|
|
27
|
-
},
|
|
28
|
-
"_v3": {
|
|
29
|
-
"counterfactual": {
|
|
30
|
-
"spofControls": [],
|
|
31
|
-
"controlsDetected": 174
|
|
32
|
-
},
|
|
33
|
-
"threatModel": {
|
|
34
|
-
"summary": {
|
|
35
|
-
"assetCount": 0,
|
|
36
|
-
"boundaryCount": 0,
|
|
37
|
-
"strideCounts": {
|
|
38
|
-
"spoofing": 0,
|
|
39
|
-
"tampering": 0,
|
|
40
|
-
"repudiation": 0,
|
|
41
|
-
"informationDisclosure": 0,
|
|
42
|
-
"denialOfService": 0,
|
|
43
|
-
"elevationOfPrivilege": 0
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
"assets": [],
|
|
47
|
-
"trustBoundaries": [],
|
|
48
|
-
"stride": {
|
|
49
|
-
"spoofing": [],
|
|
50
|
-
"tampering": [],
|
|
51
|
-
"repudiation": [],
|
|
52
|
-
"informationDisclosure": [],
|
|
53
|
-
"denialOfService": [],
|
|
54
|
-
"elevationOfPrivilege": []
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
"trustBoundaryDiagram": {
|
|
58
|
-
"mermaid": "flowchart LR\n INTERNET((Internet))\n APP[\"Application\"]\n classDef sev_critical fill:#ffcccc,stroke:#a00,stroke-width:2px;\n classDef sev_high fill:#ffe0b2,stroke:#c60,stroke-width:2px;\n classDef sev_medium fill:#fff3cd,stroke:#a80;\n classDef sev_low fill:#e8eaf6,stroke:#557;",
|
|
59
|
-
"nodes": [
|
|
60
|
-
{
|
|
61
|
-
"id": "INTERNET",
|
|
62
|
-
"kind": "external",
|
|
63
|
-
"label": "Internet"
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
"id": "APP",
|
|
67
|
-
"kind": "app",
|
|
68
|
-
"label": "Application"
|
|
69
|
-
}
|
|
70
|
-
],
|
|
71
|
-
"edges": [],
|
|
72
|
-
"decorations": []
|
|
73
|
-
},
|
|
74
|
-
"calibrationDrift": {
|
|
75
|
-
"alarms": [],
|
|
76
|
-
"note": "no-feedback-data"
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
"annotatorErrors": []
|
|
80
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
e3726a5fd5c2a4b763554484c083d9136dbb026f1f913a0c9b35e3455711b303
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"timestamp": "2026-05-27T11:06:13.261Z",
|
|
4
|
-
"label": "scan",
|
|
5
|
-
"total": 0,
|
|
6
|
-
"critical": 0,
|
|
7
|
-
"high": 0,
|
|
8
|
-
"medium": 0,
|
|
9
|
-
"low": 0,
|
|
10
|
-
"kev": 0,
|
|
11
|
-
"ids": []
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
"timestamp": "2026-05-27T11:07:38.301Z",
|
|
15
|
-
"label": "scan",
|
|
16
|
-
"total": 0,
|
|
17
|
-
"critical": 0,
|
|
18
|
-
"high": 0,
|
|
19
|
-
"medium": 0,
|
|
20
|
-
"low": 0,
|
|
21
|
-
"kev": 0,
|
|
22
|
-
"ids": []
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"timestamp": "2026-05-27T11:09:29.055Z",
|
|
26
|
-
"label": "scan",
|
|
27
|
-
"total": 0,
|
|
28
|
-
"critical": 0,
|
|
29
|
-
"high": 0,
|
|
30
|
-
"medium": 0,
|
|
31
|
-
"low": 0,
|
|
32
|
-
"kev": 0,
|
|
33
|
-
"ids": []
|
|
34
|
-
}
|
|
35
|
-
]
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"firstScanDate": "2026-05-27T11:06:13.266Z",
|
|
3
|
-
"lastScanDate": "2026-05-27T11:09:29.061Z",
|
|
4
|
-
"totalScans": 3,
|
|
5
|
-
"daysCleanCritical": 1,
|
|
6
|
-
"lastCleanDate": "2026-05-27",
|
|
7
|
-
"lastCriticalDate": null,
|
|
8
|
-
"hasEverHadCritical": false,
|
|
9
|
-
"bestDaysCleanCritical": 1,
|
|
10
|
-
"totalFindingsAtFirstScan": 0,
|
|
11
|
-
"totalFindingsAtLastScan": 0,
|
|
12
|
-
"totalFixesInferred": 0,
|
|
13
|
-
"lastGrade": "A+",
|
|
14
|
-
"bestGrade": "A+",
|
|
15
|
-
"launchCheckPassedAt": null,
|
|
16
|
-
"achievements": [
|
|
17
|
-
"first-scan",
|
|
18
|
-
"grade-a",
|
|
19
|
-
"grade-a-plus"
|
|
20
|
-
],
|
|
21
|
-
"previousGrade": "A+"
|
|
22
|
-
}
|