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

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 +83 -0
  2. package/bin/agentic-security.js +5 -1
  3. package/dist/4970.index.js +58 -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 +14 -0
  18. package/src/pipeline/assurance-mode.js +58 -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,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 };