@clear-capabilities/agentic-security-scanner 0.136.2 → 0.136.9
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 +236 -0
- package/bin/agentic-security.js +186 -37
- package/dist/113.index.js +13 -4
- package/dist/178.index.js +1 -1
- package/dist/207.index.js +5 -4
- package/dist/238.index.js +1 -1
- package/dist/317.index.js +36 -6
- package/dist/384.index.js +1 -1
- package/dist/435.index.js +183 -14
- package/dist/444.index.js +20 -11
- package/dist/449.index.js +8 -1
- package/dist/526.index.js +3 -3
- package/dist/637.index.js +1 -1
- package/dist/agentic-security.mjs +14 -14
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/dist/compliance-frameworks/nist-privacy-1-1.json +2 -2
- package/dist/compliance-frameworks/owasp-asvs-5.json +1 -1
- package/package.json +18 -10
- package/src/dataflow/CLAUDE.md +10 -4
- package/src/dataflow/builtin-summaries.js +1 -1
- package/src/dataflow/engine.js +324 -60
- package/src/dataflow/implicit-flow.js +68 -36
- package/src/dataflow/incremental.js +18 -3
- package/src/dataflow/index.js +2 -1
- package/src/dataflow/proven-clean.js +41 -0
- package/src/dataflow/sanitizer-gate.js +35 -9
- package/src/dataflow/sanitizer-proof.js +21 -3
- package/src/dataflow/stub-aware-filter.js +36 -13
- package/src/dataflow/summaries.js +21 -2
- package/src/engine.js +202 -42
- package/src/ir/CLAUDE.md +4 -1
- package/src/ir/balanced-call.js +55 -0
- package/src/ir/parser-cs.js +62 -9
- package/src/ir/parser-go.js +29 -11
- package/src/ir/parser-java.js +96 -19
- package/src/ir/parser-js.js +151 -20
- package/src/ir/parser-php.js +44 -9
- package/src/ir/parser-rb.js +37 -7
- package/src/ir/ssa.js +6 -1
- package/src/lsp/server.js +28 -2
- package/src/mcp/CLAUDE.md +9 -2
- package/src/mcp/redact.js +26 -0
- package/src/mcp/tools.js +155 -14
- package/src/posture/CLAUDE.md +19 -7
- package/src/posture/accuracy-scorecard.js +9 -1
- package/src/posture/aibom.js +12 -8
- package/src/posture/auditor-walkthrough.js +102 -3
- package/src/posture/autopilot.js +8 -1
- package/src/posture/calibration-drift.js +11 -5
- package/src/posture/calibration.js +24 -2
- package/src/posture/compliance-frameworks/nist-privacy-1-1.json +2 -2
- package/src/posture/compliance-frameworks/owasp-asvs-5.json +1 -1
- package/src/posture/compliance-policy.js +33 -1
- package/src/posture/confidence.js +44 -10
- package/src/posture/corpus-enroll.js +9 -5
- package/src/posture/corpus-match.js +19 -0
- package/src/posture/csharp-analysis.js +62 -3
- package/src/posture/deploy-platform.js +4 -1
- package/src/posture/drift.js +7 -1
- package/src/posture/epss.js +13 -1
- package/src/posture/evidence-bundle.js +36 -6
- package/src/posture/exploitability-probability.js +13 -1
- package/src/posture/falsification.js +23 -2
- package/src/posture/fix-metrics.js +1 -1
- package/src/posture/fix-verify-loop.js +10 -1
- package/src/posture/iac-reachability.js +14 -8
- package/src/posture/integrity.js +25 -7
- package/src/posture/model-rescan.js +65 -0
- package/src/posture/mttr.js +5 -0
- package/src/posture/poc-inprocess.js +27 -8
- package/src/posture/regression-test-gen.js +23 -8
- package/src/posture/reverse-blast-radius.js +5 -1
- package/src/posture/risk-dollars.js +18 -1
- package/src/posture/secret-history.js +20 -11
- package/src/posture/security-trend.js +7 -1
- package/src/posture/stack-playbook.js +22 -1
- package/src/posture/threat-model-grounding.js +2 -2
- package/src/posture/validator-metrics.js +10 -3
- package/src/posture/verifier.js +32 -57
- package/src/report/index.js +183 -14
- package/src/runScan.js +1 -1
- package/src/sast/_comment-strip.js +15 -4
- package/src/sast/_secret-entropy.js +1 -1
- package/src/sast/authz.js +6 -4
- package/src/sast/bench-shape/index.js +2 -7
- package/src/sast/claude-md-prompt-injection.js +14 -3
- package/src/sast/cloud-iam.js +60 -7
- package/src/sast/cpp-bench-extras.js +1 -1
- package/src/sast/csrf.js +7 -5
- package/src/sast/env-hygiene.js +5 -2
- package/src/sast/iac-terraform.js +25 -0
- package/src/sast/java-bench-extras.js +1 -1
- package/src/sast/java-constant-fold.js +5 -5
- package/src/sast/llm-owasp.js +4 -2
- package/src/sast/mcp-audit.js +7 -0
- package/src/sast/pipeline.js +8 -0
- package/src/sast/prompt-template.js +8 -6
- package/src/sast/prototype-pollution.js +6 -2
- package/src/sast/redos-nfa.js +6 -6
- package/src/sast/secret-concat.js +13 -2
- package/src/sast/ssrf-cloud-metadata.js +6 -3
- package/src/sast/xss-reflected-multilang.js +1 -1
- package/src/sast/xxe.js +1 -1
- package/src/sca/CLAUDE.md +3 -4
- package/src/sca/container.js +35 -3
- package/src/sca/dep-confusion.js +7 -0
- package/src/sca/sarif-ingest.js +0 -187
package/src/sca/sarif-ingest.js
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
// 0.7.0 Feat-7: SARIF 2.1.0 ingest — normalizes and merges external scanner findings into unified report.
|
|
2
|
-
//
|
|
3
|
-
// Reads SARIF 2.1.0 from gitleaks / Semgrep / Bandit / Trivy / Checkov / SonarQube
|
|
4
|
-
// and merges into our scan. Findings are normalised + deduplicated against our
|
|
5
|
-
// own findings via a fingerprint of (CWE/CVE, file, line ±2, rule), and a
|
|
6
|
-
// `sources[]` array is added to the merged finding so attribution is preserved.
|
|
7
|
-
//
|
|
8
|
-
// Pure ESM, no external deps. Reads JSON only — no schema validation library.
|
|
9
|
-
|
|
10
|
-
import * as fs from 'node:fs';
|
|
11
|
-
import * as crypto from 'node:crypto';
|
|
12
|
-
|
|
13
|
-
// Tool-name → (kind, defaultSeverityIfUnset)
|
|
14
|
-
const TOOL_PROFILE = {
|
|
15
|
-
'semgrep': { kind: 'sast', defSev: 'medium' },
|
|
16
|
-
'opengrep': { kind: 'sast', defSev: 'medium' },
|
|
17
|
-
'bandit': { kind: 'sast', defSev: 'medium' },
|
|
18
|
-
'eslint': { kind: 'sast', defSev: 'low' },
|
|
19
|
-
'sonarqube': { kind: 'sast', defSev: 'medium' },
|
|
20
|
-
'codeql': { kind: 'sast', defSev: 'high' },
|
|
21
|
-
'gitleaks': { kind: 'secret', defSev: 'high' },
|
|
22
|
-
'trufflehog': { kind: 'secret', defSev: 'high' },
|
|
23
|
-
'detect-secrets':{ kind: 'secret', defSev: 'high' },
|
|
24
|
-
'trivy': { kind: 'sca', defSev: 'high' },
|
|
25
|
-
'grype': { kind: 'sca', defSev: 'high' },
|
|
26
|
-
'osv-scanner': { kind: 'sca', defSev: 'high' },
|
|
27
|
-
'checkov': { kind: 'iac', defSev: 'medium' },
|
|
28
|
-
'tfsec': { kind: 'iac', defSev: 'medium' },
|
|
29
|
-
'kics': { kind: 'iac', defSev: 'medium' },
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// SARIF level → our severity tier
|
|
33
|
-
const LEVEL_MAP = { error: 'critical', warning: 'high', note: 'medium', none: 'low' };
|
|
34
|
-
|
|
35
|
-
// Normalize a tool name into a TOOL_PROFILE key (case + version-agnostic)
|
|
36
|
-
function _toolKey(rawName) {
|
|
37
|
-
if (!rawName) return null;
|
|
38
|
-
const n = String(rawName).toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
39
|
-
for (const k of Object.keys(TOOL_PROFILE)) if (n.includes(k)) return k;
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function _fingerprint(file, line, vuln, cwe) {
|
|
44
|
-
// Bucketed line (±2) so that off-by-one rule reports merge correctly.
|
|
45
|
-
const bucket = Math.floor((line || 0) / 2) * 2;
|
|
46
|
-
const key = `${file}:${bucket}:${(cwe || '').toUpperCase()}:${(vuln || '').replace(/\W+/g, '_').toLowerCase()}`;
|
|
47
|
-
return crypto.createHash('sha256').update(key).digest('hex').slice(0, 16);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function _extractCWE(rule) {
|
|
51
|
-
// SARIF rules can carry CWE in `properties.tags`, `properties.cwe`, or `helpUri`.
|
|
52
|
-
const tags = rule?.properties?.tags || [];
|
|
53
|
-
for (const t of tags) {
|
|
54
|
-
const m = String(t).match(/CWE-?(\d+)/i);
|
|
55
|
-
if (m) return `CWE-${m[1]}`;
|
|
56
|
-
}
|
|
57
|
-
if (rule?.properties?.cwe) {
|
|
58
|
-
const m = String(rule.properties.cwe).match(/(\d+)/);
|
|
59
|
-
if (m) return `CWE-${m[1]}`;
|
|
60
|
-
}
|
|
61
|
-
if (rule?.helpUri) {
|
|
62
|
-
const m = rule.helpUri.match(/cwe\.mitre\.org\/data\/definitions\/(\d+)/i);
|
|
63
|
-
if (m) return `CWE-${m[1]}`;
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function _extractCVE(result) {
|
|
69
|
-
// Trivy / Grype / osv-scanner put CVE in ruleId or properties.
|
|
70
|
-
const id = result?.ruleId || '';
|
|
71
|
-
const m = id.match(/(CVE-\d{4}-\d{4,7})/i);
|
|
72
|
-
if (m) return m[1].toUpperCase();
|
|
73
|
-
const tags = result?.properties?.tags || [];
|
|
74
|
-
for (const t of tags) {
|
|
75
|
-
const tm = String(t).match(/(CVE-\d{4}-\d{4,7})/i);
|
|
76
|
-
if (tm) return tm[1].toUpperCase();
|
|
77
|
-
}
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Convert a single SARIF result + its run context into our normalized finding.
|
|
82
|
-
function _resultToFinding(result, run, toolKey, defSev) {
|
|
83
|
-
const loc = result.locations?.[0]?.physicalLocation;
|
|
84
|
-
const file = loc?.artifactLocation?.uri || result?.fingerprints?.path || '(unknown)';
|
|
85
|
-
const line = loc?.region?.startLine || 0;
|
|
86
|
-
const ruleId = result.ruleId || '';
|
|
87
|
-
const rule = (run.tool?.driver?.rules || []).find(r => r.id === ruleId);
|
|
88
|
-
const message = result.message?.text || rule?.fullDescription?.text || rule?.shortDescription?.text || ruleId;
|
|
89
|
-
const sev = LEVEL_MAP[result.level] || defSev;
|
|
90
|
-
const cwe = _extractCWE(rule || result);
|
|
91
|
-
const cve = _extractCVE(result);
|
|
92
|
-
const profile = TOOL_PROFILE[toolKey] || { kind: 'sast', defSev: 'medium' };
|
|
93
|
-
return {
|
|
94
|
-
file: file.replace(/^file:\/\//, ''),
|
|
95
|
-
line,
|
|
96
|
-
severity: sev,
|
|
97
|
-
vuln: cve ? `${cve}: ${message}`.slice(0, 240) : message.slice(0, 240),
|
|
98
|
-
cwe,
|
|
99
|
-
cve,
|
|
100
|
-
kind: profile.kind,
|
|
101
|
-
ruleId,
|
|
102
|
-
snippet: result.locations?.[0]?.physicalLocation?.contextRegion?.snippet?.text
|
|
103
|
-
|| result.locations?.[0]?.physicalLocation?.region?.snippet?.text
|
|
104
|
-
|| '',
|
|
105
|
-
sources: [run.tool?.driver?.name || toolKey],
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Load and parse a SARIF file. Returns an array of normalized findings.
|
|
110
|
-
export function ingestSARIFFile(filePath) {
|
|
111
|
-
let raw;
|
|
112
|
-
try { raw = fs.readFileSync(filePath, 'utf8'); }
|
|
113
|
-
catch (e) { throw new Error(`Cannot read SARIF file ${filePath}: ${e.message}`); }
|
|
114
|
-
let doc;
|
|
115
|
-
try { doc = JSON.parse(raw); }
|
|
116
|
-
catch (e) { throw new Error(`Invalid JSON in SARIF file ${filePath}: ${e.message}`); }
|
|
117
|
-
if (!doc || !Array.isArray(doc.runs)) {
|
|
118
|
-
throw new Error(`Not a SARIF document (missing runs[]): ${filePath}`);
|
|
119
|
-
}
|
|
120
|
-
const out = [];
|
|
121
|
-
for (const run of doc.runs) {
|
|
122
|
-
const toolName = run.tool?.driver?.name;
|
|
123
|
-
const toolKey = _toolKey(toolName);
|
|
124
|
-
const profile = TOOL_PROFILE[toolKey] || { kind: 'sast', defSev: 'medium' };
|
|
125
|
-
for (const result of (run.results || [])) {
|
|
126
|
-
try { out.push(_resultToFinding(result, run, toolKey || (toolName||'').toLowerCase(), profile.defSev)); }
|
|
127
|
-
catch (_) {}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return out;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Merge an array of external findings into the scan result. Mutates `scan` in place.
|
|
134
|
-
// Behaviour:
|
|
135
|
-
// - Findings whose fingerprint matches an existing scan.findings entry have their
|
|
136
|
-
// source tool name appended to scan.findings[i].sources[] and (if the external
|
|
137
|
-
// finding's severity is higher) the existing severity is bumped.
|
|
138
|
-
// - New findings (no match) are appended to scan.findings, scan.secrets, or
|
|
139
|
-
// scan.supplyChain depending on `kind`.
|
|
140
|
-
export function mergeSARIFFindings(scan, externals) {
|
|
141
|
-
// Index existing findings by fingerprint
|
|
142
|
-
const existingFP = new Map();
|
|
143
|
-
for (const f of (scan.findings || [])) {
|
|
144
|
-
const fp = _fingerprint((f.file||'').split(' -> ').pop(), f.line||f.source?.line||f.sink?.line||0, f.vuln||f.type, f.cwe);
|
|
145
|
-
existingFP.set(fp, f);
|
|
146
|
-
}
|
|
147
|
-
let merged = 0, added = 0;
|
|
148
|
-
const SEV_RANK = { critical: 4, high: 3, medium: 2, low: 1, info: 0 };
|
|
149
|
-
for (const ext of externals) {
|
|
150
|
-
const fp = _fingerprint(ext.file, ext.line, ext.vuln, ext.cwe);
|
|
151
|
-
const existing = existingFP.get(fp);
|
|
152
|
-
if (existing) {
|
|
153
|
-
existing.sources = Array.from(new Set([...(existing.sources || ['agentic-security']), ...ext.sources]));
|
|
154
|
-
// Bump severity if the external tool reported higher
|
|
155
|
-
if ((SEV_RANK[ext.severity] || 0) > (SEV_RANK[existing.severity] || 0)) {
|
|
156
|
-
existing.severity = ext.severity;
|
|
157
|
-
}
|
|
158
|
-
merged++;
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
// Append as new finding into the right bucket
|
|
162
|
-
const id = `sarif-ingest:${fp}`;
|
|
163
|
-
const newFinding = {
|
|
164
|
-
id, kind: ext.kind, severity: ext.severity, vuln: ext.vuln,
|
|
165
|
-
cwe: ext.cwe, file: ext.file, line: ext.line, snippet: ext.snippet || '',
|
|
166
|
-
sources: ext.sources, parser: 'SARIF',
|
|
167
|
-
};
|
|
168
|
-
if (ext.kind === 'secret') (scan.secrets = scan.secrets || []).push(newFinding);
|
|
169
|
-
else if (ext.kind === 'sca') (scan.supplyChain = scan.supplyChain || []).push({ ...newFinding, type: 'vulnerable_dep' });
|
|
170
|
-
else if (ext.kind === 'iac' || ext.kind === 'sast' || ext.kind === 'logic') (scan.findings = scan.findings || []).push(newFinding);
|
|
171
|
-
added++;
|
|
172
|
-
}
|
|
173
|
-
return { merged, added };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export function ingestAndMerge(scan, sarifPaths) {
|
|
177
|
-
let totalMerged = 0, totalAdded = 0;
|
|
178
|
-
for (const p of sarifPaths) {
|
|
179
|
-
let externals;
|
|
180
|
-
try { externals = ingestSARIFFile(p); }
|
|
181
|
-
catch (e) { /* swallow per-file errors so one bad SARIF doesn't break the run */ continue; }
|
|
182
|
-
const { merged, added } = mergeSARIFFindings(scan, externals);
|
|
183
|
-
totalMerged += merged;
|
|
184
|
-
totalAdded += added;
|
|
185
|
-
}
|
|
186
|
-
return { merged: totalMerged, added: totalAdded };
|
|
187
|
-
}
|