@clear-capabilities/agentic-security-scanner 0.167.0 → 0.169.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 +79 -0
- package/bin/agentic-security.js +237 -0
- package/dist/2923.index.js +369 -1
- package/dist/3736.index.js +2 -1
- package/dist/4700.index.js +151 -0
- package/dist/6932.index.js +1190 -0
- package/dist/agentic-security.mjs +2 -2
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +3 -3
- package/src/lineage/CLAUDE.md +19 -0
- package/src/lineage/observation-adapters.js +368 -0
- package/src/lineage/runtime-assurance.js +135 -0
- package/src/lineage/runtime-observation.js +37 -4
- package/src/posture/artifact-registry.js +3 -0
- package/src/posture/fleet/cli.js +103 -0
- package/src/posture/fleet/fleet-graph.js +142 -0
- package/src/posture/fleet/fleet-manifest.js +77 -0
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,85 @@
|
|
|
9
9
|
> make the history less accurate, not more.
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
## 0.169.0 - runtime-corroborated security twin: 4 new telemetry adapters, honest runtime assurance, a `runtime` CLI
|
|
13
|
+
|
|
14
|
+
Extends the existing Milestone 5 "Runtime-Corroborated Digital Twin" machinery (closed-world `RuntimeObservation`
|
|
15
|
+
contract, a 3-rung static-to-runtime match ladder, a non-mutating overlay) rather than building a second one —
|
|
16
|
+
closing the real gap that machinery had: only one hand-authored JSONL telemetry format could feed it.
|
|
17
|
+
|
|
18
|
+
**Four new telemetry adapters.** `otel-json` (OpenTelemetry JSON trace export, one draft per span), `envoy-access-log`
|
|
19
|
+
(Envoy's default JSON access log, including `response_flags` → an allow/deny security decision), `k8s-audit-log`
|
|
20
|
+
(Kubernetes `audit.k8s.io` JSONL — `objectRef`/`responseStatus.code`, deliberately never `user.username`), and a
|
|
21
|
+
generic JSON-lines `http-access-log`. Each is a pure, zero-fs parser reading only approved, non-payload metadata
|
|
22
|
+
(destination host/port/scheme, service name, protocol/TLS, store table, queue topic) and dropping everything else
|
|
23
|
+
(query strings, headers, usernames, bodies) — matching the existing contract's own reject-not-redact discipline.
|
|
24
|
+
One new, narrowly-scoped attribute key, `network.securityDecision` (a closed `allowed|denied` enum, not a free-form
|
|
25
|
+
identifier), carries the one new fact these formats need that the existing vocabulary didn't have a slot for.
|
|
26
|
+
|
|
27
|
+
**Honest runtime assurance.** A new `runtime-assurance.js` reports `runtimeStatus: complete|partial|unavailable` —
|
|
28
|
+
`unavailable` whenever no observation store was ever consulted, never a silent "no risk." `runtime status`,
|
|
29
|
+
`runtime reconcile` (diagnostic drift detection against the current graph, never auto-rewriting a stored
|
|
30
|
+
observation) and `runtime query` (two real graph-backed queries, plus honest, named disclosure of the three
|
|
31
|
+
PRD-example queries this architecture genuinely cannot answer) round out a new top-level `runtime` CLI dispatcher;
|
|
32
|
+
`runtime ingest` is a thin shim over the already-shipped `dataflow observations import` — zero duplicated logic.
|
|
33
|
+
|
|
34
|
+
**A real, grounded demonstration, not an asserted-away fixture.** A small app (`scanner/test/fixtures/runtime-twin/`)
|
|
35
|
+
proves all 5 of the PRD's named fixture properties in one scenario against the real built CLI: a database write
|
|
36
|
+
that executes and is observed allowed; an outbound call that never executes and is never observed; an unrecognized
|
|
37
|
+
host that IS observed but matches nothing in the static graph (reported `unmatched`, never dropped); a PII flow
|
|
38
|
+
crossing a trust boundary to a third party whose destination can never be runtime-corroborated by any adapter here
|
|
39
|
+
— a genuine, disclosed architectural limit; and a queue publish observed denied by authorization.
|
|
40
|
+
|
|
41
|
+
**What this deliberately does not do, disclosed rather than silently dropped.** No Data Flow Explorer UI/HTTP-server
|
|
42
|
+
wiring (`scanner/src/server/CLAUDE.md`'s own deferred list already named `runtime/` among the endpoints the
|
|
43
|
+
read-only local server does not serve — bolting one on here without that file's own scoping-doc-plus-test-suite
|
|
44
|
+
rigor would be a real regression). No authenticated-identity field (the existing contract omits it as a
|
|
45
|
+
payload-smuggling risk). No route/function-symbol match rungs or the 3 additional PRD-named edge states
|
|
46
|
+
(`RUNTIME_BLOCKED`/`RUNTIME_POLICY_VIOLATION`/`RUNTIME_EXPLOIT_CORRELATED`) — real, separately-scoped future work.
|
|
47
|
+
See `docs/implementation-status/runtime-corroborated-twin.md` for the full itemized status.
|
|
48
|
+
|
|
49
|
+
## 0.168.0 - fleet: multi-repository scan orchestration and cross-repository dependency blast radius
|
|
50
|
+
|
|
51
|
+
A real, honestly-scoped slice of the organization-wide security graph recommendation: a new CLI command group,
|
|
52
|
+
`fleet init|scan|status|resume|query`, built almost entirely on existing machinery rather than a new engine.
|
|
53
|
+
|
|
54
|
+
**The manifest and the scan.** `fleet.yaml` declares an organization and a list of `{id, path}` repositories
|
|
55
|
+
(plus `workers` and `perRepoTimeout`), validated closed-world — an unknown key, a duplicate id, a non-kebab id or
|
|
56
|
+
an unresolvable path refuses the whole manifest. `fleet scan fleet.yaml` drives the EXISTING `posture/fleet.js`
|
|
57
|
+
(`runFleet`) over that list: bounded concurrency, a resumable on-disk checkpoint, per-repository isolation (one
|
|
58
|
+
repo exploding never takes the run down), and an honest rollup (a failed repo is reported failed, never folded
|
|
59
|
+
into "0 findings"). No new scanning engine was written for this.
|
|
60
|
+
|
|
61
|
+
**The new part: a cross-repository dependency graph.** Built purely from each repository's own package manifest
|
|
62
|
+
(`package.json`/`go.mod`/`Cargo.toml`/`requirements.txt`/`pyproject.toml` — name plus dependencies), with no
|
|
63
|
+
deployment configuration required, so a fleet of plain application repositories works immediately. `fleet query
|
|
64
|
+
--depends-on <repo-id>` answers "which repositories depend on this one, directly or transitively, and through
|
|
65
|
+
what path" — a real question about organization-wide blast radius, not a guess.
|
|
66
|
+
|
|
67
|
+
**A real, grounded demonstration, not an asserted-away fixture.** A four-repository synthetic organization
|
|
68
|
+
(frontend, payments-api, shared-auth-lib, payments-worker) where only `shared-auth-lib` carries an actual
|
|
69
|
+
command-injection vulnerability the deterministic engine genuinely finds (the same confirmed-detected shape —
|
|
70
|
+
`req.body.host` into `child_process.exec` — already used in this repository's own fixtures). The blast-radius
|
|
71
|
+
query correctly reports `frontend` and `payments-api` as direct dependents and `payments-worker` as a transitive
|
|
72
|
+
one, through `payments-api`, at the right depth, with the right path.
|
|
73
|
+
|
|
74
|
+
**A bug this process itself caught, twice.** A first draft of the fleet CLI wrapped `runScan()`'s own
|
|
75
|
+
`{ scan, meta }` return value in a SECOND `{ scan }` object; `runFleet`'s internal summarizer reads fields
|
|
76
|
+
defensively rather than throwing on the wrong shape, so every repository silently reported zero findings with no
|
|
77
|
+
error of any kind. Found by running the CLI against the fixture and noticing the finding count was wrong, not by
|
|
78
|
+
the automated test — whose own fixture at the time also happened to use a vulnerability shape the deterministic
|
|
79
|
+
engine does not actually flag (an unlabeled function parameter reaching `exec`, no recognized untrusted source).
|
|
80
|
+
Both are fixed, and the fixture now uses the same confirmed-detected shape the test asserts against.
|
|
81
|
+
|
|
82
|
+
**Still open, and disclosed as such** (`docs/implementation-status/fleet.md`): no Internet-facing/regulated-data
|
|
83
|
+
overlay — the fuller "which Internet-facing app can reach PCI data through this component" question needs the
|
|
84
|
+
existing deployment-boundary graph's trust-zone model layered on top of this dependency graph, not attempted
|
|
85
|
+
here; no natural-language query parser (`--depends-on <repo-id>` is the one supported shape); no SQLite-backed
|
|
86
|
+
storage (reuses the existing flat-JSON fleet checkpoint; `posture/portfolio/backend.js`'s existing storage
|
|
87
|
+
abstraction is the natural place for a SQLite implementation later); no persisted job queue with lease/retry
|
|
88
|
+
(reuses in-process `runFleet` rather than the heavier, operator-only `portfolio/work-units.js` scheduler); matching
|
|
89
|
+
across repositories is by declared package name only, with no ecosystem disambiguation.
|
|
90
|
+
|
|
12
91
|
## 0.167.0 - execution-backed exploit proof, and a self-learning rule compiler that turns one confirmed finding into a deterministic detection rule
|
|
13
92
|
|
|
14
93
|
Two new, independently opt-in capabilities, each off by default until explicitly invoked.
|
package/bin/agentic-security.js
CHANGED
|
@@ -5561,6 +5561,223 @@ async function cmdDataflowTwin(args) {
|
|
|
5561
5561
|
return 0;
|
|
5562
5562
|
}
|
|
5563
5563
|
|
|
5564
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
5565
|
+
// PRD "Runtime-Corroborated Security Twin" — the `runtime` dispatcher.
|
|
5566
|
+
//
|
|
5567
|
+
// `runtime ingest`/`runtime status`/`runtime reconcile`/`runtime query`
|
|
5568
|
+
// is the literal CLI shape that PRD names. It is NOT a parallel command
|
|
5569
|
+
// surface: `ingest` is a thin shim over the already-shipped
|
|
5570
|
+
// `dataflow observations import` (zero duplicated logic — it reshapes
|
|
5571
|
+
// `args._` and delegates); `status`/`reconcile`/`query` are genuinely new
|
|
5572
|
+
// (the assurance report and the reconciliation/query reports did not
|
|
5573
|
+
// exist before this PRD), but all three are built entirely on the
|
|
5574
|
+
// already-shipped `correlateObservations`/`matchObservationToGraph`/
|
|
5575
|
+
// `loadObservationImports` — no second correlation engine, no second
|
|
5576
|
+
// observation store.
|
|
5577
|
+
|
|
5578
|
+
async function cmdRuntimeIngest(args) {
|
|
5579
|
+
// args._ = ['runtime', 'ingest', <path>?]. cmdDataflowObservationsImport
|
|
5580
|
+
// reads args._[3] as its own path slot (its own dispatch assumes
|
|
5581
|
+
// ['dataflow', 'observations', 'import', <path>?]) — reshape rather
|
|
5582
|
+
// than duplicate its ~150 lines of validation/import logic.
|
|
5583
|
+
const target = args._[2] || '.';
|
|
5584
|
+
const shimmed = { ...args, _: ['dataflow', 'observations', 'import', target] };
|
|
5585
|
+
return cmdDataflowObservationsImport(shimmed);
|
|
5586
|
+
}
|
|
5587
|
+
|
|
5588
|
+
async function _loadRuntimeInputs(targetAbs) {
|
|
5589
|
+
const { loadSignedGraph } = await import('../src/server/graph-loader.js');
|
|
5590
|
+
const loaded = loadSignedGraph(targetAbs);
|
|
5591
|
+
if (!loaded.ok) return { ok: false, message: loaded.message };
|
|
5592
|
+
|
|
5593
|
+
const dir = statePath(targetAbs, 'runtime-observations');
|
|
5594
|
+
let importRecords = null;
|
|
5595
|
+
let observations = null;
|
|
5596
|
+
if (fs.existsSync(dir)) {
|
|
5597
|
+
const store = await import('../src/lineage/observation-store.js');
|
|
5598
|
+
importRecords = store.loadObservationImports(targetAbs);
|
|
5599
|
+
observations = store.loadObservations(targetAbs);
|
|
5600
|
+
}
|
|
5601
|
+
return { ok: true, graph: loaded.graph, importRecords, observations };
|
|
5602
|
+
}
|
|
5603
|
+
|
|
5604
|
+
async function cmdRuntimeStatus(args) {
|
|
5605
|
+
const target = args._[2] || '.';
|
|
5606
|
+
const targetAbs = path.resolve(target);
|
|
5607
|
+
const format = args.flags.format ?? 'json';
|
|
5608
|
+
if (format !== 'json' && format !== 'markdown') {
|
|
5609
|
+
process.stderr.write(`agentic-security runtime status: --format must be one of json|markdown (got ${JSON.stringify(format)}).\n`);
|
|
5610
|
+
return 2;
|
|
5611
|
+
}
|
|
5612
|
+
|
|
5613
|
+
const loaded = await _loadRuntimeInputs(targetAbs);
|
|
5614
|
+
if (!loaded.ok) {
|
|
5615
|
+
process.stderr.write(`agentic-security runtime status: ${loaded.message}\n`);
|
|
5616
|
+
return 1;
|
|
5617
|
+
}
|
|
5618
|
+
|
|
5619
|
+
const environment = typeof args.flags.environment === 'string' ? args.flags.environment : null;
|
|
5620
|
+
const windowStart = typeof args.flags['window-start'] === 'string' ? args.flags['window-start'] : null;
|
|
5621
|
+
const windowEnd = typeof args.flags['window-end'] === 'string' ? args.flags['window-end'] : null;
|
|
5622
|
+
|
|
5623
|
+
const { correlateObservations } = await import('../src/lineage/observation-correlation.js');
|
|
5624
|
+
const correlation = correlateObservations(loaded.graph, loaded.observations, { environment, windowStart, windowEnd });
|
|
5625
|
+
|
|
5626
|
+
const { computeRuntimeAssurance } = await import('../src/lineage/runtime-assurance.js');
|
|
5627
|
+
const assurance = computeRuntimeAssurance({ importRecords: loaded.importRecords, correlation });
|
|
5628
|
+
|
|
5629
|
+
if (format === 'json') {
|
|
5630
|
+
process.stdout.write(JSON.stringify(assurance, null, 2) + '\n');
|
|
5631
|
+
return 0;
|
|
5632
|
+
}
|
|
5633
|
+
|
|
5634
|
+
const lines = [];
|
|
5635
|
+
lines.push('# Runtime Assurance', '');
|
|
5636
|
+
lines.push(`**runtimeStatus:** \`${assurance.runtimeStatus}\``, '');
|
|
5637
|
+
lines.push(`- Telemetry window: ${assurance.telemetryWindow.start ?? '(none)'} .. ${assurance.telemetryWindow.end ?? '(none)'}`);
|
|
5638
|
+
lines.push(`- Last observed: ${assurance.freshness.lastObservedAt ?? '(none)'}${assurance.freshness.stale ? ' (STALE)' : ''}`);
|
|
5639
|
+
lines.push(`- Mapping: ${assurance.mappingPercentage === null ? '(no observations)' : `${assurance.mappingPercentage}%`} (${assurance.unmatchedObservationCount} of ${assurance.totalObservationCount} unmatched)`);
|
|
5640
|
+
lines.push(`- Parse failures: ${assurance.parseFailures} — ${assurance.parseFailuresNote}`);
|
|
5641
|
+
lines.push('', '## Limitations', '');
|
|
5642
|
+
for (const l of assurance.limitations) lines.push(`- ${l}`);
|
|
5643
|
+
lines.push('');
|
|
5644
|
+
process.stdout.write(lines.join('\n'));
|
|
5645
|
+
return 0;
|
|
5646
|
+
}
|
|
5647
|
+
|
|
5648
|
+
async function cmdRuntimeReconcile(args) {
|
|
5649
|
+
const target = args._[2] || '.';
|
|
5650
|
+
const targetAbs = path.resolve(target);
|
|
5651
|
+
|
|
5652
|
+
const loaded = await _loadRuntimeInputs(targetAbs);
|
|
5653
|
+
if (!loaded.ok) {
|
|
5654
|
+
process.stderr.write(`agentic-security runtime reconcile: ${loaded.message}\n`);
|
|
5655
|
+
return 1;
|
|
5656
|
+
}
|
|
5657
|
+
if (loaded.observations === null) {
|
|
5658
|
+
process.stdout.write(JSON.stringify({ checked: 0, drifted: [], note: 'No runtime observation store exists for this scan — nothing to reconcile.' }, null, 2) + '\n');
|
|
5659
|
+
return 0;
|
|
5660
|
+
}
|
|
5661
|
+
|
|
5662
|
+
// Deliberately diagnostic-only: `matchObservationToGraph` is re-run here
|
|
5663
|
+
// purely to COMPARE against each record's own already-stored match — it
|
|
5664
|
+
// never rewrites the stored record. `observation-correlation.js`'s own
|
|
5665
|
+
// header explains why `correlateObservations` itself must never re-run
|
|
5666
|
+
// the ladder (a stored observation must stay honest about what it was
|
|
5667
|
+
// actually checked against at import time); `reconcile` exists
|
|
5668
|
+
// precisely to surface drift for an operator to decide on, not to
|
|
5669
|
+
// silently apply it.
|
|
5670
|
+
const { matchObservationToGraph } = await import('../src/lineage/observation-correlation.js');
|
|
5671
|
+
const drifted = [];
|
|
5672
|
+
for (const obs of loaded.observations) {
|
|
5673
|
+
const fresh = matchObservationToGraph(loaded.graph, obs);
|
|
5674
|
+
const same = fresh.matchMethod === obs.matchMethod
|
|
5675
|
+
&& fresh.matchConfidence === obs.matchConfidence
|
|
5676
|
+
&& JSON.stringify([...fresh.matchedFlowIds].sort()) === JSON.stringify([...obs.matchedFlowIds].sort());
|
|
5677
|
+
if (!same) {
|
|
5678
|
+
drifted.push({
|
|
5679
|
+
observationId: obs.id,
|
|
5680
|
+
stored: { matchMethod: obs.matchMethod, matchConfidence: obs.matchConfidence, matchedFlowIds: obs.matchedFlowIds },
|
|
5681
|
+
currentGraph: { matchMethod: fresh.matchMethod, matchConfidence: fresh.matchConfidence, matchedFlowIds: fresh.matchedFlowIds },
|
|
5682
|
+
});
|
|
5683
|
+
}
|
|
5684
|
+
}
|
|
5685
|
+
process.stdout.write(JSON.stringify({
|
|
5686
|
+
checked: loaded.observations.length,
|
|
5687
|
+
drifted,
|
|
5688
|
+
note: drifted.length > 0
|
|
5689
|
+
? 'These observations would match differently against the CURRENT graph than what is stored — the graph changed since import. Re-import with `runtime ingest` to update the stored match; nothing was changed automatically.'
|
|
5690
|
+
: 'Every stored observation still matches the current graph identically.',
|
|
5691
|
+
}, null, 2) + '\n');
|
|
5692
|
+
return 0;
|
|
5693
|
+
}
|
|
5694
|
+
|
|
5695
|
+
const _RUNTIME_SENSITIVE_CLASSES = ['PII', 'PHI', 'PCI', 'FIN', 'CREDENTIALS'];
|
|
5696
|
+
|
|
5697
|
+
const _RUNTIME_QUERIES = Object.freeze({
|
|
5698
|
+
'internet-facing-to-sensitive': {
|
|
5699
|
+
description: 'Internet-facing flows observed reaching sensitive data (PRD example: "Show Internet-facing flows observed reaching PCI data").',
|
|
5700
|
+
run(graph, correlation) {
|
|
5701
|
+
const nodesById = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
5702
|
+
const dataElementsById = new Map(graph.dataElements.map((d) => [d.id, d]));
|
|
5703
|
+
return graph.flows.filter((f) => {
|
|
5704
|
+
if (correlation.byFlow[f.id]?.layer !== 'runtime_observed') return false;
|
|
5705
|
+
const sink = nodesById.get(f.sink);
|
|
5706
|
+
if (sink?.externality?.value !== 'external') return false;
|
|
5707
|
+
return f.dataElementIds.some((id) => (dataElementsById.get(id)?.dataClasses ?? []).some((c) => _RUNTIME_SENSITIVE_CLASSES.includes(c)));
|
|
5708
|
+
}).map((f) => f.id);
|
|
5709
|
+
},
|
|
5710
|
+
},
|
|
5711
|
+
'sensitive-crossing-boundary': {
|
|
5712
|
+
description: 'Sensitive-data paths observed crossing a trust boundary (PRD example: "Show sensitive-data paths observed crossing trust boundaries").',
|
|
5713
|
+
run(graph, correlation) {
|
|
5714
|
+
const edgesById = new Map(graph.edges.map((e) => [e.id, e]));
|
|
5715
|
+
const dataElementsById = new Map(graph.dataElements.map((d) => [d.id, d]));
|
|
5716
|
+
return graph.flows.filter((f) => {
|
|
5717
|
+
if (correlation.byFlow[f.id]?.layer !== 'runtime_observed') return false;
|
|
5718
|
+
const crosses = f.edgeIds.some((id) => (edgesById.get(id)?.boundaryCrossings ?? []).length > 0);
|
|
5719
|
+
if (!crosses) return false;
|
|
5720
|
+
return f.dataElementIds.some((id) => (dataElementsById.get(id)?.dataClasses ?? []).some((c) => _RUNTIME_SENSITIVE_CLASSES.includes(c)));
|
|
5721
|
+
}).map((f) => f.id);
|
|
5722
|
+
},
|
|
5723
|
+
},
|
|
5724
|
+
});
|
|
5725
|
+
|
|
5726
|
+
// PRD-named example queries this architecture cannot honestly answer —
|
|
5727
|
+
// disclosed, never silently omitted. "Runtime flows absent from the
|
|
5728
|
+
// static graph" needs a config-declared/auto-correlated edge mechanism
|
|
5729
|
+
// (M5 7a / M2 F2-F3, both out of scope here — every RuntimeObservation
|
|
5730
|
+
// matches an EXISTING static node, it never mints a new one). "Vulnerable
|
|
5731
|
+
// functions actually exercised in production" and "identities using
|
|
5732
|
+
// risky paths" both need identity/function-level telemetry this
|
|
5733
|
+
// contract's closed-world schema deliberately omits (see this PRD's
|
|
5734
|
+
// implementation-status doc).
|
|
5735
|
+
const _RUNTIME_QUERIES_NOT_SUPPORTED = Object.freeze({
|
|
5736
|
+
'runtime-flows-absent-from-static': 'Needs a config-declared/auto-correlated edge mechanism (not built — see M5 7a / M2 F2-F3 in scanner/src/lineage/CLAUDE.md) — every RuntimeObservation matches an existing static graph node, it never mints a new one.',
|
|
5737
|
+
'vulnerable-functions-in-production': 'Needs function/symbol-level runtime telemetry; this adapter set only matches destinations (route/service/table/topic), never a function symbol.',
|
|
5738
|
+
'identities-using-risky-paths': 'The RuntimeObservation contract deliberately carries no identity field (a payload-smuggling risk) — see this PRD\'s implementation-status doc for why.',
|
|
5739
|
+
});
|
|
5740
|
+
|
|
5741
|
+
async function cmdRuntimeQuery(args) {
|
|
5742
|
+
const name = args._[2];
|
|
5743
|
+
if (!name || args.flags.list) {
|
|
5744
|
+
process.stdout.write('Supported queries:\n');
|
|
5745
|
+
for (const [k, v] of Object.entries(_RUNTIME_QUERIES)) process.stdout.write(` ${k} — ${v.description}\n`);
|
|
5746
|
+
process.stdout.write('\nNamed but NOT supported by this architecture (disclosed, not silently omitted):\n');
|
|
5747
|
+
for (const [k, v] of Object.entries(_RUNTIME_QUERIES_NOT_SUPPORTED)) process.stdout.write(` ${k} — ${v}\n`);
|
|
5748
|
+
// --list is always a successful, intentional operation (exit 0);
|
|
5749
|
+
// calling `runtime query` with neither a name nor --list is the only
|
|
5750
|
+
// real misuse here.
|
|
5751
|
+
return args.flags.list ? 0 : 2;
|
|
5752
|
+
}
|
|
5753
|
+
|
|
5754
|
+
if (Object.prototype.hasOwnProperty.call(_RUNTIME_QUERIES_NOT_SUPPORTED, name)) {
|
|
5755
|
+
process.stdout.write(JSON.stringify({ query: name, supported: false, reason: _RUNTIME_QUERIES_NOT_SUPPORTED[name] }, null, 2) + '\n');
|
|
5756
|
+
return 0;
|
|
5757
|
+
}
|
|
5758
|
+
const query = _RUNTIME_QUERIES[name];
|
|
5759
|
+
if (!query) {
|
|
5760
|
+
process.stderr.write(`agentic-security runtime query: unrecognized query "${name}" — run with --list to see every supported (and disclosed-unsupported) query name.\n`);
|
|
5761
|
+
return 2;
|
|
5762
|
+
}
|
|
5763
|
+
|
|
5764
|
+
const target = args._[3] || '.';
|
|
5765
|
+
const targetAbs = path.resolve(target);
|
|
5766
|
+
const loaded = await _loadRuntimeInputs(targetAbs);
|
|
5767
|
+
if (!loaded.ok) {
|
|
5768
|
+
process.stderr.write(`agentic-security runtime query: ${loaded.message}\n`);
|
|
5769
|
+
return 1;
|
|
5770
|
+
}
|
|
5771
|
+
|
|
5772
|
+
const environment = typeof args.flags.environment === 'string' ? args.flags.environment : null;
|
|
5773
|
+
const { correlateObservations } = await import('../src/lineage/observation-correlation.js');
|
|
5774
|
+
const correlation = correlateObservations(loaded.graph, loaded.observations, { environment, windowStart: null, windowEnd: null });
|
|
5775
|
+
|
|
5776
|
+
const flowIds = query.run(loaded.graph, correlation);
|
|
5777
|
+
process.stdout.write(JSON.stringify({ query: name, supported: true, flowIds }, null, 2) + '\n');
|
|
5778
|
+
return 0;
|
|
5779
|
+
}
|
|
5780
|
+
|
|
5564
5781
|
// Mirrors posture/fix-history.js's own _writeAtomicAndSync (temp file in
|
|
5565
5782
|
// the same directory, fsync, then rename over the target) — a crash
|
|
5566
5783
|
// mid-write must never leave recipient-profiles.json as invalid JSON,
|
|
@@ -7115,6 +7332,7 @@ async function main() {
|
|
|
7115
7332
|
case 'triage': process.exit(await cmdTriage(args));
|
|
7116
7333
|
case 'org-scan': process.exit(await cmdOrgScan(args));
|
|
7117
7334
|
case 'rules': process.exit(await cmdRules(args));
|
|
7335
|
+
case 'fleet': process.exit(await (await import('../src/posture/fleet/cli.js')).cmdFleet(args, path.resolve(args.flags.root || '.')));
|
|
7118
7336
|
case 'rule': process.exit(await cmdRule(args));
|
|
7119
7337
|
case 'tickets': process.exit(await cmdTickets(args));
|
|
7120
7338
|
case 'secure': process.exit(await cmdSecure(args));
|
|
@@ -7287,6 +7505,25 @@ async function main() {
|
|
|
7287
7505
|
}
|
|
7288
7506
|
break;
|
|
7289
7507
|
}
|
|
7508
|
+
case 'runtime': {
|
|
7509
|
+
// NOT a `dataflow` subcommand — PRD "Runtime-Corroborated Security
|
|
7510
|
+
// Twin" names this literal CLI shape (`runtime ingest/status/
|
|
7511
|
+
// reconcile/query`). `ingest` is a thin shim over the already-
|
|
7512
|
+
// shipped `dataflow observations import`; `status`/`reconcile`/
|
|
7513
|
+
// `query` are new, built entirely on the already-shipped
|
|
7514
|
+
// correlation/match/store functions. See cmdRuntimeStatus's own
|
|
7515
|
+
// header comment above.
|
|
7516
|
+
const sub = args._[1];
|
|
7517
|
+
if (sub === 'ingest') { process.exit(await cmdRuntimeIngest(args)); }
|
|
7518
|
+
else if (sub === 'status') { process.exit(await cmdRuntimeStatus(args)); }
|
|
7519
|
+
else if (sub === 'reconcile') { process.exit(await cmdRuntimeReconcile(args)); }
|
|
7520
|
+
else if (sub === 'query') { process.exit(await cmdRuntimeQuery(args)); }
|
|
7521
|
+
else {
|
|
7522
|
+
process.stderr.write(`agentic-security runtime: unrecognized sub-command "${sub}" — must be "ingest", "status", "reconcile", or "query".\n`);
|
|
7523
|
+
process.exit(2);
|
|
7524
|
+
}
|
|
7525
|
+
break;
|
|
7526
|
+
}
|
|
7290
7527
|
case 'remediation': {
|
|
7291
7528
|
// NOT a `dataflow` subcommand — this writes operator/incident
|
|
7292
7529
|
// state (the append-only remediation ledger), never the scanned
|