@clear-capabilities/agentic-security-scanner 0.163.1 → 0.164.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 +14 -0
- package/dist/9560.index.js +22 -8
- package/dist/agentic-security.mjs +1 -1
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +4 -4
- package/src/capabilities/CLAUDE.md +15 -11
- package/src/capabilities/probes.js +22 -8
- package/src/sandbox/CLAUDE.md +49 -17
- package/src/sandbox/backend-namespace.js +223 -109
- package/src/sandbox/capabilities.js +12 -0
- package/src/sandbox/control-probes.js +54 -4
- package/src/sandbox/linux-probes.js +178 -0
- package/src/sandbox/linux-rootfs.js +256 -0
- package/src/sandbox/supervise.js +31 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
cca41fc14779bcf557d1a825a32e919b51bcd4c59d73e814d37ab2ed93ca8278 agentic-security.mjs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clear-capabilities/agentic-security-scanner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.164.0",
|
|
4
4
|
"description": "Scanner engine for the agentic-security Claude Code plugin: SAST, SCA with reachability and CISA KEV, secrets, IaC, Haskell and Nix/NixOS, prompt-injection, MCP/agent-tool audit, authZ analysis, SBOM, compliance attestation, and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"test:extras": "AGENTIC_SECURITY_CPP_DATAFLOW=1 node --test test/cpp-dataflow.test.js && node --experimental-test-module-mocks --test test/fault-injection.test.js && node --experimental-test-module-mocks --test test/detector-fault-injection.test.js && node --experimental-test-module-mocks --test test/detector-fault-injection-k8s-admission.test.js && node --experimental-test-module-mocks --test test/lineage-fault-injection.test.js && npm run test:python",
|
|
69
69
|
"loop": "node ../scripts/loop-engineering/run.mjs",
|
|
70
70
|
"test:loop-real-prd": "node --test ../scripts/loop-engineering/test/loop-real-prd.test.js",
|
|
71
|
-
"test:loop": "node --test --test-concurrency=1 ../scripts/loop-engineering/test/loop-manifest.test.js ../scripts/loop-engineering/test/loop-watchdog.test.js ../scripts/loop-engineering/test/loop-background.test.js ../scripts/loop-engineering/test/loop-dashboard.test.js ../scripts/loop-engineering/test/loop-evidence.test.js ../scripts/loop-engineering/test/loop-recovery.test.js ../scripts/loop-engineering/test/loop-remote.test.js ../scripts/loop-engineering/test/loop-runbook.test.js ../scripts/loop-engineering/test/loop-assurance-profile.test.js ../scripts/loop-engineering/test/loop-assurance-runtime.test.js",
|
|
71
|
+
"test:loop": "node --test --test-concurrency=1 ../scripts/loop-engineering/test/loop-manifest.test.js ../scripts/loop-engineering/test/loop-watchdog.test.js ../scripts/loop-engineering/test/loop-background.test.js ../scripts/loop-engineering/test/loop-dashboard.test.js ../scripts/loop-engineering/test/loop-evidence.test.js ../scripts/loop-engineering/test/loop-recovery.test.js ../scripts/loop-engineering/test/loop-remote.test.js ../scripts/loop-engineering/test/loop-runbook.test.js ../scripts/loop-engineering/test/loop-assurance-profile.test.js ../scripts/loop-engineering/test/loop-assurance-runtime.test.js ../scripts/loop-engineering/test/loop-bounds.test.js ../scripts/loop-engineering/test/loop-spend.test.js",
|
|
72
72
|
"test:python": "node ../scripts/run-python-tests.mjs",
|
|
73
73
|
"test:smoke": "node --test test/smoke.test.js test/demo-app.test.js",
|
|
74
74
|
"test:ollama-e2e": "AGENTIC_SECURITY_OLLAMA_E2E=1 node --test test/ollama-e2e.test.js",
|
|
@@ -106,11 +106,11 @@
|
|
|
106
106
|
"evaluation": "node ../scripts/evaluation.mjs",
|
|
107
107
|
"evaluation:synthetic": "node ../scripts/evaluation.mjs synthetic",
|
|
108
108
|
"test:routing": "node --test test/routing/outcomes.test.js test/routing/economics.test.js test/routing/adapter.test.js test/routing/calibration.test.js test/routing/decide.test.js test/routing/unchanged-when-off.test.js test/routing/shadow.test.js test/routing/promotion.test.js test/routing/drift.test.js test/routing/canary.test.js test/routing/feedback.test.js test/routing/report.test.js",
|
|
109
|
-
"test:release-closure": "node --test test/release-closure/closure-plan.test.js test/release-closure/closure-record.test.js test/release-closure/compatibility.test.js test/release-closure/rollout-stages.test.js test/release-closure/migration-readers.test.js test/release-closure/rollback.test.js test/release-closure/wrappers.test.js test/release-closure/final-closure.test.js test/release-closure/final-closure-controller.test.js test/release-closure/runner-hardening.test.js",
|
|
109
|
+
"test:release-closure": "node --test test/release-closure/closure-plan.test.js test/release-closure/closure-record.test.js test/release-closure/compatibility.test.js test/release-closure/rollout-stages.test.js test/release-closure/migration-readers.test.js test/release-closure/rollback.test.js test/release-closure/wrappers.test.js test/release-closure/final-closure.test.js test/release-closure/final-closure-controller.test.js test/release-closure/runner-hardening.test.js test/release-closure/remote-attestation.test.js",
|
|
110
110
|
"release:closure:check": "node ../scripts/release-closure.mjs --run",
|
|
111
111
|
"release:closure:static": "node ../scripts/release-closure.mjs --static",
|
|
112
112
|
"test:documentation": "node --test test/documentation/drift-check.test.js test/documentation/generated-pages.test.js test/documentation/scope-and-examples.test.js test/documentation/policy-cards.test.js test/documentation/operations-and-review.test.js",
|
|
113
|
-
"test:capabilities": "node --test test/capabilities/manifest.test.js test/capabilities/filesystem.test.js test/capabilities/exec.test.js test/capabilities/network.test.js test/capabilities/canary.test.js test/capabilities/tools.test.js test/capabilities/recovery.test.js test/capabilities/receipts.test.js test/capabilities/adversarial.test.js",
|
|
113
|
+
"test:capabilities": "node --test test/capabilities/manifest.test.js test/capabilities/filesystem.test.js test/capabilities/exec.test.js test/capabilities/network.test.js test/capabilities/canary.test.js test/capabilities/tools.test.js test/capabilities/recovery.test.js test/capabilities/receipts.test.js test/capabilities/adversarial.test.js test/capabilities/linux-enforcement.test.js",
|
|
114
114
|
"test:verification": "node --test test/verification/verification-record.test.js test/verification/oracle-adapters.test.js test/verification/replay-manifest.test.js test/verification/patch-negative.test.js test/verification/non-taint-classes.test.js test/verification/interface-equivalence.test.js test/verification/advisory-gating.test.js test/verification/oracle-conformance.test.js",
|
|
115
115
|
"test:deployment": "node --test test/deployment/boundary-graph.test.js test/deployment/config-adapters.test.js test/deployment/trace-correlation.test.js test/deployment/cross-repo.test.js test/deployment/attack-paths.test.js test/deployment/drift.test.js test/deployment/boundary-presentation.test.js test/deployment/differentiation-ablation.test.js test/deployment/boundaries-cli.test.js",
|
|
116
116
|
"test:portfolio": "node --test test/portfolio/manifest.test.js test/portfolio/bundle.test.js test/portfolio/signing.test.js test/portfolio/work-units.test.js test/portfolio/resume.test.js test/portfolio/scheduler.test.js test/portfolio/progress.test.js test/portfolio/retention.test.js test/portfolio/backend.test.js",
|
|
@@ -67,15 +67,20 @@ report says so. Without the opt-in an isolation-required task is `unsupported`
|
|
|
67
67
|
(`platform-unsupported`). A control that is not proved BLOCKS the run (typed
|
|
68
68
|
`blocked`, `missing-execution-backend`, the target never executes).
|
|
69
69
|
|
|
70
|
-
**Linux is `
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
caps are
|
|
70
|
+
**Linux is `partially-verified`**, and only by the hosted `sandbox-linux` job. Its
|
|
71
|
+
active probes proved write confinement, read denial, environment scrubbing, the
|
|
72
|
+
no-network default, tree termination, the file-size limit, read confinement to
|
|
73
|
+
declared roots and multi-root writes on the namespace backend (a pivoted tmpfs
|
|
74
|
+
root; protected paths absent or masked; a PID namespace the kernel tears down).
|
|
75
|
+
NOT verified: **mediated network** is not implemented on Linux (an empty network
|
|
76
|
+
namespace has no path to a proxy), so a task that declares a destination is
|
|
77
|
+
`blocked` there with `network-mediation is unsupported`, never allowed; and
|
|
78
|
+
**process-count caps** are never asserted on any backend (on Linux the cause of
|
|
79
|
+
the earlier non-refusal is that the resource prelude runs under dash, whose
|
|
80
|
+
`ulimit` has no `-u`; no replacement is proved). `maxProcesses` is carried and
|
|
81
|
+
reported `unverified`, memory caps `not-enforced`. The execution suites under
|
|
82
|
+
`test/capabilities/` still skip on Linux (their helpers select the userspace
|
|
83
|
+
backend only and several assertions encode macOS semantics); see the gaps below.
|
|
79
84
|
|
|
80
85
|
## Known limits (also in `REPORT_LIMITATIONS`)
|
|
81
86
|
|
|
@@ -107,8 +112,7 @@ attempts were stopped on this backend in this run; it does not show the sandbox
|
|
|
107
112
|
### What is still not claimed
|
|
108
113
|
|
|
109
114
|
Tool and delegation checks are policy at the tool boundary (the tool then runs in the server process): the
|
|
110
|
-
runner remains the only enforcement layer. Linux
|
|
111
|
-
record, and no Linux outcome is asserted. No process-count cap is asserted. The corpus is not wired into the
|
|
115
|
+
runner remains the only enforcement layer. Linux is `partially-verified` in the platform statement (the controls listed there, on the hosted job only) and the attack-coverage record still says `unverified` for Linux, because the corpus is not run on Linux. No process-count cap is asserted. The corpus is not wired into the
|
|
112
116
|
`sandbox-linux` CI job: it would need execution cases that skip loudly (never silently pass) on Linux, and that
|
|
113
117
|
has not been shown from this workspace. The enforced-mode release gate (`enforcedModeReleaseGate`) is a library
|
|
114
118
|
function used by the suite; it is not yet called from `scripts/release-check.mjs`.
|
|
@@ -48,15 +48,18 @@ function mk(prefix) { return fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(
|
|
|
48
48
|
function rm(p) { try { fs.rmSync(p, { recursive: true, force: true }); } catch { /* best effort */ } }
|
|
49
49
|
const token = (p) => `${p}-${crypto.randomBytes(8).toString('hex')}`;
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
// `run` is a seam so a test can hand the probe a runner that is deliberately
|
|
52
|
+
// wrong (a read root left too wide, a root left writable) and see the probe say
|
|
53
|
+
// so: a probe that cannot fail proves nothing.
|
|
54
|
+
export async function probeFsRead(backend, runner = runConfinedSupervised) {
|
|
55
|
+
if (backend !== 'userspace' && backend !== 'namespace') return unsupported(`read confinement to declared roots is not implemented on the ${backend} backend`);
|
|
53
56
|
const root = mk('agsec-cap-r-'); const ro = mk('agsec-cap-ro-'); const outside = mk('agsec-cap-out-');
|
|
54
57
|
try {
|
|
55
58
|
const okTok = token('READABLE'); const secTok = token('SEALED');
|
|
56
59
|
fs.writeFileSync(path.join(ro, 'r.txt'), okTok);
|
|
57
60
|
fs.writeFileSync(path.join(outside, 's.txt'), secTok);
|
|
58
61
|
fs.symlinkSync(path.join(outside, 's.txt'), path.join(ro, 'link'));
|
|
59
|
-
const run = (script, readRoots) =>
|
|
62
|
+
const run = (script, readRoots) => runner(['/bin/sh', '-c', script], { root, readRoots, timeoutMs: 8000, graceMs: 300 });
|
|
60
63
|
const pos = await run(`cat '${ro}/r.txt'`, [ro]);
|
|
61
64
|
if (!pos.stdout.includes(okTok)) return notProved(`positive control failed: a file inside a declared root was not readable (${pos.status})`);
|
|
62
65
|
const open = await run(`cat '${outside}/s.txt'`, [ro, outside]);
|
|
@@ -75,13 +78,13 @@ async function probeFsRead(backend) {
|
|
|
75
78
|
} finally { rm(root); rm(ro); rm(outside); }
|
|
76
79
|
}
|
|
77
80
|
|
|
78
|
-
async function probeMultiWrite(backend) {
|
|
79
|
-
if (backend !== 'userspace') return unsupported(`several declared write roots are not implemented on the ${backend} backend`);
|
|
81
|
+
export async function probeMultiWrite(backend, runner = runConfinedSupervised) {
|
|
82
|
+
if (backend !== 'userspace' && backend !== 'namespace') return unsupported(`several declared write roots are not implemented on the ${backend} backend`);
|
|
80
83
|
const root = mk('agsec-cap-w-'); const w1 = mk('agsec-cap-w1-'); const w2 = mk('agsec-cap-w2-');
|
|
81
84
|
const ro = mk('agsec-cap-wro-'); const outside = mk('agsec-cap-wout-');
|
|
82
85
|
try {
|
|
83
86
|
const script = [`echo a > '${w1}/f'`, `echo b > '${w2}/f'`, `echo c > '${ro}/f'`, `echo d > '${outside}/f'`, 'true'].join('; ');
|
|
84
|
-
await
|
|
87
|
+
await runner(['/bin/sh', '-c', script], { root, readRoots: [ro], writeRoots: [w1, w2], timeoutMs: 8000, graceMs: 300 });
|
|
85
88
|
if (!fs.existsSync(path.join(w1, 'f')) || !fs.existsSync(path.join(w2, 'f'))) return notProved('positive control failed: a declared write root was not writable');
|
|
86
89
|
if (fs.existsSync(path.join(ro, 'f'))) return notProved('a root declared read-only was writable');
|
|
87
90
|
if (fs.existsSync(path.join(outside, 'f'))) return notProved('an undeclared directory was writable');
|
|
@@ -197,8 +200,19 @@ export function isAdvertisedBackend(backend, platform = process.platform) {
|
|
|
197
200
|
export function platformStatements() {
|
|
198
201
|
return Object.freeze({
|
|
199
202
|
linux: {
|
|
200
|
-
backend: 'namespace', status: '
|
|
201
|
-
|
|
203
|
+
backend: 'namespace', status: 'partially-verified',
|
|
204
|
+
// Evidence is the hosted sandbox-linux CI job (ubuntu-latest, x86_64), whose active probes
|
|
205
|
+
// (attack plus positive control, with fault-injection counterparts) proved these controls.
|
|
206
|
+
// Anything not listed here is not claimed: a verified control is verified on that runner image
|
|
207
|
+
// and kernel, not on every Linux host, which is why the job runs on every push.
|
|
208
|
+
evidence: 'sandbox-linux CI job (hosted ubuntu-latest, x86_64); scripts/sandbox-linux-verify.mjs',
|
|
209
|
+
verifiedControls: Object.freeze([
|
|
210
|
+
'write-confinement', 'read-denial', 'env-scrub', 'network', 'tree-termination', 'file-size-limit',
|
|
211
|
+
'fs-read-confinement', 'fs-multi-root-write',
|
|
212
|
+
]),
|
|
213
|
+
unsupportedControls: Object.freeze(['network-mediation']),
|
|
214
|
+
unassertedControls: Object.freeze(['process-cap']),
|
|
215
|
+
note: 'advertised for enforced mode. Proved by active probes on the hosted Linux runner (not on every Linux host): write-confinement, read-denial, env-scrub, network, tree-termination, file-size-limit, fs-read-confinement, fs-multi-root-write. Mediated network is NOT implemented on the namespace backend (an empty network namespace has no path to a proxy), so a task that declares a network destination is blocked on Linux, never allowed. Process-count caps are never claimed: the cause of the earlier non-refusal was found (the shell used for the resource prelude has no process-limit option, so the cap was never applied) and no replacement has been proved. The attack corpus is not run in the sandbox-linux job, so corpus coverage stays unverified on Linux.',
|
|
202
216
|
},
|
|
203
217
|
darwin: {
|
|
204
218
|
backend: 'userspace', status: 'host-proved-not-advertised',
|
package/src/sandbox/CLAUDE.md
CHANGED
|
@@ -281,9 +281,9 @@ privilegeDrop`), the same mechanism `limits.js` uses for an unenforceable
|
|
|
281
281
|
limit. It is never silently skipped, and the escape test that covers the
|
|
282
282
|
rebind attack fails if it is missing rather than quietly passing.
|
|
283
283
|
|
|
284
|
-
**
|
|
285
|
-
|
|
286
|
-
|
|
284
|
+
**Executed on a hosted Linux runner.** The `sandbox-linux` job ran the suite
|
|
285
|
+
(see the verification sections below); this section describes code that has
|
|
286
|
+
been exercised, on that runner image and kernel only.
|
|
287
287
|
|
|
288
288
|
## Timeout does not kill the process tree
|
|
289
289
|
|
|
@@ -297,18 +297,24 @@ inside the policy profile — their writes and egress remain confined — but th
|
|
|
297
297
|
are still running and still consuming resources. A caller that needs a hard
|
|
298
298
|
tree kill must implement it.
|
|
299
299
|
|
|
300
|
-
The namespace backend
|
|
301
|
-
|
|
302
|
-
namespace
|
|
303
|
-
|
|
304
|
-
|
|
300
|
+
The namespace backend closes this with the kernel, not a sweep: the command
|
|
301
|
+
runs under `--pid --fork --kill-child`, so when the `unshare` supervisor dies
|
|
302
|
+
the namespace's init gets SIGKILL and the kernel kills every member, including
|
|
303
|
+
a double-forked `setsid` descendant that no process-group or sweep logic can
|
|
304
|
+
find. `--kill-child` is used only where `unshare` advertises it
|
|
305
|
+
(`namespaceTreeKill`); without it supervised execution is refused. Proved by
|
|
306
|
+
the `linux-probes.js` probes on the hosted runner (kill the supervisor, kill
|
|
307
|
+
the init directly). Earlier text here said killing the direct child should reap
|
|
308
|
+
the tree; CI showed it did not (the killed process was the supervisor, not the
|
|
309
|
+
init) until `--kill-child` was added.
|
|
305
310
|
|
|
306
311
|
## Known limitation, deliberately accepted: reads are not confined
|
|
307
312
|
|
|
308
313
|
The userspace policy allows `(allow file-read*)` globally — that backend
|
|
309
314
|
confines **writes**, network egress, and resource use, but **not reads**. (The
|
|
310
|
-
kernel-namespace backend has the same cut
|
|
311
|
-
tree is rebound read-only, not detached, so everything on it stays readable.
|
|
315
|
+
kernel-namespace backend has the same cut in its DEFAULT mode: its mount
|
|
316
|
+
tree is rebound read-only, not detached, so everything on it stays readable.
|
|
317
|
+
Its capability mode, entered with `readRoots`, does confine reads; see below.)
|
|
312
318
|
A confined command can read any file on the host the OS-level
|
|
313
319
|
permissions allow, including outside the sandbox root. Exfiltration of
|
|
314
320
|
readable host files (writing what was read to network or to a location the
|
|
@@ -364,20 +370,26 @@ then runs supervised with protected paths read-denied.
|
|
|
364
370
|
default protected set is the key directory, common credential directories
|
|
365
371
|
under the home directory, plus caller-supplied sealed-label and evidence
|
|
366
372
|
directories. A denylist is a floor, not a claim of host-wide read isolation.
|
|
367
|
-
The namespace backend
|
|
368
|
-
|
|
373
|
+
The namespace backend implements it by masking: an empty tmpfs over a
|
|
374
|
+
protected directory, `/dev/null` over a protected file (in capability mode a
|
|
375
|
+
protected path outside every declared root is simply absent). Proved by the
|
|
376
|
+
protected-canary probes on the hosted runner.
|
|
369
377
|
- **Process trees** (`supervise.js`): own process group, periodic descendant
|
|
370
378
|
sweep, SIGTERM, grace, SIGKILL, then a poll that confirms no survivors; the
|
|
371
379
|
same cleanup runs on a clean exit. Cancel is an `AbortSignal` (status
|
|
372
380
|
`cancelled`). Output is capped. Known gap: a double-fork plus setsid between
|
|
373
381
|
two sweeps escapes; only a PID namespace or cgroup closes it. Supervised
|
|
374
|
-
execution exists
|
|
375
|
-
|
|
382
|
+
execution exists for the userspace backend and for the namespace backend
|
|
383
|
+
where `--kill-child` is available (see above).
|
|
376
384
|
- **Active probes**: write confinement, read denial, env scrub, network, tree
|
|
377
385
|
termination and the file-size limit each run an attack plus a positive
|
|
378
386
|
control through the real backend. `process-cap` is reported `unverified` on
|
|
379
|
-
every backend and must not be claimed enforced
|
|
380
|
-
macOS
|
|
387
|
+
every backend and must not be claimed enforced. Per-uid and system-wide on
|
|
388
|
+
macOS. On Linux the cause of the earlier non-refusal is now known: the
|
|
389
|
+
resource prelude is run by `/bin/sh`, which is dash on the hosted runner, and
|
|
390
|
+
dash's `ulimit` has no `-u` option (`ulimit: Illegal option -u` appears in
|
|
391
|
+
the `sandbox-linux` log), so the cap was never applied at all. No replacement
|
|
392
|
+
(for example `prlimit`) has been proved, so the cap stays unasserted.
|
|
381
393
|
`capabilityReport()` carries the standing note that Linux enforcement is
|
|
382
394
|
evidenced only where the `sandbox-linux` CI job ran the same probes.
|
|
383
395
|
- **Status**: target output is returned labelled `untrusted` and is never parsed
|
|
@@ -396,7 +408,27 @@ then runs supervised with protected paths read-denied.
|
|
|
396
408
|
- `writeRoots`: further writable subtrees (readable too). `cwd`: working directory, which must exist.
|
|
397
409
|
- `networkProxyPort`: opens exactly one loopback port (`(remote ip "localhost:PORT")`); DNS, every other port and every other address stay denied.
|
|
398
410
|
|
|
399
|
-
Verified by execution on macOS only (`test/capabilities/`, probes in `capabilities/probes.js`). The namespace backend implements
|
|
411
|
+
Verified by execution on macOS only (`test/capabilities/`, probes in `capabilities/probes.js`). The namespace backend implements `readRoots`, `writeRoots`, `cwd` and
|
|
412
|
+
`denyReadPaths` as **capability mode** (entered when `readRoots` is an array): a
|
|
413
|
+
tmpfs root holding only the runtime baseline (`/usr`, `/bin`, `/lib*`, a few
|
|
414
|
+
identity files, a fresh `/proc` and a minimal `/dev`) plus the declared roots,
|
|
415
|
+
read-only for read roots and read-write for write roots, protected paths absent
|
|
416
|
+
or masked, then `pivot_root` and a detached old root (`linux-rootfs.js` builds
|
|
417
|
+
the plan and the setup script; `backend-namespace.js` runs it). Capability drop
|
|
418
|
+
(`setpriv`, with no-new-privs) is required in this mode, not best effort.
|
|
419
|
+
`networkProxyPort` is REFUSED: mediated network is not implemented on Linux, so
|
|
420
|
+
a task that declares a destination is blocked there.
|
|
421
|
+
|
|
422
|
+
### Linux evidence
|
|
423
|
+
|
|
424
|
+
`linux-probes.js` and the generic probes in `control-probes.js` and
|
|
425
|
+
`capabilities/probes.js` attack each control and pair it with a positive
|
|
426
|
+
control; `test/capabilities/linux-enforcement.test.js` also runs every probe
|
|
427
|
+
against a deliberately wrong runner (fault injection) and requires it to say so.
|
|
428
|
+
`scripts/sandbox-linux-verify.mjs` (the `sandbox-linux` job) fails if a required
|
|
429
|
+
suite skips, a required control is not proved, or a control that must stay
|
|
430
|
+
unasserted claims to be proved, and uploads the evidence table. Exploratory runs
|
|
431
|
+
on a local aarch64 VM are not evidence; only the hosted x86_64 job is.
|
|
400
432
|
|
|
401
433
|
## Extending this module
|
|
402
434
|
|