@clear-capabilities/agentic-security-scanner 0.147.5 → 0.148.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 +77 -0
- package/bin/agentic-security.js +47 -15
- package/dist/agentic-security.mjs +2 -2
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/dist/compliance-frameworks/nist-800-171-r3.json +1269 -0
- package/package.json +1 -1
- package/src/posture/auditor-walkthrough.js +89 -6
- package/src/posture/compliance-frameworks/nist-800-171-r3.json +1269 -0
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,83 @@
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
## 0.148.0 - NIST SP 800-171 Rev. 3 (CUI / CMMC basis) as the 10th bundled framework
|
|
15
|
+
|
|
16
|
+
Adds NIST SP 800-171 Rev. 3 to `/compliance --report <framework>` (aliases
|
|
17
|
+
`800-171` and `cui`), `--walkthrough`, `--gap` and `--format oscal`, plus a
|
|
18
|
+
standalone deep-attestation scanner at `scripts/nist-800-171/scan.py`.
|
|
19
|
+
|
|
20
|
+
**All 97 requirements are carried, including the 43 this engine cannot assess.**
|
|
21
|
+
Shipping only the code-observable subset would have been a smaller, better-looking
|
|
22
|
+
artifact and a dishonest one, omission reads as coverage. Whole families
|
|
23
|
+
(Awareness and Training, Personnel Security, Physical Protection) and most of
|
|
24
|
+
Incident Response, Maintenance and Media Protection report as requiring manual
|
|
25
|
+
evidence. Ratings: 16 `yes`, 38 `partial`, 43 `no`.
|
|
26
|
+
|
|
27
|
+
**The `code_testable` rating is ours, not NIST's.** Unlike the AI 600-1 workbook,
|
|
28
|
+
the 800-171 export rates no control for testability. That judgment lives in
|
|
29
|
+
`scripts/nist-800-171/code-testability.json` with a per-requirement rationale, and
|
|
30
|
+
is joined into the generated catalog by `build-catalog.py`. A control with no
|
|
31
|
+
rating is a hard build failure, never a default, defaulting would either invent
|
|
32
|
+
coverage or silently suppress a requirement. The generator is stdlib-only, so
|
|
33
|
+
unlike the openpyxl-based AI 600-1 gate its drift check can never be unrunnable
|
|
34
|
+
for a missing dependency.
|
|
35
|
+
|
|
36
|
+
**Fixes a real pre-existing bug in the shared evaluator.** `evaluateFramework`,
|
|
37
|
+
behind `--report`/`--walkthrough`/`--gap`/`--format oscal` for *all ten* frameworks
|
|
38
|
+
had no vacuous-satisfaction guard: a scan that read zero files produced empty
|
|
39
|
+
finding buckets, and an empty bucket rendered as `✓ no open findings`. Measured on
|
|
40
|
+
800-171, **32 of 97 controls read `present` off a scan that examined nothing**. The
|
|
41
|
+
guard existed only inside `privacy-framework.js`, whose own comment already named
|
|
42
|
+
`evaluateFramework` as where the hazard originates. The fix reads three count
|
|
43
|
+
fields (a real scan persists `scanned.files` and `_scanMeta.filesScanned` but *not*
|
|
44
|
+
top-level `filesScanned`, which exists only on the in-memory object) and treats
|
|
45
|
+
**absent as unknown, not zero**, degrading requires positive evidence that nothing
|
|
46
|
+
was examined, so a genuinely clean project is unaffected. 2419/2419 posture tests
|
|
47
|
+
pass with no test file modified.
|
|
48
|
+
|
|
49
|
+
**One scanning engine, not two.** `scripts/nist-compliance/scan.py` now reads its
|
|
50
|
+
framework identity from the catalog it is given rather than from hardcoded strings,
|
|
51
|
+
and the 800-171 scanner is a thin wrapper supplying defaults. This keeps exactly one
|
|
52
|
+
copy of the ReDoS-hardened matcher, two copies would mean a fix to one silently
|
|
53
|
+
missing the other. The AI 600-1 scanner's md/csv/json output was pinned before and
|
|
54
|
+
after and is byte-identical.
|
|
55
|
+
|
|
56
|
+
**Fixes OSCAL documents that could not be attested.** Every compliance OSCAL export
|
|
57
|
+
read its timestamp from `scan._scanMeta.startedAt`, a key scans do not have, they
|
|
58
|
+
carry `startedAt` at the top level. So `_when()` fell through to `new Date()` at emit
|
|
59
|
+
time, which that function's own comment says must never happen, because "two emits of
|
|
60
|
+
one scan then differ and the artifact cannot be attested." Two emits of the same scan
|
|
61
|
+
genuinely differed. Fixed at all three call sites (both in `bin/agentic-security.js`,
|
|
62
|
+
one in `commands/compliance.md`); `last-modified` is now the scan's own clock, and with
|
|
63
|
+
`AGENTIC_SECURITY_DETERMINISTIC=1` the document is byte-identical across runs. This
|
|
64
|
+
affected every framework, not just the new one.
|
|
65
|
+
|
|
66
|
+
Also: the `nist-catalog-freshness` release gate now iterates every generated catalog
|
|
67
|
+
instead of naming one script path, `docs/compliance/nist-800-171-r3-coverage.md` is
|
|
68
|
+
pointer-based rather than restating counts that would drift, and that page carries a
|
|
69
|
+
recorded walkthrough (`docs/brand/nist-800-171-demo.tape`, regenerable with `vhs`)
|
|
70
|
+
showing assess → remediate → re-assess against `examples/demo-app`.
|
|
71
|
+
|
|
72
|
+
**Fixes `module:scan-history`, which could never resolve.** The evaluator's artifact table
|
|
73
|
+
listed only `scan-history/` (a directory) while an ordinary scan writes `scan-history.json`
|
|
74
|
+
(a file), both spellings are real in this codebase (`findings-memory.js` uses the
|
|
75
|
+
directory; `security-trend.js` and `router.js` use the file), but only one was listed. Every
|
|
76
|
+
control mapped to it reported the artifact missing even when the history existed, across
|
|
77
|
+
five bundled frameworks. Table entries may now be an array of acceptable paths, and any one
|
|
78
|
+
of them evidences the control.
|
|
79
|
+
|
|
80
|
+
Measured effect, because a change that moves compliance verdicts should be quantified rather
|
|
81
|
+
than asserted: **no control anywhere became `satisfied`**, `present` held at 43 across all
|
|
82
|
+
five frameworks. Eight controls moved `manual` → `partial` (2 eu-ai-act, 1 hipaa, 4
|
|
83
|
+
nist-800-171-r3, 1 nist-ai-600-1, 1 nist-privacy-1-1). Those are artifact-existence controls,
|
|
84
|
+
which the honesty model already caps at `partial`, so finding the artifact can only move them
|
|
85
|
+
out of "not assessed", never to satisfied. In OSCAL terms the reports get *stricter*, not
|
|
86
|
+
more flattering: for 800-171 against the demo app, findings went 42 → 46 with satisfied
|
|
87
|
+
unchanged at 28, i.e. four controls moved from unassessed into assessed-and-not-satisfied.
|
|
88
|
+
|
|
89
|
+
No CMMC assessment and no SPRS score is produced or implied.
|
|
90
|
+
|
|
14
91
|
## 0.147.5 — Fix: `bench:provenance:check`'s cold-memory sample size was unreliable on GitHub Actions
|
|
15
92
|
|
|
16
93
|
`v0.147.4`'s tag was pushed but its hosted release-gate run failed before
|
package/bin/agentic-security.js
CHANGED
|
@@ -2447,7 +2447,7 @@ async function cmdCompliance(args) {
|
|
|
2447
2447
|
if (fmt === 'oscal') {
|
|
2448
2448
|
const { toOSCALCompliance, complianceRowsFromEvaluation } = await import('../src/report/oscal.js');
|
|
2449
2449
|
writeStdout(JSON.stringify(
|
|
2450
|
-
toOSCALCompliance(fw, complianceRowsFromEvaluation(evaluation), { startedAt: scan._scanMeta?.startedAt }),
|
|
2450
|
+
toOSCALCompliance(fw, complianceRowsFromEvaluation(evaluation), { startedAt: scan.startedAt || scan._scanMeta?.startedAt }),
|
|
2451
2451
|
null, 2) + '\n');
|
|
2452
2452
|
return 0;
|
|
2453
2453
|
}
|
|
@@ -2478,7 +2478,7 @@ async function cmdCompliance(args) {
|
|
|
2478
2478
|
const { toOSCALCompliance, complianceRowsFromPrivacy } = await import('../src/report/oscal.js');
|
|
2479
2479
|
const fwMeta = loadFramework(scanRoot, PRIVACY_FRAMEWORK_ID) || { id: PRIVACY_FRAMEWORK_ID, name: r.frameworkName };
|
|
2480
2480
|
writeStdout(JSON.stringify(
|
|
2481
|
-
toOSCALCompliance(fwMeta, complianceRowsFromPrivacy(r), { startedAt: scan._scanMeta?.startedAt }),
|
|
2481
|
+
toOSCALCompliance(fwMeta, complianceRowsFromPrivacy(r), { startedAt: scan.startedAt || scan._scanMeta?.startedAt }),
|
|
2482
2482
|
null, 2) + '\n');
|
|
2483
2483
|
return args.flags['fail-on'] === 'gap' && r.summary.gap > 0 ? 1 : 0;
|
|
2484
2484
|
}
|
|
@@ -6302,7 +6302,27 @@ async function cmdDataflowWatch(args) {
|
|
|
6302
6302
|
// change that makes `scan --watch` exit before it ever watches anything.
|
|
6303
6303
|
}
|
|
6304
6304
|
|
|
6305
|
+
// A version below the declared `engines.node` floor is still let through by
|
|
6306
|
+
// npm (EBADENGINE is a warning, not an install failure), so on an old Node
|
|
6307
|
+
// this file used to either crash deep inside a dependency with a confusing
|
|
6308
|
+
// stack, or — on the specific versions handled by the guard below — do
|
|
6309
|
+
// nothing at all. Fail fast, once, with a message that names the actual
|
|
6310
|
+
// requirement instead.
|
|
6311
|
+
function checkNodeVersionOrExit() {
|
|
6312
|
+
const required = __require('../package.json').engines?.node;
|
|
6313
|
+
const requiredMajor = Number(/(\d+)/.exec(required || '')?.[1]);
|
|
6314
|
+
const actualMajor = Number(process.versions.node.split('.')[0]);
|
|
6315
|
+
if (Number.isFinite(requiredMajor) && actualMajor < requiredMajor) {
|
|
6316
|
+
console.error(
|
|
6317
|
+
`agentic-security: requires Node.js ${required} — you're running Node ${process.versions.node}.\n` +
|
|
6318
|
+
`Upgrade Node (e.g. 'nvm install ${requiredMajor}' or https://nodejs.org/) and try again.`
|
|
6319
|
+
);
|
|
6320
|
+
process.exit(1);
|
|
6321
|
+
}
|
|
6322
|
+
}
|
|
6323
|
+
|
|
6305
6324
|
async function main() {
|
|
6325
|
+
checkNodeVersionOrExit();
|
|
6306
6326
|
const args = parseArgs(process.argv.slice(2));
|
|
6307
6327
|
const cmd = args._[0];
|
|
6308
6328
|
try {
|
|
@@ -6663,18 +6683,30 @@ async function main() {
|
|
|
6663
6683
|
// @clear-capabilities/agentic-security-scanner` install path), Node
|
|
6664
6684
|
// resolves `import.meta.url` to the symlink's realpath while
|
|
6665
6685
|
// `process.argv[1]` stays the symlink path as invoked, so the two never
|
|
6666
|
-
// match
|
|
6667
|
-
//
|
|
6668
|
-
//
|
|
6669
|
-
//
|
|
6670
|
-
//
|
|
6671
|
-
//
|
|
6672
|
-
//
|
|
6673
|
-
//
|
|
6674
|
-
//
|
|
6675
|
-
//
|
|
6676
|
-
//
|
|
6677
|
-
//
|
|
6678
|
-
|
|
6686
|
+
// match under a naive comparison. `import.meta.main` is resolved correctly
|
|
6687
|
+
// through a symlink — verified live through an actual symlink, not just
|
|
6688
|
+
// read about — see the Task 17 fix report. It was added in Node v24.2.0
|
|
6689
|
+
// (backported to v22.18.0) and is currently Stability 1.0 (early
|
|
6690
|
+
// development) per Node's own docs — NOT stable, and NOT available on
|
|
6691
|
+
// v20.11 as an earlier version of this comment incorrectly claimed.
|
|
6692
|
+
// Concretely: it is `undefined` on Node 24.0.0/24.1.x, which satisfy this
|
|
6693
|
+
// repo's declared `engines.node: ">=24.0.0"` floor, so `import.meta.main`
|
|
6694
|
+
// alone would reproduce this exact bug (main() silently never runs) on
|
|
6695
|
+
// those two point releases when invoked through the symlinked bin (npx/
|
|
6696
|
+
// global install) — the common case, not the exception. The fallback
|
|
6697
|
+
// below resolves `process.argv[1]`'s realpath before comparing, so it
|
|
6698
|
+
// gives the right answer through a symlink too — verified live against a
|
|
6699
|
+
// real symlink, same as the `import.meta.main` claim above — instead of
|
|
6700
|
+
// silently exiting with no output on every Node version where
|
|
6701
|
+
// `import.meta.main` is undefined (also: <22.18.0, and 23.x).
|
|
6702
|
+
function isDirectCliInvocation() {
|
|
6703
|
+
if (import.meta.main !== undefined) return import.meta.main;
|
|
6704
|
+
try {
|
|
6705
|
+
return import.meta.url === `file://${fs.realpathSync(process.argv[1])}`;
|
|
6706
|
+
} catch {
|
|
6707
|
+
return false;
|
|
6708
|
+
}
|
|
6709
|
+
}
|
|
6710
|
+
if (isDirectCliInvocation()) {
|
|
6679
6711
|
main();
|
|
6680
6712
|
}
|