@clear-capabilities/agentic-security-scanner 0.134.0 → 0.136.2
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 +196 -0
- package/bin/agentic-security-audit.js +2 -1
- package/bin/agentic-security-consistency.js +2 -1
- package/bin/agentic-security.js +269 -44
- package/dist/113.index.js +6 -6
- package/dist/117.index.js +3 -1
- package/dist/178.index.js +1 -1
- package/dist/220.index.js +5 -3
- package/dist/238.index.js +4 -4
- package/dist/317.index.js +270 -0
- package/dist/384.index.js +1 -1
- package/dist/435.index.js +13 -7
- package/dist/513.index.js +7 -3
- package/dist/526.index.js +6 -6
- package/dist/637.index.js +1 -1
- package/dist/675.index.js +7 -5
- package/dist/839.index.js +4 -3
- package/dist/905.index.js +1173 -0
- package/dist/agentic-security.mjs +14 -14
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/dist/compliance-frameworks/ccpa.json +32 -0
- package/dist/compliance-frameworks/eu-ai-act.json +51 -0
- package/dist/compliance-frameworks/gdpr.json +45 -0
- package/dist/compliance-frameworks/hipaa-security-rule.json +56 -0
- package/dist/compliance-frameworks/nist-ai-600-1.json +51 -0
- package/dist/compliance-frameworks/nist-csf-2.json +73 -0
- package/dist/compliance-frameworks/nist-privacy-1-1.json +846 -0
- package/dist/compliance-frameworks/owasp-asvs-5.json +79 -0
- package/dist/compliance-frameworks/owasp-llm-top-10.json +69 -0
- package/package.json +9 -5
- package/src/badge.js +2 -1
- package/src/dataflow/cross-service-taint.js +2 -1
- package/src/dataflow/ifds-precise.js +6 -4
- package/src/dataflow/incremental.js +7 -5
- package/src/discovery/CLAUDE.md +10 -0
- package/src/discovery/index.js +175 -3
- package/src/discovery/llm-invoke.js +90 -1
- package/src/discovery/memory.js +163 -0
- package/src/engine.js +45 -8
- package/src/integrations/tickets.js +7 -6
- package/src/ir/ir-stats.js +1 -1
- package/src/ir/parser-cpp.js +1 -1
- package/src/leaderboard.js +3 -2
- package/src/llm-validator/consistency.js +6 -2
- package/src/llm-validator/index.js +1 -2
- package/src/mcp/audit.js +2 -1
- package/src/mcp/tools.js +4 -3
- package/src/posture/CLAUDE.md +26 -1
- package/src/posture/accuracy-scorecard.js +59 -1
- package/src/posture/agents-memory.js +5 -3
- package/src/posture/auditor-walkthrough.js +9 -7
- package/src/posture/auth-posture-import.js +5 -4
- package/src/posture/compliance-frameworks/nist-privacy-1-1.json +846 -0
- package/src/posture/compliance-policy.js +7 -9
- package/src/posture/custom-rules.js +7 -5
- package/src/posture/cve-alert-daemon.js +6 -5
- package/src/posture/dep-add-guard.js +2 -1
- package/src/posture/deterministic.js +3 -2
- package/src/posture/evidence-bundle.js +246 -0
- package/src/posture/exploitability-probability.js +2 -1
- package/src/posture/feature-flags.js +3 -2
- package/src/posture/findings-memory.js +3 -3
- package/src/posture/fix-history.js +5 -2
- package/src/posture/fix-metrics.js +5 -5
- package/src/posture/fix-plan.js +2 -1
- package/src/posture/grader-calibration.js +3 -4
- package/src/posture/intent-context.js +2 -1
- package/src/posture/learning.js +4 -3
- package/src/posture/license-attributions.js +5 -7
- package/src/posture/license-graph.js +2 -1
- package/src/posture/license-policy.js +2 -1
- package/src/posture/model-rescan.js +4 -3
- package/src/posture/network-policy-import.js +3 -2
- package/src/posture/pqc-migration-plan.js +7 -5
- package/src/posture/pr-augment.js +8 -5
- package/src/posture/privacy-framework.js +262 -0
- package/src/posture/risk-dollars.js +2 -2
- package/src/posture/router.js +5 -4
- package/src/posture/ruleset-version.js +2 -2
- package/src/posture/runtime-correlation.js +2 -1
- package/src/posture/sbom-diff.js +12 -3
- package/src/posture/sca-policy.js +7 -4
- package/src/posture/scan-checkpoint.js +15 -0
- package/src/posture/state-dir.js +34 -0
- package/src/posture/telemetry-ingest.js +4 -3
- package/src/posture/threat-model-auto.js +4 -1
- package/src/posture/threat-model-grounding.js +11 -1
- package/src/posture/time-to-fix.js +3 -2
- package/src/posture/triage-memory.js +3 -2
- package/src/posture/waf-ingest.js +6 -5
- package/src/posture/watch-mode.js +4 -3
- package/src/sast/code-injection-multilang.js +29 -0
- package/src/sca/dep-confusion.js +2 -1
|
@@ -121,7 +121,14 @@ export function buildScorecard(inputs) {
|
|
|
121
121
|
notGeneralRecall: true,
|
|
122
122
|
notGeneralFalsePositiveRate: true,
|
|
123
123
|
f1Emitted: false,
|
|
124
|
-
|
|
124
|
+
// Was: "no labelled real-world population is available". That stopped
|
|
125
|
+
// being true when bench/independent/ was built, and a stale justification
|
|
126
|
+
// is worse than none — it argues against a measurement that now exists.
|
|
127
|
+
// No F1 is emitted FOR THE CURATED CORPUS, and the reason is narrower:
|
|
128
|
+
// its `post/` fixtures are authored here to be silent, so its precision
|
|
129
|
+
// denominator describes fixture design rather than engine behaviour.
|
|
130
|
+
// F1 over the independent population IS reported, in its own section.
|
|
131
|
+
f1OmissionReason: 'the curated corpus authors both its vulnerable and its fixed fixtures, so an F1 over it would measure fixture design, not accuracy; F1 over the labelled third-party population is reported separately from bench/independent/RESULT.json',
|
|
125
132
|
},
|
|
126
133
|
corpus: {
|
|
127
134
|
measuredThisRun: true,
|
|
@@ -138,6 +145,17 @@ export function buildScorecard(inputs) {
|
|
|
138
145
|
corpusBaseline: committed.corpusBaseline
|
|
139
146
|
? { source: 'bench/cve-replay/corpus-baseline.json', generatedAt: committed.corpusBaseline.generatedAt, total: committed.corpusBaseline.total, passing: committed.corpusBaseline.passing }
|
|
140
147
|
: null,
|
|
148
|
+
independent: committed.independent
|
|
149
|
+
? {
|
|
150
|
+
source: 'bench/independent/RESULT.json',
|
|
151
|
+
measuredAt: committed.independent.measuredAt || null,
|
|
152
|
+
engineVersion: committed.independent.engineVersion || null,
|
|
153
|
+
population: committed.independent.population || null,
|
|
154
|
+
overall: committed.independent.overall || null,
|
|
155
|
+
wide: committed.independent.wide || null,
|
|
156
|
+
byLanguage: committed.independent.byLanguage || null,
|
|
157
|
+
}
|
|
158
|
+
: null,
|
|
141
159
|
proofCorpus: proof
|
|
142
160
|
? {
|
|
143
161
|
source: 'bench/proof-corpus/results/summary.json',
|
|
@@ -307,6 +325,46 @@ export function renderScorecardMarkdown(m) {
|
|
|
307
325
|
}
|
|
308
326
|
L.push('Per-file counts are in `docs/scorecard.json`.');
|
|
309
327
|
L.push('');
|
|
328
|
+
const ind = m.committedInputs.independent;
|
|
329
|
+
if (ind && ind.overall) {
|
|
330
|
+
L.push('## Independent evaluation population — the number that matters');
|
|
331
|
+
L.push('');
|
|
332
|
+
L.push('Everything above is a **regression net**: its fixtures and its labels are both');
|
|
333
|
+
L.push('written here, which is why its detection rate sits at the ceiling by');
|
|
334
|
+
L.push('construction. `bench/independent/` is the other instrument — real upstream code');
|
|
335
|
+
L.push('at the commit where a vulnerability really existed, with the CWE assigned by a');
|
|
336
|
+
L.push('public advisory database rather than by this project.');
|
|
337
|
+
L.push('');
|
|
338
|
+
L.push(`**Measured ${ind.measuredAt} on engine ${ind.engineVersion}, ` +
|
|
339
|
+
`n=${ind.population?.scoredEntries}, ${ind.population?.unscored} unscored** ` +
|
|
340
|
+
'(*committed artifact*, `' + ind.source + '` — read, not re-run: scoring takes ~32 minutes).');
|
|
341
|
+
L.push('');
|
|
342
|
+
L.push('| | Advisory-local (**the claim**) | Wide (diagnostic) |');
|
|
343
|
+
L.push('| --- | --- | --- |');
|
|
344
|
+
L.push(`| Precision | **${formatRate(ind.overall.precision?.n, ind.overall.precision?.d)}** | ${formatRate(ind.wide?.precision?.n, ind.wide?.precision?.d)} |`);
|
|
345
|
+
L.push(`| Recall | **${formatRate(ind.overall.recall?.n, ind.overall.recall?.d)}** | ${formatRate(ind.wide?.recall?.n, ind.wide?.recall?.d)} |`);
|
|
346
|
+
L.push(`| F1 | **${ind.overall.f1 === null || ind.overall.f1 === undefined ? 'n/a' : ind.overall.f1.toFixed(3)}** | ${ind.wide?.f1 === null || ind.wide?.f1 === undefined ? 'n/a' : ind.wide.f1.toFixed(3)} |`);
|
|
347
|
+
L.push('');
|
|
348
|
+
if (ind.byLanguage) {
|
|
349
|
+
L.push('| Language | n | Recall | Precision |');
|
|
350
|
+
L.push('| --- | --- | --- | --- |');
|
|
351
|
+
for (const [k, v] of Object.entries(ind.byLanguage)) {
|
|
352
|
+
L.push(`| ${k} | ${v.entries} | ${formatRate(v.recall?.n, v.recall?.d)} | ${formatRate(v.precision?.n, v.precision?.d)} |`);
|
|
353
|
+
}
|
|
354
|
+
L.push('');
|
|
355
|
+
}
|
|
356
|
+
L.push('**Quote the advisory-local column.** "Wide" scores the same scans without');
|
|
357
|
+
L.push('restricting findings to the files the advisory\'s fix commit touched — it asks');
|
|
358
|
+
L.push('only whether the CWE appeared *anywhere* in the package. Over scopes holding up');
|
|
359
|
+
L.push('to 1740 findings that is close to asking whether the codebase contains the bug');
|
|
360
|
+
L.push('class at all, a question with a much easier yes. It is kept because it is the');
|
|
361
|
+
L.push('only way to tell whether a change moved the engine or moved the benchmark.');
|
|
362
|
+
L.push('');
|
|
363
|
+
L.push('Against ~100% on the curated corpus above. **That gap is the most useful number');
|
|
364
|
+
L.push('in this document**, and publishing it is the point of the exercise. The figure');
|
|
365
|
+
L.push('went DOWN when the benchmark was corrected, and is published that way.');
|
|
366
|
+
L.push('');
|
|
367
|
+
}
|
|
310
368
|
L.push('## Committed artifacts referenced (not re-run by this command)');
|
|
311
369
|
L.push('');
|
|
312
370
|
const cb = m.committedInputs.corpusBaseline;
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
import * as fs from 'node:fs';
|
|
30
30
|
import * as path from 'node:path';
|
|
31
31
|
|
|
32
|
+
import { statePath, stateWritesEnabled } from './state-dir.js';
|
|
32
33
|
const MEMORY_FILE = '.agentic-security/AGENTS.md';
|
|
33
34
|
const ARCHIVE_FILE = '.agentic-security/AGENTS.md.archive';
|
|
34
35
|
const MAX_BYTES = 20 * 1024;
|
|
@@ -36,8 +37,8 @@ const MAX_ENTRY_BYTES = 2 * 1024;
|
|
|
36
37
|
const ARCHIVE_MAX_BYTES = 200 * 1024;
|
|
37
38
|
const HEADER = '# AGENTS.md\n\nAgent-authored continual-learning notes. Each entry: timestamp + agent name + one short paragraph. New entries appended at the bottom; oldest entries rotate to AGENTS.md.archive when this file exceeds 20 KB.\n\n';
|
|
38
39
|
|
|
39
|
-
function _resolve(scanRoot) { return
|
|
40
|
-
function _archivePath(scanRoot) { return
|
|
40
|
+
function _resolve(scanRoot) { return statePath(scanRoot, 'AGENTS.md'); }
|
|
41
|
+
function _archivePath(scanRoot) { return statePath(scanRoot, 'AGENTS.md.archive'); }
|
|
41
42
|
|
|
42
43
|
export function readAgentsMemory(scanRoot) {
|
|
43
44
|
const fp = _resolve(scanRoot);
|
|
@@ -65,7 +66,8 @@ export function appendAgentsMemory(scanRoot, { agent, body }) {
|
|
|
65
66
|
const entry = `\n## ${ts} agent: ${agent}\n\n${snippet}\n`;
|
|
66
67
|
try {
|
|
67
68
|
const fp = _resolve(scanRoot);
|
|
68
|
-
|
|
69
|
+
if (!stateWritesEnabled()) return;
|
|
70
|
+
fs.mkdirSync(path.dirname(fp), { recursive: true });
|
|
69
71
|
if (!fs.existsSync(fp)) fs.writeFileSync(fp, HEADER);
|
|
70
72
|
fs.appendFileSync(fp, entry);
|
|
71
73
|
_maybeRotate(scanRoot);
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
//
|
|
9
9
|
// nist-csf-2 NIST Cybersecurity Framework 2.0
|
|
10
10
|
// nist-ai-600-1 NIST AI Risk Management Framework, GenAI profile
|
|
11
|
+
// nist-privacy-1-1 NIST Privacy Framework 1.1 (see posture/privacy-framework.js
|
|
12
|
+
// for the assessment + remediation layer over it)
|
|
11
13
|
// owasp-asvs-5 OWASP Application Security Verification Standard 5.0
|
|
12
14
|
// owasp-llm-top-10 OWASP Top 10 for LLM Applications 2025
|
|
13
15
|
// eu-ai-act EU AI Act (Regulation 2024/1689)
|
|
@@ -32,9 +34,8 @@
|
|
|
32
34
|
import * as fs from 'node:fs';
|
|
33
35
|
import * as path from 'node:path';
|
|
34
36
|
|
|
37
|
+
import { statePath, stateWritesEnabled } from './state-dir.js';
|
|
35
38
|
const BUNDLED_DIR = path.join(path.dirname(new URL(import.meta.url).pathname), 'compliance-frameworks');
|
|
36
|
-
const STATE = '.agentic-security';
|
|
37
|
-
|
|
38
39
|
function _readJson(fp) {
|
|
39
40
|
try { return JSON.parse(fs.readFileSync(fp, 'utf8')); } catch { return null; }
|
|
40
41
|
}
|
|
@@ -52,7 +53,7 @@ export function listFrameworks(scanRoot) {
|
|
|
52
53
|
}
|
|
53
54
|
} catch {}
|
|
54
55
|
if (scanRoot) {
|
|
55
|
-
const projDir =
|
|
56
|
+
const projDir = statePath(scanRoot, 'compliance');
|
|
56
57
|
if (fs.existsSync(projDir)) {
|
|
57
58
|
try {
|
|
58
59
|
for (const sub of fs.readdirSync(projDir)) {
|
|
@@ -73,7 +74,7 @@ export function listFrameworks(scanRoot) {
|
|
|
73
74
|
*/
|
|
74
75
|
export function loadFramework(scanRoot, id) {
|
|
75
76
|
if (scanRoot) {
|
|
76
|
-
const projFp =
|
|
77
|
+
const projFp = statePath(scanRoot, 'compliance', id, 'controls.json');
|
|
77
78
|
if (fs.existsSync(projFp)) return _readJson(projFp);
|
|
78
79
|
}
|
|
79
80
|
for (const fn of fs.readdirSync(BUNDLED_DIR)) {
|
|
@@ -164,11 +165,11 @@ export function evaluateFramework(scanRoot, fw, scan) {
|
|
|
164
165
|
// A '.../' sentinel marks a source-relative artifact (project source,
|
|
165
166
|
// e.g. a hook or agent file) — resolve it against the scan root itself.
|
|
166
167
|
// Everything else is a runtime artifact under the STATE dir. Without
|
|
167
|
-
// this, `
|
|
168
|
+
// this, `statePath(scanRoot, '.../x')` never resolves and the
|
|
168
169
|
// control falsely reads "not present" for every project.
|
|
169
170
|
const resolved = !target ? null
|
|
170
171
|
: target.startsWith('.../') ? path.join(scanRoot, target.slice(4))
|
|
171
|
-
:
|
|
172
|
+
: statePath(scanRoot, target);
|
|
172
173
|
const label = target ? target.replace(/^\.\.\.\//, '') : '(unmapped)';
|
|
173
174
|
if (resolved && fs.existsSync(resolved)) {
|
|
174
175
|
obs.push(`✓ ${mod}: ${label} present.`);
|
|
@@ -251,7 +252,8 @@ export function renderWalkthrough(fw, evaluation, opts = {}) {
|
|
|
251
252
|
* Persist the walkthrough at .agentic-security/auditor-walkthroughs/<id>.md
|
|
252
253
|
*/
|
|
253
254
|
export function persistWalkthrough(scanRoot, fw, body) {
|
|
254
|
-
const dir =
|
|
255
|
+
const dir = statePath(scanRoot, 'auditor-walkthroughs');
|
|
256
|
+
if (!stateWritesEnabled()) return null;
|
|
255
257
|
try { fs.mkdirSync(dir, { recursive: true }); } catch {}
|
|
256
258
|
const fp = path.join(dir, `${fw.id}.md`);
|
|
257
259
|
try { fs.writeFileSync(fp, body); } catch {}
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
import * as fs from 'node:fs';
|
|
28
28
|
import * as path from 'node:path';
|
|
29
29
|
|
|
30
|
+
import { statePath } from './state-dir.js';
|
|
30
31
|
const KNOWN_GOOD = new Set([
|
|
31
32
|
'session+csrf', 'session+admin', 'session+claim', 'session',
|
|
32
33
|
'jwt+verify', 'oauth+pkce', 'mtls',
|
|
@@ -41,15 +42,15 @@ const FAMILIES_GATEABLE_BY_AUTH = new Set([
|
|
|
41
42
|
]);
|
|
42
43
|
|
|
43
44
|
const CANDIDATE_PATHS = [
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
45
|
+
'auth-posture.json',
|
|
46
|
+
'auth-posture.yml',
|
|
47
|
+
'auth-posture.yaml',
|
|
47
48
|
];
|
|
48
49
|
|
|
49
50
|
export function loadAuthPosture(scanRoot) {
|
|
50
51
|
const root = scanRoot || process.cwd();
|
|
51
52
|
for (const rel of CANDIDATE_PATHS) {
|
|
52
|
-
const fp =
|
|
53
|
+
const fp = statePath(root, rel);
|
|
53
54
|
if (!fs.existsSync(fp)) continue;
|
|
54
55
|
try {
|
|
55
56
|
const text = fs.readFileSync(fp, 'utf8');
|