@clear-capabilities/agentic-security-scanner 0.140.0 → 0.141.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/CHANGELOG.md +148 -0
- package/dist/113.index.js +79 -3
- package/dist/178.index.js +1 -1
- package/dist/238.index.js +77 -1
- package/dist/384.index.js +1 -1
- package/dist/435.index.js +12 -0
- package/dist/526.index.js +79 -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/ccpa.json +34 -7
- package/dist/compliance-frameworks/eu-ai-act.json +65 -14
- package/dist/compliance-frameworks/gdpr.json +56 -12
- package/dist/compliance-frameworks/hipaa-security-rule.json +68 -15
- package/dist/compliance-frameworks/nist-ai-600-1.json +57 -12
- package/dist/compliance-frameworks/nist-csf-2.json +78 -16
- package/dist/compliance-frameworks/nist-privacy-1-1.json +3 -0
- package/dist/compliance-frameworks/owasp-asvs-5.json +91 -20
- package/dist/compliance-frameworks/owasp-llm-top-10.json +89 -20
- package/package.json +16 -5
- package/src/dataflow/catalog.js +61 -0
- package/src/engine.js +262 -22
- package/src/mcp/tools.js +12 -0
- package/src/posture/accuracy-scorecard.js +57 -0
- package/src/posture/aibom.js +110 -1
- package/src/posture/auditor-walkthrough.js +56 -17
- package/src/posture/compliance-frameworks/ccpa.json +34 -7
- package/src/posture/compliance-frameworks/eu-ai-act.json +65 -14
- package/src/posture/compliance-frameworks/gdpr.json +56 -12
- package/src/posture/compliance-frameworks/hipaa-security-rule.json +68 -15
- package/src/posture/compliance-frameworks/nist-ai-600-1.json +57 -12
- package/src/posture/compliance-frameworks/nist-csf-2.json +78 -16
- package/src/posture/compliance-frameworks/nist-privacy-1-1.json +3 -0
- package/src/posture/compliance-frameworks/owasp-asvs-5.json +91 -20
- package/src/posture/compliance-frameworks/owasp-llm-top-10.json +89 -20
- package/src/posture/concurrency-checker.js +3 -3
- package/src/posture/coverage-strength.js +182 -0
- package/src/posture/epss.js +17 -1
- package/src/posture/family-registry.js +103 -0
- package/src/posture/family-resolve.js +47 -0
- package/src/posture/fix-coverage.js +113 -0
- package/src/posture/fix-metrics.js +76 -0
- package/src/posture/mcp-rug-pull.js +144 -0
- package/src/posture/poc-inprocess.js +217 -1
- package/src/posture/proof-coverage.js +162 -0
- package/src/posture/reachability-filter.js +44 -0
- package/src/posture/sbom.js +12 -3
- package/src/runScan.js +56 -5
- package/src/sast/CLAUDE.md +2 -2
- package/src/sast/claude-md-prompt-injection.js +47 -3
- package/src/sast/cloud-iam.js +23 -0
- package/src/sast/convention-deviation.js +66 -3
- package/src/sast/crypto-protocol.js +23 -0
- package/src/sast/dapp-frontend.js +20 -0
- package/src/sast/iac-cloud-templates.js +337 -0
- package/src/sast/k8s-admission.js +27 -0
- package/src/sast/ml-supply-chain.js +22 -0
- package/src/sast/ruby.js +132 -0
- package/src/sast/web3-advanced.js +26 -0
- package/src/sca/CLAUDE.md +21 -4
- package/src/sca/container.js +18 -1
- package/src/sca/dep-confusion.js +69 -3
|
@@ -428,6 +428,63 @@ export function renderScorecardMarkdown(m) {
|
|
|
428
428
|
}
|
|
429
429
|
L.push('Per-file counts are in `docs/scorecard.json`.');
|
|
430
430
|
L.push('');
|
|
431
|
+
// PRD F12.6 — the honest scorecard publishes the LIMITS too, not only the
|
|
432
|
+
// rates. Three claims this project makes are only meaningful with their
|
|
433
|
+
// caveat attached, and each caveat was invisible before this section:
|
|
434
|
+
// proof coverage — "provable" means provable by a JAVASCRIPT-ONLY harness
|
|
435
|
+
// calibration — the confidence surface is currently UNVERIFIED
|
|
436
|
+
// compliance — most controls are backed by weak or unmeasured detectors
|
|
437
|
+
// Publishing a rate without its ceiling is how a number comes to mean
|
|
438
|
+
// whatever the reader assumes.
|
|
439
|
+
if (m.limits) {
|
|
440
|
+
L.push('## Stated limits — what these capabilities cannot do');
|
|
441
|
+
L.push('');
|
|
442
|
+
if (m.limits.proof) {
|
|
443
|
+
const pc = m.limits.proof;
|
|
444
|
+
L.push('### Execution proof coverage, with its ceiling');
|
|
445
|
+
L.push('');
|
|
446
|
+
L.push('| Bucket | Share of all findings |');
|
|
447
|
+
L.push('| --- | --- |');
|
|
448
|
+
L.push(`| Provable (a proof class exists) | ${pc.provable.n}/${pc.total} |`);
|
|
449
|
+
L.push(`| Declined on purpose, reason stated | ${pc.indeterminate.n}/${pc.total} |`);
|
|
450
|
+
L.push(`| No proof class yet (backlog) | ${pc.unclassified.n}/${pc.total} |`);
|
|
451
|
+
L.push(`| **Out of harness scope (not JavaScript)** | **${pc.outOfScope.n}/${pc.total}** |`);
|
|
452
|
+
L.push('');
|
|
453
|
+
L.push(`**The ceiling.** The in-process proof harness only loads JavaScript. Of ${pc.total} findings`);
|
|
454
|
+
L.push(`measured on the CVE corpus, ${pc.reachable.n} are reachable by it at all. Proof coverage is`);
|
|
455
|
+
L.push(`**${pc.provable.n}/${pc.total}** of ALL findings and **${pc.provable.n}/${pc.reachable.n}** of the reachable ones.`);
|
|
456
|
+
L.push('Both are given because they differ by a lot, and a reader shown only one will');
|
|
457
|
+
L.push('draw the wrong conclusion in whichever direction that one flatters.');
|
|
458
|
+
L.push('');
|
|
459
|
+
L.push('Adding more proof classes does not move the ceiling. A Python or Java finding is');
|
|
460
|
+
L.push('not backlog — it is unreachable by this harness at any effort.');
|
|
461
|
+
L.push('');
|
|
462
|
+
}
|
|
463
|
+
if (m.limits.calibration) {
|
|
464
|
+
L.push('### Confidence calibration');
|
|
465
|
+
L.push('');
|
|
466
|
+
L.push(`**${m.limits.calibration}**`);
|
|
467
|
+
L.push('');
|
|
468
|
+
L.push('Every finding carries a confidence number. That number is a claim about how');
|
|
469
|
+
L.push('often the engine is right, and it is only worth what the evidence behind it is');
|
|
470
|
+
L.push('worth. `calibration-seed.json` is FITTING data, so measuring against it would');
|
|
471
|
+
L.push('reproduce the error it was fitted to. The release gate fails on this by default;');
|
|
472
|
+
L.push('a dated waiver is what currently allows a release, and it expires.');
|
|
473
|
+
L.push('');
|
|
474
|
+
}
|
|
475
|
+
if (m.limits.compliance) {
|
|
476
|
+
const c = m.limits.compliance;
|
|
477
|
+
L.push('### Compliance control strength');
|
|
478
|
+
L.push('');
|
|
479
|
+
L.push(`Of ${c.total} bundled controls, **${c.partiallyEvidenced} are backed by a detector that is weak or was never`);
|
|
480
|
+
L.push('measured against an independent corpus**, and are flagged `partiallyEvidenced`.');
|
|
481
|
+
L.push(`A further **${c.notCodeTestable} are organisational or artifact-existence only** and can never`);
|
|
482
|
+
L.push('read as evidenced by a scanner. A control appearing in a coverage map is not a');
|
|
483
|
+
L.push('statement that the control is satisfied.');
|
|
484
|
+
L.push('');
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
431
488
|
const ind = m.committedInputs.independent;
|
|
432
489
|
if (ind && ind.overall) {
|
|
433
490
|
L.push('## Independent evaluation population — the number that matters');
|
package/src/posture/aibom.js
CHANGED
|
@@ -201,7 +201,19 @@ export function buildAIBOM(scan, fileContents = {}, meta = {}) {
|
|
|
201
201
|
return {
|
|
202
202
|
aibomFormat: 'agentic-security AI-BOM',
|
|
203
203
|
version: '1',
|
|
204
|
-
|
|
204
|
+
// PRD F5.5 — this document is PROPRIETARY and now says so.
|
|
205
|
+
//
|
|
206
|
+
// It previously carried `cyclonedxCompatible: '1.7-ml-bom'`, which was an
|
|
207
|
+
// unverified claim: nothing validated it, and the document has no
|
|
208
|
+
// `bomFormat`, no `specVersion` and no CycloneDX `components` array, so it
|
|
209
|
+
// would not have parsed as CycloneDX at all. A consumer reading that field
|
|
210
|
+
// and feeding this to a CycloneDX tool would have got an error, not a BOM.
|
|
211
|
+
//
|
|
212
|
+
// The honest options F5.5 gives are "validate mechanically, or be labelled
|
|
213
|
+
// proprietary". Both are taken: the field below states the truth, and
|
|
214
|
+
// `toCycloneDXMLBOM()` emits a REAL ML-BOM that `validateMLBOM()` checks.
|
|
215
|
+
proprietary: true,
|
|
216
|
+
cyclonedxMlBom: 'not this document — call toCycloneDXMLBOM() for a CycloneDX 1.6 ML-BOM view',
|
|
205
217
|
generatedAt: meta.startedAt || new Date().toISOString(),
|
|
206
218
|
models,
|
|
207
219
|
promptTemplates,
|
|
@@ -286,3 +298,100 @@ export function aibomToMarkdown(aibom) {
|
|
|
286
298
|
|
|
287
299
|
return out.join('\n');
|
|
288
300
|
}
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
// ── CycloneDX ML-BOM (PRD F5.5) ────────────────────────────────────────────
|
|
304
|
+
//
|
|
305
|
+
// A real CycloneDX 1.6 document describing the models this scan found, using the
|
|
306
|
+
// ML-BOM shape: `components[].type: 'machine-learning-model'` carrying a
|
|
307
|
+
// `modelCard`. Emitted separately from the proprietary AI-BOM rather than
|
|
308
|
+
// pretending the proprietary one already conforms.
|
|
309
|
+
//
|
|
310
|
+
// The serial number derives from content under --deterministic, for the same
|
|
311
|
+
// reason toCycloneDX's does: an attestation over a BOM is meaningless if the BOM
|
|
312
|
+
// changes on every run.
|
|
313
|
+
import * as _crypto from 'node:crypto';
|
|
314
|
+
import { isDeterministic as _isDet } from './deterministic.js';
|
|
315
|
+
|
|
316
|
+
function _mlbomSerial(seed) {
|
|
317
|
+
if (!_isDet()) return `urn:uuid:${_crypto.randomUUID()}`;
|
|
318
|
+
const h = _crypto.createHash('sha256').update(String(seed)).digest('hex');
|
|
319
|
+
return `urn:uuid:${h.slice(0, 8)}-${h.slice(8, 12)}-4${h.slice(13, 16)}-${((parseInt(h[16], 16) & 0x3) | 0x8).toString(16)}${h.slice(17, 20)}-${h.slice(20, 32)}`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function toCycloneDXMLBOM(aibom, meta = {}) {
|
|
323
|
+
const models = (aibom && aibom.models) || [];
|
|
324
|
+
const components = models.map((m) => ({
|
|
325
|
+
type: 'machine-learning-model',
|
|
326
|
+
'bom-ref': `model:${m.provider || 'unknown'}/${m.name || 'unknown'}${m.version ? `@${m.version}` : ''}`,
|
|
327
|
+
name: m.name || 'unknown',
|
|
328
|
+
...(m.version ? { version: m.version } : {}),
|
|
329
|
+
modelCard: {
|
|
330
|
+
modelParameters: {
|
|
331
|
+
...(m.provider ? { approach: { type: 'supervised' } } : {}),
|
|
332
|
+
...(m.task ? { task: m.task } : {}),
|
|
333
|
+
},
|
|
334
|
+
// `considerations` is where CycloneDX expects known risks to live. Pinning
|
|
335
|
+
// status is a real supply-chain property of a model reference: an unpinned
|
|
336
|
+
// model can change under you between runs.
|
|
337
|
+
considerations: {
|
|
338
|
+
technicalLimitations: m.pinned
|
|
339
|
+
? []
|
|
340
|
+
: ['model reference is not pinned to a version; the served model can change between runs'],
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
properties: [
|
|
344
|
+
...(m.provider ? [{ name: 'agentic-security:provider', value: String(m.provider) }] : []),
|
|
345
|
+
...(m.file ? [{ name: 'agentic-security:declaredIn', value: String(m.file) }] : []),
|
|
346
|
+
{ name: 'agentic-security:pinned', value: String(!!m.pinned) },
|
|
347
|
+
],
|
|
348
|
+
}));
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
bomFormat: 'CycloneDX',
|
|
352
|
+
specVersion: '1.6',
|
|
353
|
+
serialNumber: _mlbomSerial(JSON.stringify(components.map((c) => c['bom-ref']))),
|
|
354
|
+
version: 1,
|
|
355
|
+
metadata: {
|
|
356
|
+
timestamp: meta.startedAt || (aibom && aibom.generatedAt) || new Date().toISOString(),
|
|
357
|
+
tools: [{ vendor: 'Clear Capabilities', name: 'agentic-security', version: meta.engineVersion || 'dev' }],
|
|
358
|
+
component: { type: 'application', name: 'scan-target', version: '1.0.0' },
|
|
359
|
+
},
|
|
360
|
+
components,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Mechanical validation of an ML-BOM.
|
|
366
|
+
*
|
|
367
|
+
* STRUCTURAL, not full JSON-Schema validation: fetching the official CycloneDX
|
|
368
|
+
* schema at scan time would break the no-runtime-network rule, and vendoring it
|
|
369
|
+
* would add a file that silently rots against upstream. So this checks the
|
|
370
|
+
* required fields and the ML-BOM-specific shape, and SAYS that is what it does —
|
|
371
|
+
* a check labelled "validates against CycloneDX" that only tests a few keys
|
|
372
|
+
* would be the same unverified claim this item exists to remove.
|
|
373
|
+
*/
|
|
374
|
+
export function validateMLBOM(doc) {
|
|
375
|
+
const errors = [];
|
|
376
|
+
const req = (cond, msg) => { if (!cond) errors.push(msg); };
|
|
377
|
+
|
|
378
|
+
req(doc && typeof doc === 'object', 'not an object');
|
|
379
|
+
if (!doc || typeof doc !== 'object') return { ok: false, errors, checked: 'structural' };
|
|
380
|
+
|
|
381
|
+
req(doc.bomFormat === 'CycloneDX', `bomFormat must be "CycloneDX", got ${JSON.stringify(doc.bomFormat)}`);
|
|
382
|
+
req(/^1\.[4-9]$/.test(String(doc.specVersion || '')), `specVersion must be 1.4-1.9, got ${JSON.stringify(doc.specVersion)}`);
|
|
383
|
+
req(Number.isInteger(doc.version) && doc.version >= 1, 'version must be a positive integer');
|
|
384
|
+
req(/^urn:uuid:[0-9a-f-]{36}$/i.test(String(doc.serialNumber || '')), 'serialNumber must be a urn:uuid');
|
|
385
|
+
req(doc.metadata && typeof doc.metadata === 'object', 'metadata is required');
|
|
386
|
+
req(Array.isArray(doc.components), 'components must be an array');
|
|
387
|
+
|
|
388
|
+
for (const [i, c] of (Array.isArray(doc.components) ? doc.components : []).entries()) {
|
|
389
|
+
req(typeof c.name === 'string' && c.name, `components[${i}].name is required`);
|
|
390
|
+
req(typeof c.type === 'string' && c.type, `components[${i}].type is required`);
|
|
391
|
+
if (c.type === 'machine-learning-model') {
|
|
392
|
+
req(c.modelCard && typeof c.modelCard === 'object',
|
|
393
|
+
`components[${i}] is a machine-learning-model but carries no modelCard — that is the whole ML-BOM extension`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return { ok: errors.length === 0, errors, checked: 'structural (required fields + ML-BOM component shape), NOT full JSON-Schema validation' };
|
|
397
|
+
}
|
|
@@ -35,6 +35,11 @@ import * as fs from 'node:fs';
|
|
|
35
35
|
import * as path from 'node:path';
|
|
36
36
|
|
|
37
37
|
import { statePath, stateWritesEnabled } from './state-dir.js';
|
|
38
|
+
import { COMPLIANCE_FAMILY_ALIAS, resolveFamilyKeys } from './family-resolve.js';
|
|
39
|
+
import { strengthOfControl as _strengthOfControl } from './coverage-strength.js';
|
|
40
|
+
|
|
41
|
+
// Re-exported so existing callers/tests keep importing these from here.
|
|
42
|
+
export { COMPLIANCE_FAMILY_ALIAS, resolveFamilyKeys };
|
|
38
43
|
const BUNDLED_DIR = path.join(path.dirname(new URL(import.meta.url).pathname), 'compliance-frameworks');
|
|
39
44
|
function _readJson(fp) {
|
|
40
45
|
try { return JSON.parse(fs.readFileSync(fp, 'utf8')); } catch { return null; }
|
|
@@ -118,15 +123,6 @@ export function loadFramework(scanRoot, id) {
|
|
|
118
123
|
// `k8s-pod-privileged`; `nist-csf-2.json`/`hipaa-security-rule.json` map to
|
|
119
124
|
// the compliance-side spelling `k8s-pod-security-privileged`, which no
|
|
120
125
|
// detector ever emitted.
|
|
121
|
-
export const COMPLIANCE_FAMILY_ALIAS = {
|
|
122
|
-
// ASVS spells it `sqli`; every detector emits `sql-injection` (or a
|
|
123
|
-
// language-prefixed variant, which the suffix rule below does NOT cover
|
|
124
|
-
// because the prefix is on the wrong end).
|
|
125
|
-
'sqli': ['sql-injection', 'dart-sql-injection', 'laravel-sql-injection'],
|
|
126
|
-
'auth-missing': ['broken-access-control', 'fastapi-missing-auth', 'springboot-missing-authz', 'laravel-missing-auth', 'quarkus-missing-authz'],
|
|
127
|
-
'authz': ['broken-access-control', 'idor', 'springboot-missing-authz', 'quarkus-missing-authz'],
|
|
128
|
-
'k8s-pod-security-privileged': ['k8s-pod-privileged'],
|
|
129
|
-
};
|
|
130
126
|
|
|
131
127
|
// CMP-1 audit trail: every `family:` string referenced by the bundled
|
|
132
128
|
// compliance-frameworks/*.json files was cross-checked against real
|
|
@@ -223,7 +219,19 @@ export function evaluateFramework(scanRoot, fw, scan) {
|
|
|
223
219
|
|
|
224
220
|
if (maps.length === 0) {
|
|
225
221
|
obs.push('No automated mapping — requires manual evidence collection.');
|
|
226
|
-
|
|
222
|
+
// PRD F10.2: carry the MEASURED strength of the backing detector, so a
|
|
223
|
+
// control mapped to a detector that finds 3 of 18 independent advisories
|
|
224
|
+
// cannot read the same as one backed by a detector that finds nearly
|
|
225
|
+
// everything. Import is lazy so the evaluator keeps working if the bench
|
|
226
|
+
// artifacts are absent (they degrade to `unmeasured`, never to a default).
|
|
227
|
+
let evidence = null;
|
|
228
|
+
try { evidence = _strengthOfControl(c); } catch { /* strength is additive; never block evaluation */ }
|
|
229
|
+
results.push({
|
|
230
|
+
control: c,
|
|
231
|
+
status,
|
|
232
|
+
observations: obs,
|
|
233
|
+
...(evidence ? { evidence, partiallyEvidenced: evidence.tier === 'weak' || evidence.tier === 'unmeasured' } : {}),
|
|
234
|
+
});
|
|
227
235
|
continue;
|
|
228
236
|
}
|
|
229
237
|
|
|
@@ -268,7 +276,6 @@ export function evaluateFramework(scanRoot, fw, scan) {
|
|
|
268
276
|
hasUnverifiableMapping = true;
|
|
269
277
|
continue;
|
|
270
278
|
}
|
|
271
|
-
const aliasFams = COMPLIANCE_FAMILY_ALIAS[fam] || [];
|
|
272
279
|
// Several detectors emit the family as `<family>-<rule-slug>` — the
|
|
273
280
|
// observed vocabulary holds `prompt-injection-http-user-input-in-llm-`,
|
|
274
281
|
// `xpath-injection-query-built-via-string-c` and similar. This lookup
|
|
@@ -281,11 +288,8 @@ export function evaluateFramework(scanRoot, fw, scan) {
|
|
|
281
288
|
// prefix test would let `nosql-injection` satisfy a `sql-injection`
|
|
282
289
|
// mapping, silently merging two different vulnerability classes. A test
|
|
283
290
|
// pins that boundary in the failing direction.
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
for (const [key, list] of families) {
|
|
287
|
-
if (bases.some(b => key === b || key.startsWith(`${b}-`))) candidates.push(...list);
|
|
288
|
-
}
|
|
291
|
+
const candidates = resolveFamilyKeys(fam, families.keys())
|
|
292
|
+
.flatMap(k => families.get(k) || []);
|
|
289
293
|
const scoped = subfam ? candidates.filter(f => !f.subfamily || f.subfamily === subfam) : candidates;
|
|
290
294
|
const open = scoped.filter(f => !f.intentSuppressed && !f.pastDecision && (f.severity === 'critical' || f.severity === 'high'));
|
|
291
295
|
if (open.length) {
|
|
@@ -359,13 +363,48 @@ export function evaluateFramework(scanRoot, fw, scan) {
|
|
|
359
363
|
// any control that already reads 'partial' because of an inherently
|
|
360
364
|
// unverifiable rule: mapping — that precedent (rule: caps at 'partial',
|
|
361
365
|
// never reaching 'present' OR 'absent') is unchanged.
|
|
366
|
+
// PRD F10.3: a control NIST/the framework rates as not code-testable must
|
|
367
|
+
// never read as evidenced by this tool. Two shapes were reaching 'present'
|
|
368
|
+
// that should not have:
|
|
369
|
+
//
|
|
370
|
+
// codeTestable:'no' — organisational (policy, training, governance).
|
|
371
|
+
// Nothing a scanner observes can satisfy it.
|
|
372
|
+
// codeTestable:'partial' — the only mappings are `module:` artifact
|
|
373
|
+
// EXISTENCE checks. "threat-model.json is
|
|
374
|
+
// present" is not evidence that threat modelling
|
|
375
|
+
// happened; it is evidence a file exists.
|
|
376
|
+
//
|
|
377
|
+
// Both are capped at 'partial' via the same unverifiable-mapping path a
|
|
378
|
+
// `rule:` mapping already uses, and the reason is stated in the
|
|
379
|
+
// observations so a reader is told WHY rather than left to infer it.
|
|
380
|
+
if (c.codeTestable === 'no') {
|
|
381
|
+
obs.push('⚠ this control is organisational, not code-testable — a scanner cannot evidence it (codeTestable: no).');
|
|
382
|
+
hasUnverifiableMapping = true;
|
|
383
|
+
anySignal = true;
|
|
384
|
+
} else if (c.codeTestable === 'partial' && maps.every(m => !m.startsWith('family:'))) {
|
|
385
|
+
obs.push('⚠ backed only by artifact-existence checks — a present file is weaker evidence than a detector finding nothing (codeTestable: partial).');
|
|
386
|
+
hasUnverifiableMapping = true;
|
|
387
|
+
}
|
|
388
|
+
|
|
362
389
|
if (!anySignal) status = 'manual';
|
|
363
390
|
else if (hasUnverifiableMapping) status = 'partial';
|
|
364
391
|
else if (allCleared) status = 'present';
|
|
365
392
|
else if (!anyCleared) status = 'absent';
|
|
366
393
|
else status = 'partial';
|
|
367
394
|
|
|
368
|
-
|
|
395
|
+
// PRD F10.2: carry the MEASURED strength of the backing detector, so a
|
|
396
|
+
// control mapped to a detector that finds 3 of 18 independent advisories
|
|
397
|
+
// cannot read the same as one backed by a detector that finds nearly
|
|
398
|
+
// everything. Import is lazy so the evaluator keeps working if the bench
|
|
399
|
+
// artifacts are absent (they degrade to `unmeasured`, never to a default).
|
|
400
|
+
let evidence = null;
|
|
401
|
+
try { evidence = _strengthOfControl(c); } catch { /* strength is additive; never block evaluation */ }
|
|
402
|
+
results.push({
|
|
403
|
+
control: c,
|
|
404
|
+
status,
|
|
405
|
+
observations: obs,
|
|
406
|
+
...(evidence ? { evidence, partiallyEvidenced: evidence.tier === 'weak' || evidence.tier === 'unmeasured' } : {}),
|
|
407
|
+
});
|
|
369
408
|
}
|
|
370
409
|
return results;
|
|
371
410
|
}
|
|
@@ -4,29 +4,56 @@
|
|
|
4
4
|
"publisher": "California Legislature",
|
|
5
5
|
"license": "California statute (public)",
|
|
6
6
|
"url": "https://leginfo.legislature.ca.gov/faces/codes_displayText.xhtml?division=3.&part=4.&lawCode=CIV&title=1.81.5",
|
|
7
|
+
"scope": "SELECTIVE SUBSET. 4 of the CCPA/CPRA obligations, chosen because a code scanner can produce evidence for them. The statute is far broader; the majority of its duties (notice, consumer request handling, contracts, retention policy) are organisational and are NOT represented here. Absence of a control is not a statement of compliance.",
|
|
8
|
+
"controlsDigest": "ebc1f708c329ab42",
|
|
9
|
+
"controlCount": 4,
|
|
7
10
|
"controls": [
|
|
8
11
|
{
|
|
9
12
|
"id": "§1798.100",
|
|
10
13
|
"summary": "Consumer right to know — businesses inform consumers about categories of personal info collected.",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
14
|
+
"codeTestable": "partial",
|
|
15
|
+
"evidence": [
|
|
16
|
+
"DPIA artifact lists every PII field with file:line provenance."
|
|
17
|
+
],
|
|
18
|
+
"mapsTo": [
|
|
19
|
+
"module:privacy-taint"
|
|
20
|
+
]
|
|
13
21
|
},
|
|
14
22
|
{
|
|
15
23
|
"id": "§1798.105",
|
|
16
24
|
"summary": "Right to delete — businesses comply with verified deletion requests.",
|
|
17
|
-
"
|
|
25
|
+
"codeTestable": "no",
|
|
26
|
+
"evidence": [
|
|
27
|
+
"No data-exposure findings on PII storage paths."
|
|
28
|
+
]
|
|
18
29
|
},
|
|
19
30
|
{
|
|
20
31
|
"id": "§1798.150",
|
|
21
32
|
"summary": "Civil action for security breach affecting personal information.",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
33
|
+
"codeTestable": "yes",
|
|
34
|
+
"evidence": [
|
|
35
|
+
"Crypto findings cleared on PII handling code.",
|
|
36
|
+
"TLS pinning checks pass."
|
|
37
|
+
],
|
|
38
|
+
"mapsTo": [
|
|
39
|
+
"family:crypto-weak-cipher",
|
|
40
|
+
"family:crypto-tls-version",
|
|
41
|
+
"family:crypto-tls-no-verify"
|
|
42
|
+
]
|
|
24
43
|
},
|
|
25
44
|
{
|
|
26
45
|
"id": "§1798.81.5",
|
|
27
46
|
"summary": "Reasonable security procedures and practices.",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
47
|
+
"codeTestable": "yes",
|
|
48
|
+
"evidence": [
|
|
49
|
+
"No critical findings on user-data endpoints.",
|
|
50
|
+
"Auth-missing zero on PII paths."
|
|
51
|
+
],
|
|
52
|
+
"mapsTo": [
|
|
53
|
+
"family:auth-missing",
|
|
54
|
+
"family:authz",
|
|
55
|
+
"family:hardcoded-secret"
|
|
56
|
+
]
|
|
30
57
|
}
|
|
31
58
|
]
|
|
32
59
|
}
|
|
@@ -4,48 +4,99 @@
|
|
|
4
4
|
"publisher": "European Parliament & Council",
|
|
5
5
|
"license": "EU law (Official Journal)",
|
|
6
6
|
"url": "https://eur-lex.europa.eu/eli/reg/2024/1689/oj",
|
|
7
|
+
"scope": "SELECTIVE SUBSET. 7 obligations drawn from the high-risk-system and GPAI articles where a code signal exists. The Act is far broader; conformity assessment, registration, human oversight and post-market monitoring are organisational and are NOT represented here.",
|
|
8
|
+
"controlsDigest": "16d15998a686a19b",
|
|
9
|
+
"controlCount": 7,
|
|
7
10
|
"controls": [
|
|
8
11
|
{
|
|
9
12
|
"id": "Art.9",
|
|
10
13
|
"summary": "Risk management system — continuous, iterative risk identification and mitigation.",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
14
|
+
"codeTestable": "partial",
|
|
15
|
+
"evidence": [
|
|
16
|
+
"Threat model artifact (.agentic-security/threat-model.{json,md}).",
|
|
17
|
+
"Continuous scan history present."
|
|
18
|
+
],
|
|
19
|
+
"mapsTo": [
|
|
20
|
+
"module:threat-model-auto",
|
|
21
|
+
"module:scan-history"
|
|
22
|
+
]
|
|
13
23
|
},
|
|
14
24
|
{
|
|
15
25
|
"id": "Art.10",
|
|
16
26
|
"summary": "Data governance — training/validation data is relevant, representative, and bias-checked.",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
27
|
+
"codeTestable": "yes",
|
|
28
|
+
"evidence": [
|
|
29
|
+
"No training-data-pii findings.",
|
|
30
|
+
"DPIA artifact for any PII handling."
|
|
31
|
+
],
|
|
32
|
+
"mapsTo": [
|
|
33
|
+
"family:training-data-pii",
|
|
34
|
+
"family:pii-exposure"
|
|
35
|
+
]
|
|
19
36
|
},
|
|
20
37
|
{
|
|
21
38
|
"id": "Art.11",
|
|
22
39
|
"summary": "Technical documentation — system architecture, components, training procedures documented.",
|
|
23
|
-
"
|
|
24
|
-
"
|
|
40
|
+
"codeTestable": "partial",
|
|
41
|
+
"evidence": [
|
|
42
|
+
"AIBOM artifact at .agentic-security/aibom.json.",
|
|
43
|
+
"Threat model + DPIA + compliance-evidence present."
|
|
44
|
+
],
|
|
45
|
+
"mapsTo": [
|
|
46
|
+
"module:aibom",
|
|
47
|
+
"module:threat-model-auto",
|
|
48
|
+
"module:compliance-policy"
|
|
49
|
+
]
|
|
25
50
|
},
|
|
26
51
|
{
|
|
27
52
|
"id": "Art.12",
|
|
28
53
|
"summary": "Record-keeping — automatic logging of system events for traceability.",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
54
|
+
"codeTestable": "partial",
|
|
55
|
+
"evidence": [
|
|
56
|
+
"MCP audit log .agentic-security/mcp-audit.log present.",
|
|
57
|
+
"Scan history retained."
|
|
58
|
+
],
|
|
59
|
+
"mapsTo": [
|
|
60
|
+
"module:mcp-audit",
|
|
61
|
+
"module:scan-history"
|
|
62
|
+
]
|
|
31
63
|
},
|
|
32
64
|
{
|
|
33
65
|
"id": "Art.13",
|
|
34
66
|
"summary": "Transparency — instructions for use enable users to interpret the system's output correctly.",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
67
|
+
"codeTestable": "partial",
|
|
68
|
+
"evidence": [
|
|
69
|
+
"why-fired annotation surfaces detection provenance on every finding."
|
|
70
|
+
],
|
|
71
|
+
"mapsTo": [
|
|
72
|
+
"module:why-fired"
|
|
73
|
+
]
|
|
37
74
|
},
|
|
38
75
|
{
|
|
39
76
|
"id": "Art.14",
|
|
40
77
|
"summary": "Human oversight — system permits humans to override / interrupt.",
|
|
41
|
-
"
|
|
42
|
-
"
|
|
78
|
+
"codeTestable": "partial",
|
|
79
|
+
"evidence": [
|
|
80
|
+
"Fix application requires confirm:true.",
|
|
81
|
+
"Bodyguard hook can refuse risky edits."
|
|
82
|
+
],
|
|
83
|
+
"mapsTo": [
|
|
84
|
+
"module:pre-edit-bodyguard",
|
|
85
|
+
"module:apply-fix"
|
|
86
|
+
]
|
|
43
87
|
},
|
|
44
88
|
{
|
|
45
89
|
"id": "Art.15",
|
|
46
90
|
"summary": "Accuracy, robustness, cybersecurity — appropriate level of accuracy and resilience.",
|
|
47
|
-
"
|
|
48
|
-
"
|
|
91
|
+
"codeTestable": "partial",
|
|
92
|
+
"evidence": [
|
|
93
|
+
"Calibration + held-out evaluation present (.agentic-security/calibration-seed.json).",
|
|
94
|
+
"OWASP Benchmark regression gate."
|
|
95
|
+
],
|
|
96
|
+
"mapsTo": [
|
|
97
|
+
"module:calibration",
|
|
98
|
+
"module:holdout-eval"
|
|
99
|
+
]
|
|
49
100
|
}
|
|
50
101
|
]
|
|
51
102
|
}
|
|
@@ -4,42 +4,86 @@
|
|
|
4
4
|
"publisher": "European Parliament & Council",
|
|
5
5
|
"license": "EU law (Official Journal)",
|
|
6
6
|
"url": "https://eur-lex.europa.eu/eli/reg/2016/679/oj",
|
|
7
|
+
"scope": "SELECTIVE SUBSET. 6 articles where a code scanner can produce evidence (security of processing, data minimisation, DPIA inputs). GDPR has 99 articles; lawful basis, data-subject rights, transfers and records of processing are organisational and are NOT represented here.",
|
|
8
|
+
"controlsDigest": "749f9dad2b5657cb",
|
|
9
|
+
"controlCount": 6,
|
|
7
10
|
"controls": [
|
|
8
11
|
{
|
|
9
12
|
"id": "Art.5",
|
|
10
13
|
"summary": "Principles relating to processing of personal data (lawfulness, fairness, transparency, purpose limitation, data minimization, accuracy, storage limitation, integrity, accountability).",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
14
|
+
"codeTestable": "yes",
|
|
15
|
+
"evidence": [
|
|
16
|
+
"DPIA artifact at .agentic-security/dpia.md.",
|
|
17
|
+
"PII fields enumerated by the privacy-taint module."
|
|
18
|
+
],
|
|
19
|
+
"mapsTo": [
|
|
20
|
+
"family:pii-exposure",
|
|
21
|
+
"module:privacy-taint"
|
|
22
|
+
]
|
|
13
23
|
},
|
|
14
24
|
{
|
|
15
25
|
"id": "Art.25",
|
|
16
26
|
"summary": "Data protection by design and by default.",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
27
|
+
"codeTestable": "partial",
|
|
28
|
+
"evidence": [
|
|
29
|
+
"Threat model produced before code.",
|
|
30
|
+
"PII annotation runs at scan time, not as an afterthought."
|
|
31
|
+
],
|
|
32
|
+
"mapsTo": [
|
|
33
|
+
"module:threat-model-auto",
|
|
34
|
+
"module:privacy-taint"
|
|
35
|
+
]
|
|
19
36
|
},
|
|
20
37
|
{
|
|
21
38
|
"id": "Art.30",
|
|
22
39
|
"summary": "Records of processing activities — controller maintains a register.",
|
|
23
|
-
"
|
|
24
|
-
"
|
|
40
|
+
"codeTestable": "partial",
|
|
41
|
+
"evidence": [
|
|
42
|
+
"DPIA + privacy-taint annotations cover each PII field with file:line provenance."
|
|
43
|
+
],
|
|
44
|
+
"mapsTo": [
|
|
45
|
+
"module:privacy-taint"
|
|
46
|
+
]
|
|
25
47
|
},
|
|
26
48
|
{
|
|
27
49
|
"id": "Art.32",
|
|
28
50
|
"summary": "Security of processing — appropriate technical and organizational measures.",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
51
|
+
"codeTestable": "yes",
|
|
52
|
+
"evidence": [
|
|
53
|
+
"Zero open critical crypto findings.",
|
|
54
|
+
"Auth-missing findings zero on user-data endpoints.",
|
|
55
|
+
"TLS pinning checks pass."
|
|
56
|
+
],
|
|
57
|
+
"mapsTo": [
|
|
58
|
+
"family:crypto-weak-cipher",
|
|
59
|
+
"family:crypto-tls-version",
|
|
60
|
+
"family:crypto-tls-no-verify",
|
|
61
|
+
"family:auth-missing"
|
|
62
|
+
]
|
|
31
63
|
},
|
|
32
64
|
{
|
|
33
65
|
"id": "Art.33",
|
|
34
66
|
"summary": "Notification of personal data breach to the supervisory authority within 72 hours.",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
67
|
+
"codeTestable": "partial",
|
|
68
|
+
"evidence": [
|
|
69
|
+
"Fix history retained.",
|
|
70
|
+
"Audit log preserves the breach-window evidence."
|
|
71
|
+
],
|
|
72
|
+
"mapsTo": [
|
|
73
|
+
"module:fix-history",
|
|
74
|
+
"module:mcp-audit"
|
|
75
|
+
]
|
|
37
76
|
},
|
|
38
77
|
{
|
|
39
78
|
"id": "Art.35",
|
|
40
79
|
"summary": "Data protection impact assessment (DPIA) for high-risk processing.",
|
|
41
|
-
"
|
|
42
|
-
"
|
|
80
|
+
"codeTestable": "partial",
|
|
81
|
+
"evidence": [
|
|
82
|
+
"DPIA artifact present at .agentic-security/dpia.md."
|
|
83
|
+
],
|
|
84
|
+
"mapsTo": [
|
|
85
|
+
"module:privacy-taint:emitDpiaArtifact"
|
|
86
|
+
]
|
|
43
87
|
}
|
|
44
88
|
]
|
|
45
89
|
}
|