@clear-capabilities/agentic-security-scanner 0.148.5 → 0.149.1
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 +50 -0
- package/bin/agentic-security.js +34 -10
- package/dist/agentic-security.mjs +2 -2
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +2 -2
- package/src/dataflow/engine.js +14 -0
- package/src/engine.js +47 -11
- package/src/lineage/driver.js +8 -0
- package/src/lineage/graph-builder.js +1 -0
- package/src/lineage/index.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,56 @@
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
## 0.149.1 - Fix scan hang on unbounded registry/OSV network calls
|
|
14
|
+
|
|
15
|
+
An SCA scan could hang indefinitely at "Registry metadata..." (or, less visibly, during OSV
|
|
16
|
+
lookup) with no timeout, no error, and no way to tell it apart from a genuine long-running scan.
|
|
17
|
+
`queryRegistries()` and `queryOSV()` in `src/engine.js` fired `fetch()` against npm, PyPI,
|
|
18
|
+
Packagist, crates.io, RubyGems, pub.dev, Maven Central, and the OSV API with no timeout at all —
|
|
19
|
+
if any single registry stalled (rate-limiting, a proxy silently dropping the connection, DNS),
|
|
20
|
+
the whole scan sat there forever, since Node's global `fetch` has no default timeout.
|
|
21
|
+
|
|
22
|
+
1. All 9 registry/OSV fetches now carry `AbortSignal.timeout(8000)`, matching the convention
|
|
23
|
+
already used for the KEV feed fetch.
|
|
24
|
+
2. `queryOSV`'s per-vulnerability detail fetch now also honors `AGENTIC_SECURITY_OFFLINE=1`, so
|
|
25
|
+
offline scans skip that network path instead of attempting and timing out on it.
|
|
26
|
+
3. `queryRegistries` deliberately does **not** honor `AGENTIC_SECURITY_OFFLINE` — that flag's
|
|
27
|
+
established scope in this codebase is OSV/KEV/EPSS, and `test/sca-deprecated.test.js` already
|
|
28
|
+
depends on registry lookups still running (against a stubbed fetch) under `OFFLINE=1`.
|
|
29
|
+
|
|
30
|
+
## 0.149.0 - Live progress reporting for long-running scans
|
|
31
|
+
|
|
32
|
+
A scan running deep interprocedural taint analysis or the Data Flow Explorer's lineage graph
|
|
33
|
+
build could sit silent for minutes with no sign anything was happening, which read as a hang and
|
|
34
|
+
was a real reason people quit the run before it finished. This release wires progress reporting
|
|
35
|
+
through the phases that were previously silent, and threads the existing stderr status line into
|
|
36
|
+
every command that runs a fresh scan.
|
|
37
|
+
|
|
38
|
+
1. `runTaintEngine` (`src/dataflow/engine.js`) now reports live `current/total` progress from its
|
|
39
|
+
dominant per-function analysis loop via an optional `opts.onProgress`, additive and byte-
|
|
40
|
+
identical when omitted.
|
|
41
|
+
2. `runFieldIdentityAnalysis` (`src/lineage/driver.js`) reports the same live per-function progress
|
|
42
|
+
for the lineage graph build, threaded through `graph-builder.js` -> `coverage.js` ->
|
|
43
|
+
`index.js`'s `buildLineageGraph`.
|
|
44
|
+
3. Both the deep-taint and lineage-graph phases are one synchronous, unbreakable call each (Node's
|
|
45
|
+
event loop can't tick mid-call), so a "starting (budget Ns)" message prints the instant each
|
|
46
|
+
phase begins, then live progress once its main loop runs.
|
|
47
|
+
4. The ~52 posture/provenance annotators (previously silent as a block) now report
|
|
48
|
+
`[Annotating] N/52 <name>` as each one runs.
|
|
49
|
+
5. The stderr `\r[phase] current/total` status line, previously wired only into the default `scan`
|
|
50
|
+
command, is now shared (`scanProgressReporter()`/`clearScanProgressLine()`) and used by
|
|
51
|
+
`scan --watch`, `ci`, `verify-attestation`, and `dataflow watch` (the seed scan and every
|
|
52
|
+
rescan) too.
|
|
53
|
+
|
|
54
|
+
`posture`/`compliance`/`supply`/`triage`/`labs` read the persisted `last-scan.json` rather than
|
|
55
|
+
scanning themselves, so they inherit this the moment a `scan` produced that file; `org-scan`
|
|
56
|
+
(concurrent multi-repo) is deliberately left silent, since a shared progress line across parallel
|
|
57
|
+
workers would just interleave and garble.
|
|
58
|
+
|
|
59
|
+
Also: `ide/vscode`'s transitive `js-yaml` dev dependency (pulled in via `@vscode/vsce`) is bumped
|
|
60
|
+
4.3.1 -> 4.3.2, closing a high-severity CPU-exhaustion advisory the release gate's dependency-
|
|
61
|
+
currency check found (GHSA-2883-xcg3-v3hh) — a non-breaking patch release, `npm audit fix` only.
|
|
62
|
+
|
|
13
63
|
## 0.148.5 - Third premortem pass on the --assurance strict fix: clean bill of health, four polish items closed anyway
|
|
14
64
|
|
|
15
65
|
0.148.4's fix was put through a THIRD adversarial premortem pass to check whether it introduced
|
package/bin/agentic-security.js
CHANGED
|
@@ -53,6 +53,26 @@ const MACHINE_FORMATS = new Set([
|
|
|
53
53
|
'json', 'sarif', 'oscal', 'cyclonedx', 'sbom', 'spdx', 'vex', 'openvex', 'pbom', 'aibom',
|
|
54
54
|
]);
|
|
55
55
|
function isMachineFormat(fmt) { return MACHINE_FORMATS.has(String(fmt)); }
|
|
56
|
+
|
|
57
|
+
// Shared stderr progress reporter for any command that runs a fresh scan —
|
|
58
|
+
// the same `\r[phase] current/total file` status line the default `scan`
|
|
59
|
+
// command has always printed, extracted so every OTHER scan-driving command
|
|
60
|
+
// (`scan --watch`, `ci`, `verify-attestation`, `dataflow watch`) reports
|
|
61
|
+
// progress too instead of running silently. Gated on `stderr.isTTY` exactly
|
|
62
|
+
// like the original: piping to a file/CI log never gets a `\r`-spammed
|
|
63
|
+
// status line. `runFullScan` (engine.js) now reports progress for every
|
|
64
|
+
// phase, including the previously-silent deep-analysis/lineage/annotator
|
|
65
|
+
// phases — see that file's own `setProgress` call sites.
|
|
66
|
+
function scanProgressReporter() {
|
|
67
|
+
return (p) => {
|
|
68
|
+
if (process.stderr.isTTY) process.stderr.write(`\r[${p.phase}] ${p.current}/${p.total} ${p.file} `);
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Wipes the progress line printed by scanProgressReporter() above — call
|
|
72
|
+
// once a scan this reporter was attached to has finished.
|
|
73
|
+
function clearScanProgressLine() {
|
|
74
|
+
if (process.stderr.isTTY) process.stderr.write('\r' + ' '.repeat(80) + '\r');
|
|
75
|
+
}
|
|
56
76
|
import { toCycloneDX, toSPDX } from '../src/posture/sbom.js';
|
|
57
77
|
import { toPBOM } from '../src/sast/pipeline.js';
|
|
58
78
|
import { buildAIBOM, aibomToMarkdown } from '../src/posture/aibom.js';
|
|
@@ -619,11 +639,13 @@ async function cmdScan(args) {
|
|
|
619
639
|
process.env.AGENTIC_SECURITY_INCREMENTAL = '1';
|
|
620
640
|
const { watchProject, computeDelta, persistStatus, renderStatusLine } = await import('../src/posture/watch-mode.js');
|
|
621
641
|
process.stderr.write(`[watch] scanning ${targetAbs} on change — Ctrl-C to stop. Status → .agentic-security/watch-status.md\n`);
|
|
622
|
-
const seed = await runScan(targetAbs, {});
|
|
642
|
+
const seed = await runScan(targetAbs, { onProgress: scanProgressReporter() });
|
|
643
|
+
clearScanProgressLine();
|
|
623
644
|
let prevFindings = seed.scan.findings || [];
|
|
624
645
|
await watchProject(targetAbs, async () => {
|
|
625
646
|
try {
|
|
626
|
-
const { scan } = await runScan(targetAbs, {});
|
|
647
|
+
const { scan } = await runScan(targetAbs, { onProgress: scanProgressReporter() });
|
|
648
|
+
clearScanProgressLine();
|
|
627
649
|
const curr = scan.findings || [];
|
|
628
650
|
const delta = computeDelta(prevFindings, curr);
|
|
629
651
|
persistStatus(targetAbs, delta);
|
|
@@ -690,9 +712,7 @@ async function cmdScan(args) {
|
|
|
690
712
|
|
|
691
713
|
const { scan, meta } = await runScan(target, {
|
|
692
714
|
changedSince,
|
|
693
|
-
onProgress: (
|
|
694
|
-
if (process.stderr.isTTY) process.stderr.write(`\r[${p.phase}] ${p.current}/${p.total} ${p.file} `);
|
|
695
|
-
},
|
|
715
|
+
onProgress: scanProgressReporter(),
|
|
696
716
|
});
|
|
697
717
|
// --require-provenance: flag (never fail) any finding whose provenance
|
|
698
718
|
// isn't resolved, via scanHealth — deliberately independent of the
|
|
@@ -740,7 +760,7 @@ async function cmdScan(args) {
|
|
|
740
760
|
// The BOM/attestation emitters stamp the producing engine's version into
|
|
741
761
|
// their metadata; carry the real package version so it can never drift.
|
|
742
762
|
if (meta && meta.engineVersion == null) meta.engineVersion = PKG_VERSION;
|
|
743
|
-
|
|
763
|
+
clearScanProgressLine();
|
|
744
764
|
|
|
745
765
|
const only = args.flags.only;
|
|
746
766
|
if (only) {
|
|
@@ -1230,7 +1250,8 @@ async function cmdCi(args) {
|
|
|
1230
1250
|
else process.stderr.write(`[ci] full scan (no baseline ref detected)\n`);
|
|
1231
1251
|
|
|
1232
1252
|
const profile = loadPersonaProfile(targetAbs, args);
|
|
1233
|
-
const { scan, meta } = await runScan(target, { changedSince: baseline || null });
|
|
1253
|
+
const { scan, meta } = await runScan(target, { changedSince: baseline || null, onProgress: scanProgressReporter() });
|
|
1254
|
+
clearScanProgressLine();
|
|
1234
1255
|
|
|
1235
1256
|
// Apply suppressions + overrides + packs, mirroring cmdScan's pipeline.
|
|
1236
1257
|
scan.findings = applySuppressions(scan.findings || [], targetAbs, profile);
|
|
@@ -2740,7 +2761,8 @@ async function cmdVerifyRunAttestation(attestation, args) {
|
|
|
2740
2761
|
const { normalizeFindings } = await import('../src/report/index.js');
|
|
2741
2762
|
const { effectiveVersion } = await import('../src/posture/ruleset-version.js');
|
|
2742
2763
|
const { verifyRunAttestation } = await import('../src/posture/attestation.js');
|
|
2743
|
-
const { scan } = await runScan(projectPath);
|
|
2764
|
+
const { scan } = await runScan(projectPath, { onProgress: scanProgressReporter() });
|
|
2765
|
+
clearScanProgressLine();
|
|
2744
2766
|
const r = verifyRunAttestation(attestation, {
|
|
2745
2767
|
findings: normalizeFindings(scan),
|
|
2746
2768
|
engineVersion: PKG_VERSION,
|
|
@@ -6224,7 +6246,8 @@ async function cmdDataflowWatch(args) {
|
|
|
6224
6246
|
// uncached provenance-resolution cost on every edit), while every OTHER
|
|
6225
6247
|
// state write named above stays fully suppressed. Do not "simplify" this
|
|
6226
6248
|
// back to a bare runScan call.
|
|
6227
|
-
const seed = await withStateWritesDisabled(() => runScan(targetAbs, {}), { exceptCategories: ['provenance-cache'] });
|
|
6249
|
+
const seed = await withStateWritesDisabled(() => runScan(targetAbs, { onProgress: scanProgressReporter() }), { exceptCategories: ['provenance-cache'] });
|
|
6250
|
+
clearScanProgressLine();
|
|
6228
6251
|
if (!seed.scan.lineageGraph) {
|
|
6229
6252
|
process.stderr.write(`agentic-security dataflow watch: seed scan produced no data-flow graph (${_lineageStatusReason(seed.scan.lineageStatus)}) — nothing to watch/diff against.\n`);
|
|
6230
6253
|
return 1;
|
|
@@ -6266,7 +6289,8 @@ async function cmdDataflowWatch(args) {
|
|
|
6266
6289
|
return;
|
|
6267
6290
|
}
|
|
6268
6291
|
try {
|
|
6269
|
-
const { scan } = await withStateWritesDisabled(() => runScan(targetAbs, {}), { exceptCategories: ['provenance-cache'] });
|
|
6292
|
+
const { scan } = await withStateWritesDisabled(() => runScan(targetAbs, { onProgress: scanProgressReporter() }), { exceptCategories: ['provenance-cache'] });
|
|
6293
|
+
clearScanProgressLine();
|
|
6270
6294
|
if (!scan.lineageGraph) {
|
|
6271
6295
|
process.stderr.write(`[watch-dataflow] rescan produced no data-flow graph (${_lineageStatusReason(scan.lineageStatus)}) — skipping this change.\n`);
|
|
6272
6296
|
return;
|