@clear-capabilities/agentic-security-scanner 0.148.1 → 0.148.4

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/bin/agentic-security.js +5 -1
  3. package/dist/4970.index.js +108 -2
  4. package/dist/agentic-security.mjs +2 -2
  5. package/dist/agentic-security.mjs.sha256 +1 -1
  6. package/dist/compliance-frameworks/ccpa.json +2 -0
  7. package/dist/compliance-frameworks/eu-ai-act.json +12 -16
  8. package/dist/compliance-frameworks/gdpr.json +8 -6
  9. package/dist/compliance-frameworks/hipaa-security-rule.json +9 -9
  10. package/dist/compliance-frameworks/nist-800-171-r3.json +6 -5
  11. package/dist/compliance-frameworks/nist-ai-600-1.json +6 -4
  12. package/dist/compliance-frameworks/nist-csf-2.json +6 -5
  13. package/dist/compliance-frameworks/nist-privacy-1-1.json +20 -10
  14. package/dist/compliance-frameworks/owasp-asvs-5.json +2 -0
  15. package/dist/compliance-frameworks/owasp-llm-top-10.json +10 -11
  16. package/package.json +4 -3
  17. package/src/engine.js +46 -14
  18. package/src/pipeline/assurance-mode.js +108 -2
  19. package/src/posture/accuracy-scorecard.js +34 -0
  20. package/src/posture/aibom.js +22 -0
  21. package/src/posture/artifact-registry.js +2 -0
  22. package/src/posture/auditor-walkthrough.js +91 -35
  23. package/src/posture/compliance-frameworks/ccpa.json +2 -0
  24. package/src/posture/compliance-frameworks/eu-ai-act.json +12 -16
  25. package/src/posture/compliance-frameworks/gdpr.json +8 -6
  26. package/src/posture/compliance-frameworks/hipaa-security-rule.json +9 -9
  27. package/src/posture/compliance-frameworks/nist-800-171-r3.json +6 -5
  28. package/src/posture/compliance-frameworks/nist-ai-600-1.json +6 -4
  29. package/src/posture/compliance-frameworks/nist-csf-2.json +6 -5
  30. package/src/posture/compliance-frameworks/nist-privacy-1-1.json +20 -10
  31. package/src/posture/compliance-frameworks/owasp-asvs-5.json +2 -0
  32. package/src/posture/compliance-frameworks/owasp-llm-top-10.json +10 -11
  33. package/src/posture/verifier.js +70 -0
@@ -4,8 +4,10 @@
4
4
  "publisher": "NIST",
5
5
  "license": "public-domain (US Federal publication)",
6
6
  "url": "https://www.nist.gov/cyberframework",
7
+ "sourceVerifiedAt": "2026-09-07",
8
+ "sourceVerificationNote": "Verified 2026-09-07 (WebFetch): confirmed NIST Cybersecurity Framework, CSF 2.0 stated as current throughout the page, matching this bundle. No content hash pinned: this is a resource hub page (quick-start guides, profiles, informative references, a downloadable PDF), not a single fixed document.",
7
9
  "scope": "SELECTIVE SUBSET. 8 subcategories with a code-observable signal. CSF 2.0 has over 100 subcategories; GOVERN in particular is organisational and is NOT represented here.",
8
- "controlsDigest": "bcff46f53e75f501",
10
+ "controlsDigest": "1df76b5bde1d0044",
9
11
  "controlCount": 8,
10
12
  "controls": [
11
13
  {
@@ -45,12 +47,11 @@
45
47
  "summary": "Vulnerabilities and threats are identified.",
46
48
  "codeTestable": "partial",
47
49
  "evidence": [
48
- "Regular scan history under .agentic-security/scan-history/.",
49
- "ATT&CK / ATLAS technique mapping on findings."
50
+ "ATT&CK / ATLAS technique mapping on findings (last-scan.json).",
51
+ "Removed module:exploitability-probability (adversarial premortem Q2, 2026-09-07): that name is not in the ARTIFACT vocabulary table — exploitability-probability.js is a real, live annotator (posture/exploitability-probability.js), but it sets fields directly on findings in memory and writes no standalone artifact of its own; its output lands in last-scan.json, the exact file the surviving module:attack-taxonomy leg on this control already checks. This mapping could never read 'present' via this leg, on any project, since it shipped — removed rather than pointed at a duplicate check with no new signal."
50
52
  ],
51
53
  "mapsTo": [
52
- "module:attack-taxonomy",
53
- "module:exploitability-probability"
54
+ "module:attack-taxonomy"
54
55
  ]
55
56
  },
56
57
  {
@@ -4,9 +4,9 @@
4
4
  "publisher": "NIST",
5
5
  "license": "public-domain (US Federal publication)",
6
6
  "url": "https://www.nist.gov/privacy-framework",
7
- "note": "Each control carries NIST's own `codeTestable` rating (yes/partial/no). That rating says whether a control COULD be assessed from source code it does not say this engine assesses it. `mapsTo` is present only where this engine produces a real signal; a control that NIST rates testable but which this engine cannot see is reported as an ENGINE GAP rather than silently passed.",
8
- "scope": "FULL CATALOGUE, partially assessable. All 104 controls are present and each carries `codeTestable`. 48 are governance controls no scanner can assess; those report `manual`, never `satisfied`. See posture/privacy-framework.js.",
9
- "controlsDigest": "447154af306c58d3",
7
+ "note": "Each control carries a `codeTestable` rating (yes/partial/no). This rating is HAND-AUTHORED BY THIS PROJECT, not NIST: the Privacy Framework document ships no machine-readable testability column (unlike NIST AI 600-1, whose source workbook has one, read by scripts/nist-compliance/build-catalog.py). The rating says whether a control COULD be assessed from source code, in this project's judgment, it does not say this engine assesses it, and it is not NIST's own claim. `mapsTo` is present only where this engine produces a real signal; a control rated testable but which this engine cannot see is reported as an ENGINE GAP rather than silently passed.",
8
+ "scope": "FULL CATALOGUE, partially assessable, transcribed from an Initial Public Draft (IPD) NOT yet finalized by NIST as of the last verification (see sourceVerificationNote). All 104 controls are present and each carries `codeTestable`, hand-authored by this project (see `note`), NOT a NIST-published rating. 48 are governance controls no scanner can assess; those report `manual`, never `satisfied`. See posture/privacy-framework.js.",
9
+ "controlsDigest": "3b91775abd5ffc1b",
10
10
  "controlCount": 104,
11
11
  "controls": [
12
12
  {
@@ -494,8 +494,11 @@
494
494
  "summary": "Log records are determined, documented, implemented, and reviewed in accordance with policy and incorporating the principle of data minimization.",
495
495
  "codeTestable": "partial",
496
496
  "mapsTo": [
497
- "module:scan-history",
498
- "module:mcp-audit"
497
+ "module:scan-history"
498
+ ],
499
+ "evidence": [
500
+ "Scan history retained.",
501
+ "Removed module:mcp-audit after a second-review pass: mcp-audit.log records calls to THIS tool's own MCP server, not the organisation's own log-record policy and review practice. Category error (self-referential: about this tool, not the target)."
499
502
  ]
500
503
  },
501
504
  {
@@ -605,8 +608,10 @@
605
608
  "category": "Data Processing Awareness (CM.AW-P)",
606
609
  "summary": "Records of data disclosures and sharing are maintained and can be accessed for review or transmission/disclosure.",
607
610
  "codeTestable": "yes",
608
- "mapsTo": [
609
- "module:mcp-audit"
611
+ "mapsTo": [],
612
+ "evidence": [
613
+ "No automated signal in this engine after a second-review pass removed the only prior mapping. module:mcp-audit pointed at THIS scanner's own MCP call log, not at the organisation's own records of data disclosures and sharing. Category error (self-referential: about this tool, not the target).",
614
+ "Reported as an engine gap: NIST rates this control code-testable (data-disclosure records could in principle be evidenced from a disclosure-log artifact in the scanned project), but no detector here looks for one."
610
615
  ]
611
616
  },
612
617
  {
@@ -623,8 +628,11 @@
623
628
  "summary": "Data provenance and lineage are maintained and can be accessed for review or transmission/disclosure.",
624
629
  "codeTestable": "partial",
625
630
  "mapsTo": [
626
- "module:sbom-diff",
627
- "module:integrity"
631
+ "module:sbom-diff"
632
+ ],
633
+ "evidence": [
634
+ "SBOM diff history retained.",
635
+ "Removed module:integrity after a second-review pass: last-scan.json.sig is this scanner signing its OWN scan output, not evidence of the organisation's own data provenance and lineage tracking. Category error (self-referential: about this tool, not the target), same class as the 03.03.08 fix in the NIST 800-171 framework."
628
636
  ]
629
637
  },
630
638
  {
@@ -845,5 +853,7 @@
845
853
  "summary": "Adequate resource capacity to ensure availability is maintained.",
846
854
  "codeTestable": "yes"
847
855
  }
848
- ]
856
+ ],
857
+ "sourceVerifiedAt": "2026-09-07",
858
+ "sourceVerificationNote": "Verified 2026-09-07 (WebFetch, two independent prompts against https://www.nist.gov/privacy-framework): NIST Privacy Framework 1.1 is currently an Initial Public Draft (IPD), not a finalized standard, per the page's own \"PF 1.1 IPD\" labeling. Version 1.0 is NIST's current final release. This bundled framework's 104 controls were transcribed from the 1.1 IPD text; if NIST finalizes 1.1 with control-text or numbering changes, this file will not automatically reflect them and needs a re-check against the final publication when it ships. No sourcePdfSha256 is pinned because the source is a navigation hub page, not a single fixed document, and because a draft is expected to change before finalization, unlike a published SP."
849
859
  }
@@ -4,6 +4,8 @@
4
4
  "publisher": "OWASP Foundation",
5
5
  "license": "Creative Commons Attribution-ShareAlike 4.0",
6
6
  "url": "https://owasp.org/www-project-application-security-verification-standard/",
7
+ "sourceVerifiedAt": "2026-09-07",
8
+ "sourceVerificationNote": "Verified 2026-09-07 (WebFetch): confirmed OWASP ASVS, latest stable release listed as 5.0.0, matching this bundle (owasp-asvs-5), released at Global AppSec EU Barcelona 2025-05-30. No content hash pinned: this is a project navigation page linking to a versioned downloads page, not the standard itself.",
7
9
  "scope": "SELECTIVE SUBSET. 10 verification requirements with a direct detector behind them. ASVS 5.0 has several hundred requirements across 17 chapters; the remainder are NOT represented here.",
8
10
  "controlsDigest": "6c4371a08f010f91",
9
11
  "controlCount": 10,
@@ -4,8 +4,11 @@
4
4
  "publisher": "OWASP Foundation",
5
5
  "license": "Creative Commons Attribution-ShareAlike 4.0",
6
6
  "url": "https://owasp.org/www-project-top-10-for-large-language-model-applications/",
7
- "scope": "FULL TOP-10. All ten categories are present. Each category is a broad class rather than a single testable requirement, so a mapping indicates a detector exists for part of the class, not that the class is fully covered.",
8
- "controlsDigest": "0a0351a9536790e4",
7
+ "sourceVerifiedAt": "2026-09-07",
8
+ "sourceVersionDriftDetected": true,
9
+ "sourceVerificationNote": "Verified 2026-09-07 (WebFetch): confirmed page is OWASP Top 10 for LLM Applications, but the CURRENT published edition is now 2026 (2026-08-04), not the 2025 edition this file bundles. See scope field for the disclosure and what remediation this requires.",
10
+ "scope": "FULL TOP-10. All ten categories are present. Each category is a broad class rather than a single testable requirement, so a mapping indicates a detector exists for part of the class, not that the class is fully covered. VERSION DRIFT, disclosed 2026-09-07: this bundle is the 2025 edition. OWASP published a 2026 edition on 2026-08-04 (confirmed live, the project page now archives 2025 and lists 2026 as current); the 10-category taxonomy may have changed. This file has NOT been re-mapped to the 2026 edition, that requires a deliberate review of the new categories against this engine's detector families, not a rushed relabel. Treat this bundle as 2025-accurate only until that review lands.",
11
+ "controlsDigest": "7500b9c4268eba6f",
9
12
  "controlCount": 10,
10
13
  "controls": [
11
14
  {
@@ -101,11 +104,10 @@
101
104
  "summary": "Excessive Agency — agent autonomy is bounded by explicit guards.",
102
105
  "codeTestable": "partial",
103
106
  "evidence": [
104
- "MCP server config limits write-tools to confirm:true + reserved-write-path refusal.",
105
- "Apply-fix path requires HMAC-verified scan."
107
+ "Apply-fix path requires HMAC-verified scan.",
108
+ "Removed module:mcp-tools after a second-review pass: that path points at THIS scanner's own source file (scanner/src/mcp/tools.js), which only resolves inside this tool's own checkout, never inside an arbitrary assessed project. It cannot evidence that the assessed LLM application bounds ITS OWN agent's autonomy. Category error (self-referential: about this tool, not the target)."
106
109
  ],
107
110
  "mapsTo": [
108
- "module:mcp-tools",
109
111
  "module:apply-fix"
110
112
  ]
111
113
  },
@@ -114,13 +116,10 @@
114
116
  "summary": "Overreliance — human review checkpoints exist.",
115
117
  "codeTestable": "partial",
116
118
  "evidence": [
117
- "security-fixer agent has Stop-on-verify-fail loop.",
118
- "PreToolUse bodyguard exists."
119
+ "No automated signal in this engine after a second-review pass removed both prior mappings. module:security-fixer pointed at THIS scanner's own agent definition file (agents/security-fixer.md) and module:pre-edit-bodyguard at THIS scanner's own installed hook — both resolve only inside this tool's own checkout/plugin install, never inside an arbitrary assessed project, and neither evidences that the assessed LLM application itself has human review checkpoints. Category error (self-referential: about this tool, not the target), same class as the 03.03.08 fix in the NIST 800-171 framework.",
120
+ "Reported as an engine gap: human review checkpoints around an LLM application's outputs are code-observable in principle (an approval-gate library, a human-in-the-loop wrapper in the assessed system's own code), but no detector here looks for that in the SCANNED project."
119
121
  ],
120
- "mapsTo": [
121
- "module:security-fixer",
122
- "module:pre-edit-bodyguard"
123
- ]
122
+ "mapsTo": []
124
123
  },
125
124
  {
126
125
  "id": "LLM10",
@@ -32,6 +32,7 @@ import * as path from 'node:path';
32
32
  import * as os from 'node:os';
33
33
  import { runConfined, sandboxAvailable } from '../sandbox/index.js';
34
34
  import { isExplicitlyNoPoc } from './poc-cwe-map.js';
35
+ import { isSafeStateDir, stateDir, statePath, stateWritesEnabled } from './state-dir.js';
35
36
 
36
37
  // ─── PoC static validation ──────────────────────────────────────────────────
37
38
  //
@@ -228,5 +229,74 @@ export function verifierCoverageSummary(findings) {
228
229
  return out;
229
230
  }
230
231
 
232
+ // ─── Run persistence (adversarial premortem Q1, 2026-09-07) ────────────────
233
+ //
234
+ // `compliance-frameworks/*.json` has mapped `module:verifier` to
235
+ // `verifier-runs/` since this framework's control-to-artifact table was
236
+ // written, but nothing ever wrote to it: `cmdVerify` (bin/agentic-security.js)
237
+ // ran the loop and printed a summary, but never persisted one. That made
238
+ // NIST 800-171 `03.12.01` (Security Assessment) and NIST CSF 2.0 `RC.RP`
239
+ // (Recovery plans executed and improved) permanently unable to read
240
+ // 'present' via this leg of their mapping, on any project, ever — worse than
241
+ // a self-referential mapping (which can at least clear, dishonestly), a
242
+ // mapping that can never clear at all. This closes that gap for real, one
243
+ // record per `agentic-security verify` run, same shape as fix-metrics.js's
244
+ // append-per-attempt pattern (that file uses one growing JSONL; this one
245
+ // uses one file per run, because the table entry it satisfies is a
246
+ // directory, not a file, and a per-run file is what a reader would expect
247
+ // under a name like "verifier-runs/").
248
+ const RUN_DIR = 'verifier-runs';
249
+
250
+ /**
251
+ * Persist one record of an `agentic-security verify` invocation. Best-effort
252
+ * and silent on failure, same convention as every other posture writer: a
253
+ * verify run's own findings must never be lost because the record of having
254
+ * run could not be written.
255
+ *
256
+ * @returns {boolean} whether the record was written (for tests, not callers).
257
+ */
258
+ export function recordVerifierRun(scanRoot, summary) {
259
+ if (!scanRoot || !summary || typeof summary !== 'object') return false;
260
+ try {
261
+ const dir = stateDir(scanRoot);
262
+ if (!isSafeStateDir(dir)) return false;
263
+ if (!stateWritesEnabled()) return false;
264
+ const runsDir = statePath(scanRoot, RUN_DIR);
265
+ fs.mkdirSync(runsDir, { recursive: true });
266
+ const now = new Date();
267
+ // Millisecond Date timestamps collide under back-to-back calls (two
268
+ // `verify` invocations in the same test, or a scripted loop). hrtime is
269
+ // monotonic and nanosecond-resolution within this process, so appending
270
+ // it guarantees both a distinct filename AND correct chronological sort
271
+ // order (alphabetical == call order), which a random suffix alone would
272
+ // not: two records written in the same millisecond would sort by random
273
+ // bytes, not by which call happened first.
274
+ const stamp = now.toISOString().replace(/[^0-9TZ]/g, '-');
275
+ const seq = process.hrtime.bigint().toString().padStart(20, '0');
276
+ const record = { timestamp: now.toISOString(), ...summary };
277
+ // One writeFileSync of one complete file: a concurrent reader sees a
278
+ // whole record or nothing (never a partial one, unlike an append target).
279
+ fs.writeFileSync(path.join(runsDir, `${stamp}-${seq}.json`), JSON.stringify(record, null, 2), 'utf8');
280
+ return true;
281
+ } catch { return false; }
282
+ }
283
+
284
+ /**
285
+ * Read every well-formed run record, oldest first. A file that does not
286
+ * parse is skipped, not thrown on — same tolerant-read convention as
287
+ * fix-metrics.js's readFixAttempts.
288
+ */
289
+ export function readVerifierRuns(scanRoot) {
290
+ try {
291
+ const runsDir = statePath(scanRoot, RUN_DIR);
292
+ const files = fs.readdirSync(runsDir).filter((f) => f.endsWith('.json')).sort();
293
+ const out = [];
294
+ for (const f of files) {
295
+ try { out.push(JSON.parse(fs.readFileSync(path.join(runsDir, f), 'utf8'))); } catch { /* skip malformed */ }
296
+ }
297
+ return out;
298
+ } catch { return []; }
299
+ }
300
+
231
301
  // For tests and the no-dead-modules check.
232
302
  export const _internals = { MAX_POC_BYTES, BANNED_HOSTS, SANITIZER_TABLE, runSandboxed };