@clear-capabilities/agentic-security-scanner 0.165.1 → 0.166.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.
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
37394bc97ebb4c287b4baa85822494bda80e330e4d55194ad5fb63598bce4eb0 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.166.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 ../scripts/loop-engineering/test/loop-bounds.test.js ../scripts/loop-engineering/test/loop-spend.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 ../scripts/loop-engineering/test/loop-ci-attest.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",
|
|
@@ -110,7 +110,7 @@
|
|
|
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 test/capabilities/linux-enforcement.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 test/supervise-async-sweep.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",
|
|
@@ -87,6 +87,14 @@ is ENOENT or EROFS rather than `denied`; liveness is judged by heartbeat, not ho
|
|
|
87
87
|
pid; a declared network destination blocks the task); `sandbox-linux` fails if any
|
|
88
88
|
of them skips.
|
|
89
89
|
|
|
90
|
+
`sandbox-linux` also writes a per-test report (`sandbox-linux-test-report`, artifact of the
|
|
91
|
+
job). The loop controller reads it back, for the exact commit, to accept the six
|
|
92
|
+
Linux-namespace tests that SKIP on a host without the backend
|
|
93
|
+
(`docs/guides/loop-engineering.md`, "Attested tests"). That is evidence from the
|
|
94
|
+
hosted run, reported as its own source (`attested-from-ci: sandbox-linux@<sha>`)
|
|
95
|
+
beside the unchanged local skip count; it is not a claim that the local host
|
|
96
|
+
enforced anything and it does not change `partially-verified`.
|
|
97
|
+
|
|
90
98
|
## Known limits (also in `REPORT_LIMITATIONS`)
|
|
91
99
|
|
|
92
100
|
Descendant exec is not allowlisted; ancestors of declared roots expose metadata;
|
package/src/sandbox/supervise.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// sweep interval bounds the window; it does not close it. Only a PID-namespace
|
|
23
23
|
// or cgroup boundary closes it, and macOS has neither. The namespace backend
|
|
24
24
|
// has the boundary (see `runConfinedSupervised`).
|
|
25
|
-
import { spawn,
|
|
25
|
+
import { spawn, execFile } from 'node:child_process';
|
|
26
26
|
import { detectBackend } from './capabilities.js';
|
|
27
27
|
import { runDisabled } from './backend-disabled.js';
|
|
28
28
|
import { buildUserspaceInvocation } from './backend-userspace.js';
|
|
@@ -40,12 +40,21 @@ function groupAlive(pgid) {
|
|
|
40
40
|
function signalPid(pid, sig) { try { process.kill(pid, sig); } catch { /* already gone */ } }
|
|
41
41
|
function signalGroup(pgid, sig) { try { process.kill(-pgid, sig); } catch { /* already gone */ } }
|
|
42
42
|
|
|
43
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Descendants of `rootPid` plus members of its group, from one `ps` snapshot. Asynchronous on purpose: a synchronous `ps` blocks the
|
|
45
|
+
* controller's event loop for as long as the machine takes to run it (tens of milliseconds idle, far more under load), and this runs on
|
|
46
|
+
* every sweep.
|
|
47
|
+
*/
|
|
44
48
|
function snapshotTree(rootPid) {
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
return new Promise((resolve) => {
|
|
50
|
+
execFile('ps', ['-A', '-o', 'pid=,ppid=,pgid='], { encoding: 'utf8', timeout: 2000, maxBuffer: 16 * 1024 * 1024 }, (err, stdout) => {
|
|
51
|
+
resolve(err ? [] : treeFromPs(stdout, rootPid));
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function treeFromPs(stdout, rootPid) {
|
|
47
56
|
const rows = [];
|
|
48
|
-
for (const line of String(
|
|
57
|
+
for (const line of String(stdout).split('\n')) {
|
|
49
58
|
const m = line.trim().split(/\s+/).map(Number);
|
|
50
59
|
if (m.length === 3 && m.every(Number.isFinite)) rows.push({ pid: m[0], ppid: m[1], pgid: m[2] });
|
|
51
60
|
}
|
|
@@ -86,27 +95,28 @@ export function superviseSpawn(bin, args, {
|
|
|
86
95
|
let finishing = null;
|
|
87
96
|
let timer = null;
|
|
88
97
|
|
|
89
|
-
const sweep = () => { if (pgid) for (const p of snapshotTree(pgid)) tracked.add(p); };
|
|
90
|
-
// sweepMs <= 0 turns the periodic sweep off
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
|
|
98
|
+
const sweep = async () => { if (pgid) for (const p of await snapshotTree(pgid)) tracked.add(p); };
|
|
99
|
+
// sweepMs <= 0 turns the periodic sweep off: on the namespace backend the kernel ends every member with the supervisor
|
|
100
|
+
// (--kill-child), so the periodic sweep adds nothing there. The cleanup below still takes its own snapshots. A sweep never overlaps the
|
|
101
|
+
// previous one, so a slow `ps` under load cannot pile up work.
|
|
102
|
+
let sweeping = false;
|
|
103
|
+
const sweeper = sweepMs > 0 ? setInterval(() => { if (sweeping) return; sweeping = true; sweep().finally(() => { sweeping = false; }); }, sweepMs) : null;
|
|
94
104
|
|
|
95
|
-
const survivors = () => {
|
|
96
|
-
sweep();
|
|
105
|
+
const survivors = async () => {
|
|
106
|
+
await sweep();
|
|
97
107
|
return { live: [...tracked].filter(alive), group: groupAlive(pgid) };
|
|
98
108
|
};
|
|
99
109
|
|
|
100
110
|
async function terminate() {
|
|
101
111
|
const t = _emptyTermination();
|
|
102
|
-
const before = survivors();
|
|
112
|
+
const before = await survivors();
|
|
103
113
|
if (!before.group && before.live.length === 0) return t;
|
|
104
114
|
t.signalled = 'SIGTERM';
|
|
105
115
|
signalGroup(pgid, 'SIGTERM');
|
|
106
116
|
for (const p of tracked) signalPid(p, 'SIGTERM');
|
|
107
117
|
const deadline = Date.now() + graceMs;
|
|
108
118
|
while (Date.now() < deadline) {
|
|
109
|
-
const s = survivors();
|
|
119
|
+
const s = await survivors();
|
|
110
120
|
if (!s.group && s.live.length === 0) return t;
|
|
111
121
|
await sleep(25);
|
|
112
122
|
}
|
|
@@ -115,13 +125,13 @@ export function superviseSpawn(bin, args, {
|
|
|
115
125
|
t.signalled = 'SIGKILL';
|
|
116
126
|
const hard = Date.now() + 3000;
|
|
117
127
|
while (Date.now() < hard) {
|
|
118
|
-
const s = survivors();
|
|
128
|
+
const s = await survivors();
|
|
119
129
|
if (!s.group && s.live.length === 0) break;
|
|
120
130
|
signalGroup(pgid, 'SIGKILL');
|
|
121
131
|
for (const p of s.live) { signalPid(p, 'SIGKILL'); if (!t.killedPids.includes(p)) t.killedPids.push(p); }
|
|
122
132
|
await sleep(25);
|
|
123
133
|
}
|
|
124
|
-
const s = survivors();
|
|
134
|
+
const s = await survivors();
|
|
125
135
|
t.survivors = s.live.length ? s.live : (s.group ? [-pgid] : []);
|
|
126
136
|
return t;
|
|
127
137
|
}
|