@clear-capabilities/agentic-security-scanner 0.134.0 → 0.136.2
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 +196 -0
- package/bin/agentic-security-audit.js +2 -1
- package/bin/agentic-security-consistency.js +2 -1
- package/bin/agentic-security.js +269 -44
- package/dist/113.index.js +6 -6
- package/dist/117.index.js +3 -1
- package/dist/178.index.js +1 -1
- package/dist/220.index.js +5 -3
- package/dist/238.index.js +4 -4
- package/dist/317.index.js +270 -0
- package/dist/384.index.js +1 -1
- package/dist/435.index.js +13 -7
- package/dist/513.index.js +7 -3
- package/dist/526.index.js +6 -6
- package/dist/637.index.js +1 -1
- package/dist/675.index.js +7 -5
- package/dist/839.index.js +4 -3
- package/dist/905.index.js +1173 -0
- package/dist/agentic-security.mjs +14 -14
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/dist/compliance-frameworks/ccpa.json +32 -0
- package/dist/compliance-frameworks/eu-ai-act.json +51 -0
- package/dist/compliance-frameworks/gdpr.json +45 -0
- package/dist/compliance-frameworks/hipaa-security-rule.json +56 -0
- package/dist/compliance-frameworks/nist-ai-600-1.json +51 -0
- package/dist/compliance-frameworks/nist-csf-2.json +73 -0
- package/dist/compliance-frameworks/nist-privacy-1-1.json +846 -0
- package/dist/compliance-frameworks/owasp-asvs-5.json +79 -0
- package/dist/compliance-frameworks/owasp-llm-top-10.json +69 -0
- package/package.json +9 -5
- package/src/badge.js +2 -1
- package/src/dataflow/cross-service-taint.js +2 -1
- package/src/dataflow/ifds-precise.js +6 -4
- package/src/dataflow/incremental.js +7 -5
- package/src/discovery/CLAUDE.md +10 -0
- package/src/discovery/index.js +175 -3
- package/src/discovery/llm-invoke.js +90 -1
- package/src/discovery/memory.js +163 -0
- package/src/engine.js +45 -8
- package/src/integrations/tickets.js +7 -6
- package/src/ir/ir-stats.js +1 -1
- package/src/ir/parser-cpp.js +1 -1
- package/src/leaderboard.js +3 -2
- package/src/llm-validator/consistency.js +6 -2
- package/src/llm-validator/index.js +1 -2
- package/src/mcp/audit.js +2 -1
- package/src/mcp/tools.js +4 -3
- package/src/posture/CLAUDE.md +26 -1
- package/src/posture/accuracy-scorecard.js +59 -1
- package/src/posture/agents-memory.js +5 -3
- package/src/posture/auditor-walkthrough.js +9 -7
- package/src/posture/auth-posture-import.js +5 -4
- package/src/posture/compliance-frameworks/nist-privacy-1-1.json +846 -0
- package/src/posture/compliance-policy.js +7 -9
- package/src/posture/custom-rules.js +7 -5
- package/src/posture/cve-alert-daemon.js +6 -5
- package/src/posture/dep-add-guard.js +2 -1
- package/src/posture/deterministic.js +3 -2
- package/src/posture/evidence-bundle.js +246 -0
- package/src/posture/exploitability-probability.js +2 -1
- package/src/posture/feature-flags.js +3 -2
- package/src/posture/findings-memory.js +3 -3
- package/src/posture/fix-history.js +5 -2
- package/src/posture/fix-metrics.js +5 -5
- package/src/posture/fix-plan.js +2 -1
- package/src/posture/grader-calibration.js +3 -4
- package/src/posture/intent-context.js +2 -1
- package/src/posture/learning.js +4 -3
- package/src/posture/license-attributions.js +5 -7
- package/src/posture/license-graph.js +2 -1
- package/src/posture/license-policy.js +2 -1
- package/src/posture/model-rescan.js +4 -3
- package/src/posture/network-policy-import.js +3 -2
- package/src/posture/pqc-migration-plan.js +7 -5
- package/src/posture/pr-augment.js +8 -5
- package/src/posture/privacy-framework.js +262 -0
- package/src/posture/risk-dollars.js +2 -2
- package/src/posture/router.js +5 -4
- package/src/posture/ruleset-version.js +2 -2
- package/src/posture/runtime-correlation.js +2 -1
- package/src/posture/sbom-diff.js +12 -3
- package/src/posture/sca-policy.js +7 -4
- package/src/posture/scan-checkpoint.js +15 -0
- package/src/posture/state-dir.js +34 -0
- package/src/posture/telemetry-ingest.js +4 -3
- package/src/posture/threat-model-auto.js +4 -1
- package/src/posture/threat-model-grounding.js +11 -1
- package/src/posture/time-to-fix.js +3 -2
- package/src/posture/triage-memory.js +3 -2
- package/src/posture/waf-ingest.js +6 -5
- package/src/posture/watch-mode.js +4 -3
- package/src/sast/code-injection-multilang.js +29 -0
- package/src/sca/dep-confusion.js +2 -1
package/bin/agentic-security.js
CHANGED
|
@@ -30,6 +30,7 @@ import * as triage from '../src/posture/triage.js';
|
|
|
30
30
|
import { buildSlackDigest, buildDiscordDigest, postWebhook, buildJiraIssue, buildPrComment, buildSiemEvent, loadIntegrationConfig } from '../src/integrations/index.js';
|
|
31
31
|
import { globFiles } from '../src/util/glob.js';
|
|
32
32
|
|
|
33
|
+
import { stateDir, statePath } from '../src/posture/state-dir.js';
|
|
33
34
|
// last-scan.json integrity helpers — implementation in posture/integrity.js
|
|
34
35
|
// so the MCP server tools can share verification.
|
|
35
36
|
function _verifyLastScan(body, sigFile) {
|
|
@@ -65,6 +66,13 @@ Commands:
|
|
|
65
66
|
verify [--finding <id>] Re-run the verifier loop on last-scan findings (use --live --target <url> to execute PoCs)
|
|
66
67
|
reset [--yes] [--keep ...] Right-to-delete: wipe accumulated learned state under .agentic-security/ (preserves operator-authored config)
|
|
67
68
|
rule-synth [--dry-run] Auto-synthesise suppression rules from repeated FP verdicts (proposes — does not activate)
|
|
69
|
+
compliance [--privacy] Assess the last scan against NIST Privacy Framework 1.1
|
|
70
|
+
--list show bundled + BYO frameworks
|
|
71
|
+
--walkthrough <id> auditor narrative for any framework
|
|
72
|
+
--gap only the failing controls
|
|
73
|
+
--format cli|json|md (default cli)
|
|
74
|
+
--fail-on gap exit 1 when a control is failing
|
|
75
|
+
Reads .agentic-security/last-scan.json — run a scan first.
|
|
68
76
|
version Print version
|
|
69
77
|
banner [--full] Print the Patch-the-frog mascot + brand lockup
|
|
70
78
|
harness [path] [--include-home] Multi-harness config audit: scans .claude/,
|
|
@@ -290,20 +298,23 @@ function _bundleSha() {
|
|
|
290
298
|
}
|
|
291
299
|
|
|
292
300
|
async function writeMachineOutput(targetAbs, scan, meta, profile) {
|
|
293
|
-
const
|
|
294
|
-
const { isSafeStateDir: _isSafe } = await import('../src/posture/state-dir.js');
|
|
295
|
-
|
|
296
|
-
|
|
301
|
+
const stateDirPath = stateDir(targetAbs);
|
|
302
|
+
const { isSafeStateDir: _isSafe, stateWritesEnabled: _writesOn } = await import('../src/posture/state-dir.js');
|
|
303
|
+
// Read-only scan (NON_MUTATING_SCAN_PRD S1). These writes bypass
|
|
304
|
+
// safeWriteState because they are async, so the switch is checked here — the
|
|
305
|
+
// same refusal path the project-root check already uses.
|
|
306
|
+
if (!_writesOn() || !_isSafe(stateDirPath)) {
|
|
307
|
+
if (process.env.AGENTIC_SECURITY_DEBUG === '1') process.stderr.write(`[agentic-security] refusing to write machine output at ${stateDirPath} — no project marker\n`);
|
|
297
308
|
return;
|
|
298
309
|
}
|
|
299
|
-
await fsp.mkdir(
|
|
310
|
+
await fsp.mkdir(stateDirPath, { recursive: true });
|
|
300
311
|
// Always JSON (used by /security-fix and /security-report).
|
|
301
|
-
await fsp.writeFile(path.join(
|
|
312
|
+
await fsp.writeFile(path.join(stateDirPath, 'findings.json'),
|
|
302
313
|
JSON.stringify(toJSON(scan, meta), null, 2));
|
|
303
314
|
if (profile.profile === 'pro' || profile.machineOutput) {
|
|
304
|
-
await fsp.writeFile(path.join(
|
|
315
|
+
await fsp.writeFile(path.join(stateDirPath, 'findings.sarif'),
|
|
305
316
|
JSON.stringify(toSARIF(scan, meta), null, 2));
|
|
306
|
-
await fsp.writeFile(path.join(
|
|
317
|
+
await fsp.writeFile(path.join(stateDirPath, 'findings.csv'), toCSV(scan));
|
|
307
318
|
}
|
|
308
319
|
}
|
|
309
320
|
|
|
@@ -325,6 +336,11 @@ function parseArgs(argv) {
|
|
|
325
336
|
}
|
|
326
337
|
|
|
327
338
|
async function cmdScan(args) {
|
|
339
|
+
// NON_MUTATING_SCAN_PRD S1 — a scan is an observation; --no-state makes it one.
|
|
340
|
+
if (args.flags['no-state']) {
|
|
341
|
+
const { setStateWritesEnabled } = await import('../src/posture/state-dir.js');
|
|
342
|
+
setStateWritesEnabled(false);
|
|
343
|
+
}
|
|
328
344
|
const target = args._[1] || '.';
|
|
329
345
|
const targetAbs = path.resolve(target);
|
|
330
346
|
// Load persona profile (R1). Persona-aware defaults flow from here.
|
|
@@ -430,7 +446,7 @@ async function cmdScan(args) {
|
|
|
430
446
|
}
|
|
431
447
|
|
|
432
448
|
// --set-baseline: save current findings as baseline for future --since-baseline filtering
|
|
433
|
-
const baselinePath =
|
|
449
|
+
const baselinePath = statePath(target || '.', 'baseline.json');
|
|
434
450
|
if (args.flags['set-baseline']) {
|
|
435
451
|
const { normalizeFindings } = await import('../src/report/index.js');
|
|
436
452
|
const baselineIds = new Set(normalizeFindings(scan).map(f => f.stableId || f.id));
|
|
@@ -646,10 +662,10 @@ async function cmdScan(args) {
|
|
|
646
662
|
else process.stdout.write(body + '\n');
|
|
647
663
|
|
|
648
664
|
// Persist last scan for /security-fix and /security-report
|
|
649
|
-
const { isSafeStateDir: _isSafeStateDir } = await import('../src/posture/state-dir.js');
|
|
650
|
-
const
|
|
651
|
-
if (_isSafeStateDir(
|
|
652
|
-
await fsp.mkdir(
|
|
665
|
+
const { isSafeStateDir: _isSafeStateDir, stateWritesEnabled: _writesOnScan } = await import('../src/posture/state-dir.js');
|
|
666
|
+
const stateDirPath = stateDir(path.resolve(target));
|
|
667
|
+
if (_writesOnScan() && _isSafeStateDir(stateDirPath)) {
|
|
668
|
+
await fsp.mkdir(stateDirPath, { recursive: true });
|
|
653
669
|
const persistedScan = toJSON(scan, meta);
|
|
654
670
|
// #10 — MTTR: stamp firstSeenAt/lastSeenAt/ageDays from the PREVIOUS scan so
|
|
655
671
|
// every finding carries an age, SLA breaches can be surfaced, and the fix
|
|
@@ -660,7 +676,7 @@ async function cmdScan(args) {
|
|
|
660
676
|
const { stampFindingTimestamps, buildBaselineMap, renderSlaSummary } = await import('../src/posture/mttr.js');
|
|
661
677
|
let baselineMap = new Map();
|
|
662
678
|
try {
|
|
663
|
-
const prev = JSON.parse(await fsp.readFile(path.join(
|
|
679
|
+
const prev = JSON.parse(await fsp.readFile(path.join(stateDirPath, 'last-scan.json'), 'utf8'));
|
|
664
680
|
baselineMap = buildBaselineMap(prev);
|
|
665
681
|
} catch { /* first run — empty baseline, everything is firstSeen now */ }
|
|
666
682
|
const now = Date.now();
|
|
@@ -709,18 +725,18 @@ async function cmdScan(args) {
|
|
|
709
725
|
} catch { /* best-effort, offline-degrading — never block a scan */ }
|
|
710
726
|
}
|
|
711
727
|
const lastScanBody = JSON.stringify(persistedScan, null, 2);
|
|
712
|
-
await fsp.writeFile(path.join(
|
|
728
|
+
await fsp.writeFile(path.join(stateDirPath, 'last-scan.json'), lastScanBody);
|
|
713
729
|
try {
|
|
714
|
-
await fsp.writeFile(path.join(
|
|
730
|
+
await fsp.writeFile(path.join(stateDirPath, 'last-scan.json.sig'), _signLastScan(lastScanBody));
|
|
715
731
|
} catch { /* non-fatal — sig file is best-effort */ }
|
|
716
732
|
} else {
|
|
717
|
-
if (process.env.AGENTIC_SECURITY_DEBUG === '1') process.stderr.write(`[agentic-security] refusing to write state at ${
|
|
733
|
+
if (process.env.AGENTIC_SECURITY_DEBUG === '1') process.stderr.write(`[agentic-security] refusing to write state at ${stateDirPath} — no project marker in ${path.resolve(target)}\n`);
|
|
718
734
|
}
|
|
719
735
|
|
|
720
736
|
// 0.14.0 — update streak / achievements after every full scan. Suppress
|
|
721
737
|
// streak side effects when the user only wants raw JSON output (CI piping).
|
|
722
738
|
try {
|
|
723
|
-
const streak = recordScan(
|
|
739
|
+
const streak = recordScan(stateDirPath, persistedScan);
|
|
724
740
|
// Print celebration / streak line to stderr so it doesn't pollute --format json
|
|
725
741
|
if (process.stderr.isTTY && format !== 'json' && format !== 'sarif') {
|
|
726
742
|
const delta = formatGradeDelta(streak);
|
|
@@ -799,18 +815,18 @@ async function cmdCi(args) {
|
|
|
799
815
|
if (packNames.length) Object.assign(scan, applyPacks(scan, packNames));
|
|
800
816
|
|
|
801
817
|
// Persist the three CI artifacts.
|
|
802
|
-
const
|
|
803
|
-
const { isSafeStateDir: _isSafeCi } = await import('../src/posture/state-dir.js');
|
|
804
|
-
if (!_isSafeCi(
|
|
805
|
-
if (process.env.AGENTIC_SECURITY_DEBUG === '1') process.stderr.write(`[agentic-security] refusing to write CI artifacts at ${
|
|
818
|
+
const stateDirPath = stateDir(targetAbs);
|
|
819
|
+
const { isSafeStateDir: _isSafeCi, stateWritesEnabled: _writesOnCi } = await import('../src/posture/state-dir.js');
|
|
820
|
+
if (!_writesOnCi() || !_isSafeCi(stateDirPath)) {
|
|
821
|
+
if (process.env.AGENTIC_SECURITY_DEBUG === '1') process.stderr.write(`[agentic-security] refusing to write CI artifacts at ${stateDirPath} — no project marker\n`);
|
|
806
822
|
return;
|
|
807
823
|
}
|
|
808
|
-
await fsp.mkdir(
|
|
809
|
-
await fsp.writeFile(path.join(
|
|
824
|
+
await fsp.mkdir(stateDirPath, { recursive: true });
|
|
825
|
+
await fsp.writeFile(path.join(stateDirPath, 'findings.json'),
|
|
810
826
|
JSON.stringify(toJSON(scan, meta), null, 2));
|
|
811
|
-
await fsp.writeFile(path.join(
|
|
827
|
+
await fsp.writeFile(path.join(stateDirPath, 'findings.sarif'),
|
|
812
828
|
JSON.stringify(toSARIF(scan, meta), null, 2));
|
|
813
|
-
await fsp.writeFile(path.join(
|
|
829
|
+
await fsp.writeFile(path.join(stateDirPath, 'findings.junit.xml'),
|
|
814
830
|
toJUnit(scan, meta));
|
|
815
831
|
|
|
816
832
|
const scanCode = exitCodeFor(scan);
|
|
@@ -872,7 +888,7 @@ async function cmdAccept(args) {
|
|
|
872
888
|
const id = args.flags.finding;
|
|
873
889
|
if (!id) { console.error('--finding <id> required'); return 4; }
|
|
874
890
|
const reason = args.flags.reason || 'vibecoded for now';
|
|
875
|
-
const lastScanPath =
|
|
891
|
+
const lastScanPath = statePath(target, 'findings.json');
|
|
876
892
|
if (!fs.existsSync(lastScanPath)) { console.error('No prior scan found. Run `agentic-security scan` first.'); return 4; }
|
|
877
893
|
const last = JSON.parse(await fsp.readFile(lastScanPath, 'utf8'));
|
|
878
894
|
const f = (last.findings || []).find(x => x.id === id);
|
|
@@ -930,7 +946,7 @@ async function cmdTriage(args) {
|
|
|
930
946
|
}
|
|
931
947
|
const sub = args._[1];
|
|
932
948
|
// Sync first so list reflects the latest scan.
|
|
933
|
-
const lastScanPath =
|
|
949
|
+
const lastScanPath = statePath(target, 'findings.json');
|
|
934
950
|
if (fs.existsSync(lastScanPath)) {
|
|
935
951
|
const last = JSON.parse(await fsp.readFile(lastScanPath, 'utf8'));
|
|
936
952
|
triage.syncWithScan(target, last.findings || []);
|
|
@@ -1256,7 +1272,7 @@ async function cmdRule(args) {
|
|
|
1256
1272
|
async function cmdValidatorCache(args) {
|
|
1257
1273
|
const sub = args._[1] || 'help';
|
|
1258
1274
|
const root = path.resolve(args._[2] || '.');
|
|
1259
|
-
const cacheDir =
|
|
1275
|
+
const cacheDir = statePath(root, 'llm-cache');
|
|
1260
1276
|
if (!fs.existsSync(cacheDir)) {
|
|
1261
1277
|
console.log(`No validator cache at ${cacheDir}`);
|
|
1262
1278
|
return 0;
|
|
@@ -1319,7 +1335,7 @@ async function cmdValidatorCache(args) {
|
|
|
1319
1335
|
// FR-VER-7 fail-closed: any error → cannot-verify, never silent drop.
|
|
1320
1336
|
async function cmdVerify(args) {
|
|
1321
1337
|
const scanRoot = path.resolve(args.flags.root || '.');
|
|
1322
|
-
const lastScanPath =
|
|
1338
|
+
const lastScanPath = statePath(scanRoot, 'last-scan.json');
|
|
1323
1339
|
if (!fs.existsSync(lastScanPath)) {
|
|
1324
1340
|
console.error(`No prior scan found at ${lastScanPath}. Run \`agentic-security scan\` first.`);
|
|
1325
1341
|
return 4;
|
|
@@ -1421,9 +1437,9 @@ async function cmdVerify(args) {
|
|
|
1421
1437
|
// --yes to skip the confirmation prompt (for scripted use).
|
|
1422
1438
|
async function cmdReset(args) {
|
|
1423
1439
|
const scanRoot = path.resolve(args.flags.root || '.');
|
|
1424
|
-
const
|
|
1425
|
-
if (!fs.existsSync(
|
|
1426
|
-
console.log(`No state to reset at ${
|
|
1440
|
+
const stateDirPath = stateDir(scanRoot);
|
|
1441
|
+
if (!fs.existsSync(stateDirPath)) {
|
|
1442
|
+
console.log(`No state to reset at ${stateDirPath}`);
|
|
1427
1443
|
return 0;
|
|
1428
1444
|
}
|
|
1429
1445
|
const WIPE = new Set([
|
|
@@ -1448,17 +1464,17 @@ async function cmdReset(args) {
|
|
|
1448
1464
|
]);
|
|
1449
1465
|
const keep = new Set((args.flags.keep || '').split(',').filter(Boolean));
|
|
1450
1466
|
const targets = [];
|
|
1451
|
-
for (const entry of await fsp.readdir(
|
|
1467
|
+
for (const entry of await fsp.readdir(stateDirPath, { withFileTypes: true })) {
|
|
1452
1468
|
if (keep.has(entry.name)) continue;
|
|
1453
1469
|
if (WIPE.has(entry.name) || WIPE_DIRS.has(entry.name)) {
|
|
1454
1470
|
targets.push({ name: entry.name, dir: entry.isDirectory() });
|
|
1455
1471
|
}
|
|
1456
1472
|
}
|
|
1457
1473
|
if (!targets.length) {
|
|
1458
|
-
console.log(`Nothing to reset under ${
|
|
1474
|
+
console.log(`Nothing to reset under ${stateDirPath}.`);
|
|
1459
1475
|
return 0;
|
|
1460
1476
|
}
|
|
1461
|
-
console.log(`agentic-security reset — will remove from ${
|
|
1477
|
+
console.log(`agentic-security reset — will remove from ${stateDirPath}:`);
|
|
1462
1478
|
for (const t of targets) console.log(` ${t.name}${t.dir ? '/' : ''}`);
|
|
1463
1479
|
console.log('');
|
|
1464
1480
|
console.log('Preserving operator-authored config: rules.yml, rules/, license-policy.yml, trusted-keys.json, ruleset-version.json');
|
|
@@ -1468,7 +1484,7 @@ async function cmdReset(args) {
|
|
|
1468
1484
|
return 0;
|
|
1469
1485
|
}
|
|
1470
1486
|
for (const t of targets) {
|
|
1471
|
-
const p = path.join(
|
|
1487
|
+
const p = path.join(stateDirPath, t.name);
|
|
1472
1488
|
try {
|
|
1473
1489
|
if (t.dir) await fsp.rm(p, { recursive: true, force: true });
|
|
1474
1490
|
else await fsp.rm(p, { force: true });
|
|
@@ -1493,7 +1509,7 @@ const HUNT_IGNORE = ['node_modules/**', '.git/**', 'dist/**', 'build/**', 'vendo
|
|
|
1493
1509
|
const HUNT_MAX_FILES = 2000;
|
|
1494
1510
|
|
|
1495
1511
|
async function cmdHunt(args) {
|
|
1496
|
-
const scanRoot = path.resolve(args.flags.root || args._[
|
|
1512
|
+
const scanRoot = path.resolve(args.flags.root || args._[1] || '.');
|
|
1497
1513
|
const { listFiles } = await import('../src/util/glob.js');
|
|
1498
1514
|
const { buildProjectIR } = await import('../src/ir/index.js');
|
|
1499
1515
|
const { runDiscovery } = await import('../src/discovery/index.js');
|
|
@@ -1517,19 +1533,43 @@ async function cmdHunt(args) {
|
|
|
1517
1533
|
// Prior scan and triage verdicts feed the judge so a hunt does not re-report
|
|
1518
1534
|
// what the rule engine already found or what a human already dismissed.
|
|
1519
1535
|
let priorScan = null, triageFeedback = null;
|
|
1520
|
-
try { priorScan = JSON.parse(fs.readFileSync(
|
|
1521
|
-
try { triageFeedback = JSON.parse(fs.readFileSync(
|
|
1536
|
+
try { priorScan = JSON.parse(fs.readFileSync(statePath(scanRoot, 'last-scan.json'), 'utf8')); } catch {}
|
|
1537
|
+
try { triageFeedback = JSON.parse(fs.readFileSync(statePath(scanRoot, 'triage-feedback.json'), 'utf8')); } catch {}
|
|
1522
1538
|
|
|
1523
1539
|
const lenses = args.flags.lens ? String(args.flags.lens).split(',').map(s => s.trim()).filter(Boolean) : undefined;
|
|
1540
|
+
const intFlag = (name) => (args.flags[name] ? parseInt(args.flags[name], 10) : undefined);
|
|
1541
|
+
const numFlag = (name) => (args.flags[name] ? Number(args.flags[name]) : undefined);
|
|
1524
1542
|
const report = await runDiscovery(
|
|
1525
1543
|
{ perFileIR: perFile, callGraph, fileContents, priorScan, triageFeedback },
|
|
1526
|
-
{
|
|
1544
|
+
{
|
|
1545
|
+
lenses,
|
|
1546
|
+
maxAreas: intFlag('max-areas'),
|
|
1547
|
+
// PRD Phase 0 / C3. Defaults live in discovery/index.js so the library is
|
|
1548
|
+
// bounded even when driven by something other than this CLI.
|
|
1549
|
+
maxLlmCalls: intFlag('max-llm-calls'),
|
|
1550
|
+
maxWallMs: intFlag('max-wall-ms'),
|
|
1551
|
+
maxCandidates: intFlag('max-candidates'),
|
|
1552
|
+
maxCostUsd: numFlag('max-cost-usd'),
|
|
1553
|
+
// PRD C4 — persist and consult cross-run memory.
|
|
1554
|
+
scanRoot: args.flags['no-memory'] ? undefined : scanRoot,
|
|
1555
|
+
// PRD C2 — a comma-separated endpoint list turns on consensus.
|
|
1556
|
+
endpoints: args.flags.endpoints,
|
|
1557
|
+
costPerCallUsd: numFlag('cost-per-call-usd'),
|
|
1558
|
+
},
|
|
1527
1559
|
);
|
|
1528
1560
|
|
|
1529
1561
|
const c = report.coverage;
|
|
1530
1562
|
console.log('');
|
|
1531
1563
|
console.log(`Discovery — ${rels.length} file(s), ${c.areasPlanned} focus area(s), ${c.lensesPerArea} lens(es) each`);
|
|
1532
1564
|
console.log(` areas hunted: ${c.areasHunted}/${c.areasPlanned} (fully: ${c.areasFullyHunted}) degraded runs: ${c.degradedRuns}/${report.runs.length}`);
|
|
1565
|
+
console.log(` budget: ${c.llmCalls}/${c.maxLlmCalls} LLM calls${c.budgetExhausted ? ' ← EXHAUSTED, run is incomplete' : ''}` +
|
|
1566
|
+
`${c.candidatesCapped ? ` ${c.candidatesCapped} candidate(s) capped` : ''}` +
|
|
1567
|
+
`${c.callsPerFinding !== null && c.callsPerFinding !== undefined ? ` ${c.callsPerFinding} calls/finding` : ''}`);
|
|
1568
|
+
if (c.priorRuns !== null && c.priorRuns !== undefined) {
|
|
1569
|
+
console.log(` memory: ${c.priorRuns} prior run(s)` +
|
|
1570
|
+
`${c.rememberedRefutals ? `, ${c.rememberedRefutals} candidate(s) skipped as already refuted` : ''}`);
|
|
1571
|
+
if (c.nextWave?.summary) console.log(` next wave: ${c.nextWave.summary}`);
|
|
1572
|
+
}
|
|
1533
1573
|
console.log(` confirmation: ${JSON.stringify(c.confirmedByTier)} panels: ${c.panelsRun} (undecided ${c.undecidedPanels})`);
|
|
1534
1574
|
console.log('');
|
|
1535
1575
|
|
|
@@ -1548,6 +1588,23 @@ async function cmdHunt(args) {
|
|
|
1548
1588
|
`${report.refutedCandidates.length} refuted by the panel)`);
|
|
1549
1589
|
}
|
|
1550
1590
|
|
|
1591
|
+
// PRD C5 — variant analysis. A confirmed finding is rarely alone: the same
|
|
1592
|
+
// root cause is usually copy-pasted, and the siblings often do not trip a
|
|
1593
|
+
// rule. sweepRootCauses already does this with exact accounting for scan
|
|
1594
|
+
// findings; discovery findings simply never reached it until now.
|
|
1595
|
+
if (report.fresh.length && !args.flags['no-variants']) {
|
|
1596
|
+
try {
|
|
1597
|
+
const { sweepRootCauses, formatSweepLedger } = await import('../src/posture/root-cause-sweep.js');
|
|
1598
|
+
const sweep = sweepRootCauses(report.fresh, fileContents, { confirmedOnly: false });
|
|
1599
|
+
const ledger = formatSweepLedger(sweep);
|
|
1600
|
+
if (ledger) {
|
|
1601
|
+
console.log('');
|
|
1602
|
+
console.log('Variant sweep (siblings of the findings above):');
|
|
1603
|
+
console.log(` ${ledger}`);
|
|
1604
|
+
}
|
|
1605
|
+
} catch { /* the sweep is additive; never let it sink the report */ }
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1551
1608
|
// Degradation is part of the output. A half-failed hunt must never read as a
|
|
1552
1609
|
// clean one, so every reason is printed rather than summarised away.
|
|
1553
1610
|
if (c.reasons.length) {
|
|
@@ -1567,6 +1624,171 @@ async function cmdHunt(args) {
|
|
|
1567
1624
|
return 0;
|
|
1568
1625
|
}
|
|
1569
1626
|
|
|
1627
|
+
// PRD D2 — emit signed, per-finding evidence bundles, and verify them.
|
|
1628
|
+
//
|
|
1629
|
+
// `attest` needs our private key. `verify-attestation` needs ONLY a public key,
|
|
1630
|
+
// which is the entire point: a buyer or auditor checks the artefact without ever
|
|
1631
|
+
// having had access to anything of ours.
|
|
1632
|
+
/**
|
|
1633
|
+
* `compliance` — framework assessment from the CLI.
|
|
1634
|
+
*
|
|
1635
|
+
* Reads the LAST SCAN rather than running a new one. A compliance answer is a
|
|
1636
|
+
* statement about a scan that happened, and silently re-scanning here would
|
|
1637
|
+
* make the number depend on when you asked rather than on what was measured —
|
|
1638
|
+
* the same class of confusion as a benchmark that mutates its own corpus. If
|
|
1639
|
+
* there is no scan to read, this says so and exits non-zero instead of
|
|
1640
|
+
* assessing an empty project, which would report controls as satisfied on the
|
|
1641
|
+
* strength of having looked at nothing.
|
|
1642
|
+
*/
|
|
1643
|
+
async function cmdCompliance(args) {
|
|
1644
|
+
const scanRoot = path.resolve(args.flags.root || '.');
|
|
1645
|
+
const fmt = String(args.flags.format || 'cli');
|
|
1646
|
+
const {
|
|
1647
|
+
assessPrivacyFramework, PRIVACY_FRAMEWORK_ID, BUCKETS,
|
|
1648
|
+
} = await import('../src/posture/privacy-framework.js');
|
|
1649
|
+
const { listFrameworks, loadFramework, evaluateFramework, renderWalkthrough } =
|
|
1650
|
+
await import('../src/posture/auditor-walkthrough.js');
|
|
1651
|
+
|
|
1652
|
+
if (args.flags.list) {
|
|
1653
|
+
const fws = listFrameworks(scanRoot);
|
|
1654
|
+
if (fmt === 'json') { console.log(JSON.stringify(fws, null, 2)); return 0; }
|
|
1655
|
+
for (const f of fws) console.log(` ${f.id.padEnd(20)} ${f.name} [${f.source}]`);
|
|
1656
|
+
return 0;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
let scan;
|
|
1660
|
+
try { scan = JSON.parse(fs.readFileSync(statePath(scanRoot, 'last-scan.json'), 'utf8')); }
|
|
1661
|
+
catch {
|
|
1662
|
+
console.error('No .agentic-security/last-scan.json — run `agentic-security scan .` first.');
|
|
1663
|
+
console.error('Refusing to assess an unscanned project: every control would report as');
|
|
1664
|
+
console.error('unassessed, which is correct but useless, and one flag away from looking clean.');
|
|
1665
|
+
return 2;
|
|
1666
|
+
}
|
|
1667
|
+
// The engine records this two ways depending on the emit path; take either.
|
|
1668
|
+
scan.filesScanned = scan._scanMeta?.filesScanned ?? scan.scanned?.files ?? 0;
|
|
1669
|
+
|
|
1670
|
+
const wt = args.flags.walkthrough;
|
|
1671
|
+
if (wt && wt !== true) {
|
|
1672
|
+
const fw = loadFramework(scanRoot, String(wt));
|
|
1673
|
+
if (!fw) {
|
|
1674
|
+
console.error(`Unknown framework "${wt}". Try --list.`);
|
|
1675
|
+
return 2;
|
|
1676
|
+
}
|
|
1677
|
+
console.log(renderWalkthrough(fw, evaluateFramework(scanRoot, fw, scan), {}));
|
|
1678
|
+
return 0;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
// Default mode is --privacy: it is the only framework with a remediation
|
|
1682
|
+
// layer, so it is the only one where a CLI exit code means anything.
|
|
1683
|
+
const r = assessPrivacyFramework(scanRoot, scan);
|
|
1684
|
+
if (!r) { console.error(`Framework ${PRIVACY_FRAMEWORK_ID} could not be loaded.`); return 2; }
|
|
1685
|
+
|
|
1686
|
+
const gapsOnly = !!args.flags.gap;
|
|
1687
|
+
if (fmt === 'json') {
|
|
1688
|
+
console.log(JSON.stringify(gapsOnly ? { ...r, controls: r.controls.filter(c => c.bucket === 'gap') } : r, null, 2));
|
|
1689
|
+
} else if (fmt === 'md') {
|
|
1690
|
+
console.log(fs.readFileSync(statePath(scanRoot, 'privacy-framework.md'), 'utf8'));
|
|
1691
|
+
} else {
|
|
1692
|
+
console.log(`\n${r.frameworkName}\n`);
|
|
1693
|
+
console.log(` ${r.interpretation}\n`);
|
|
1694
|
+
for (const b of BUCKETS) {
|
|
1695
|
+
const rows = r.controls.filter(c => c.bucket === b);
|
|
1696
|
+
if (!rows.length || (gapsOnly && b !== 'gap')) continue;
|
|
1697
|
+
console.log(` ${b} (${rows.length})`);
|
|
1698
|
+
for (const c of rows) {
|
|
1699
|
+
console.log(` ${c.id.padEnd(11)} ${c.summary.slice(0, 84)}`);
|
|
1700
|
+
const f = r.findings.find(x => x.id === `privacy-framework:${c.id}`);
|
|
1701
|
+
if (f) console.log(` → ${f.remediation}`);
|
|
1702
|
+
}
|
|
1703
|
+
console.log('');
|
|
1704
|
+
}
|
|
1705
|
+
console.log(' A control that is "manual" or "engine-gap" is NOT evidence of compliance.');
|
|
1706
|
+
console.log(' This organizes scanner evidence; a licensed assessor owns the attestation.\n');
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// --fail-on gap is opt-in. A compliance opinion becomes a build failure only
|
|
1710
|
+
// when someone asks for it; defaulting to non-zero would break every pipeline
|
|
1711
|
+
// that adds this command to see the report.
|
|
1712
|
+
if (args.flags['fail-on'] === 'gap' && r.summary.gap > 0) return 1;
|
|
1713
|
+
return 0;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
async function cmdAttest(args) {
|
|
1717
|
+
const scanRoot = path.resolve(args.flags.root || '.');
|
|
1718
|
+
const {
|
|
1719
|
+
ensureKeyPair, buildEvidenceBundle, signEvidenceBundle,
|
|
1720
|
+
} = await import('../src/posture/evidence-bundle.js');
|
|
1721
|
+
|
|
1722
|
+
let scan;
|
|
1723
|
+
try { scan = JSON.parse(fs.readFileSync(statePath(scanRoot, 'last-scan.json'), 'utf8')); }
|
|
1724
|
+
catch { console.error('No .agentic-security/last-scan.json — run a scan first.'); return 2; }
|
|
1725
|
+
|
|
1726
|
+
const findings = scan.findings || [];
|
|
1727
|
+
const wanted = args.flags.id;
|
|
1728
|
+
const subset = wanted ? findings.filter(f => f.id === wanted || f.stableId === wanted) : findings;
|
|
1729
|
+
if (!subset.length) {
|
|
1730
|
+
console.error(wanted ? `No finding matching "${wanted}".` : 'No findings to attest.');
|
|
1731
|
+
return 2;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
const kp = ensureKeyPair();
|
|
1735
|
+
if (kp.created) console.error(`Generated a new signing key at ${kp.privateKey} (public: ${kp.publicKey}).`);
|
|
1736
|
+
|
|
1737
|
+
const outDir = statePath(scanRoot, 'attestations');
|
|
1738
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
1739
|
+
const meta = {
|
|
1740
|
+
engineVersion: scan.engineVersion || null,
|
|
1741
|
+
rulesetVersion: scan.rulesetVersion || null,
|
|
1742
|
+
bundleSha: scan.bundleSha || null,
|
|
1743
|
+
commit: scan.commit || null,
|
|
1744
|
+
};
|
|
1745
|
+
|
|
1746
|
+
let n = 0;
|
|
1747
|
+
for (const f of subset) {
|
|
1748
|
+
const bundle = signEvidenceBundle(buildEvidenceBundle(f, meta), kp.privateKeyPem);
|
|
1749
|
+
const name = `${(f.stableId || f.id || `finding-${n}`)}.json`.replace(/[^\w.-]/g, '_');
|
|
1750
|
+
fs.writeFileSync(path.join(outDir, name), JSON.stringify(bundle, null, 2) + '\n');
|
|
1751
|
+
n++;
|
|
1752
|
+
}
|
|
1753
|
+
console.log(`Signed ${n} evidence bundle(s) → ${path.relative(scanRoot, outDir)}/`);
|
|
1754
|
+
console.log(`Public key (share this with whoever verifies): ${kp.publicKey}`);
|
|
1755
|
+
console.log('');
|
|
1756
|
+
console.log('A bundle proves its contents are unmodified since signing. It does NOT');
|
|
1757
|
+
console.log('prove the finding is real — read evidence.proofTier for that.');
|
|
1758
|
+
return 0;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
async function cmdVerifyAttestation(args) {
|
|
1762
|
+
const { verifyEvidenceBundle, keyPaths } = await import('../src/posture/evidence-bundle.js');
|
|
1763
|
+
// `args._[0]` is the command name itself — the same convention cmdScan uses.
|
|
1764
|
+
const file = args.flags.bundle || args._[1];
|
|
1765
|
+
if (!file) { console.error('Usage: agentic-security verify-attestation <bundle.json> [--public-key <path>]'); return 2; }
|
|
1766
|
+
|
|
1767
|
+
let bundle;
|
|
1768
|
+
try { bundle = JSON.parse(fs.readFileSync(path.resolve(file), 'utf8')); }
|
|
1769
|
+
catch (e) { console.error(`Could not read bundle: ${e.message}`); return 2; }
|
|
1770
|
+
|
|
1771
|
+
const keyFile = args.flags['public-key'] || keyPaths().publicKey;
|
|
1772
|
+
let publicKeyPem = null;
|
|
1773
|
+
try { publicKeyPem = fs.readFileSync(path.resolve(keyFile), 'utf8'); }
|
|
1774
|
+
catch { console.error(`Could not read public key at ${keyFile}. Pass --public-key <path>.`); return 2; }
|
|
1775
|
+
|
|
1776
|
+
const r = verifyEvidenceBundle(bundle, publicKeyPem);
|
|
1777
|
+
if (!r.ok) {
|
|
1778
|
+
console.error(`✗ INVALID — ${r.reason}`);
|
|
1779
|
+
return 1;
|
|
1780
|
+
}
|
|
1781
|
+
const f = bundle.finding || {};
|
|
1782
|
+
console.log('✓ VALID — the bundle is exactly what the signer attested.');
|
|
1783
|
+
console.log('');
|
|
1784
|
+
console.log(` ${f.severity ? `[${f.severity}] ` : ''}${f.vuln || '(no title)'} ${f.file || '?'}:${f.line ?? '?'}`);
|
|
1785
|
+
console.log(` cwe ${f.cwe || 'n/a'} parser ${f.parser || 'n/a'} proof tier ${bundle.evidence?.proofTier || 'n/a'}`);
|
|
1786
|
+
console.log('');
|
|
1787
|
+
console.log(` proves: ${bundle.proves}`);
|
|
1788
|
+
console.log(` does NOT prove: ${bundle.doesNotProve}`);
|
|
1789
|
+
return 0;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1570
1792
|
async function cmdRuleSynth(args) {
|
|
1571
1793
|
const scanRoot = path.resolve(args.flags.root || '.');
|
|
1572
1794
|
const { synthesizeRules } = await import('../src/posture/rule-synthesis.js');
|
|
@@ -1603,7 +1825,7 @@ async function cmdPacks(args) {
|
|
|
1603
1825
|
async function cmdDigest(args) {
|
|
1604
1826
|
const target = path.resolve(args._[1] || '.');
|
|
1605
1827
|
const profile = loadProfile(target);
|
|
1606
|
-
const lastScanPath =
|
|
1828
|
+
const lastScanPath = statePath(target, 'findings.json');
|
|
1607
1829
|
if (!fs.existsSync(lastScanPath)) { console.error('No prior scan found.'); return 4; }
|
|
1608
1830
|
const last = JSON.parse(await fsp.readFile(lastScanPath, 'utf8'));
|
|
1609
1831
|
const findings = (last.findings || []).filter(f => f.severity === 'critical' || f.severity === 'high');
|
|
@@ -1631,7 +1853,7 @@ async function cmdFix(args) {
|
|
|
1631
1853
|
const isApply = !!args.flags.apply;
|
|
1632
1854
|
const scanRoot = path.resolve(args.flags.root || '.');
|
|
1633
1855
|
if (!id) { console.error('--finding <id> required'); return 4; }
|
|
1634
|
-
const lastScanPath =
|
|
1856
|
+
const lastScanPath = statePath(scanRoot, 'last-scan.json');
|
|
1635
1857
|
if (!fs.existsSync(lastScanPath)) { console.error('No prior scan found. Run `agentic-security scan` first.'); return 4; }
|
|
1636
1858
|
const lastScanBody = await fsp.readFile(lastScanPath, 'utf8');
|
|
1637
1859
|
const sigVerified = _verifyLastScan(lastScanBody, lastScanPath + '.sig');
|
|
@@ -1860,6 +2082,9 @@ async function main() {
|
|
|
1860
2082
|
case 'verify': process.exit(await cmdVerify(args));
|
|
1861
2083
|
case 'reset': process.exit(await cmdReset(args));
|
|
1862
2084
|
case 'hunt': process.exit(await cmdHunt(args));
|
|
2085
|
+
case 'compliance': process.exit(await cmdCompliance(args));
|
|
2086
|
+
case 'attest': process.exit(await cmdAttest(args));
|
|
2087
|
+
case 'verify-attestation': process.exit(await cmdVerifyAttestation(args));
|
|
1863
2088
|
case 'rule-synth': process.exit(await cmdRuleSynth(args));
|
|
1864
2089
|
case 'digest': process.exit(await cmdDigest(args));
|
|
1865
2090
|
case 'setup': process.exit(await cmdSetup(args));
|
|
@@ -1882,7 +2107,7 @@ async function main() {
|
|
|
1882
2107
|
const result = analyzeTranscript({ transcriptPath: args.flags.transcript, projectDir });
|
|
1883
2108
|
if (result.ok) {
|
|
1884
2109
|
try {
|
|
1885
|
-
const dir =
|
|
2110
|
+
const dir = stateDir(projectDir);
|
|
1886
2111
|
fs.mkdirSync(dir, { recursive: true });
|
|
1887
2112
|
fs.writeFileSync(path.join(dir, 'cache-telemetry.json'),
|
|
1888
2113
|
JSON.stringify({ updatedAt: new Date().toISOString(), metrics: result.metrics, leaks: result.leaks }, null, 2));
|
package/dist/113.index.js
CHANGED
|
@@ -52,7 +52,6 @@ export const modules = {
|
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
const STATE_DIR = '.agentic-security';
|
|
56
55
|
const LOG_FILE = 'fix-metrics.jsonl';
|
|
57
56
|
|
|
58
57
|
// Below this many samples a percentile is an artifact of the sample, not a
|
|
@@ -65,7 +64,7 @@ const RELIABLE_N = 10;
|
|
|
65
64
|
const FIX_STAGES = Object.freeze(['rescan', 'lint', 'tests', 'honesty', 'poc']);
|
|
66
65
|
|
|
67
66
|
function _logPath(scanRoot) {
|
|
68
|
-
return
|
|
67
|
+
return (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, LOG_FILE);
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
/**
|
|
@@ -77,9 +76,10 @@ function _logPath(scanRoot) {
|
|
|
77
76
|
function recordFixAttempt(scanRoot, record) {
|
|
78
77
|
if (!scanRoot || !record || typeof record !== 'object') return false;
|
|
79
78
|
try {
|
|
80
|
-
const dir =
|
|
79
|
+
const dir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .stateDir */ .Pn)(scanRoot);
|
|
81
80
|
if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.isSafeStateDir)(dir)) return false;
|
|
82
|
-
|
|
81
|
+
if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.stateWritesEnabled)()) return;
|
|
82
|
+
node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(dir, { recursive: true });
|
|
83
83
|
// One writeSync of one newline-terminated line: a concurrent reader sees
|
|
84
84
|
// whole records or nothing, and a torn tail is dropped on read.
|
|
85
85
|
node_fs__WEBPACK_IMPORTED_MODULE_0__.appendFileSync(_logPath(scanRoot), JSON.stringify(record) + '\n', 'utf8');
|
|
@@ -406,8 +406,8 @@ var external_node_child_process_ = __webpack_require__(1421);
|
|
|
406
406
|
var external_node_fs_ = __webpack_require__(3024);
|
|
407
407
|
// EXTERNAL MODULE: external "node:path"
|
|
408
408
|
var external_node_path_ = __webpack_require__(6760);
|
|
409
|
-
// EXTERNAL MODULE: ./src/engine.js +
|
|
410
|
-
var engine = __webpack_require__(
|
|
409
|
+
// EXTERNAL MODULE: ./src/engine.js + 499 modules
|
|
410
|
+
var engine = __webpack_require__(2912);
|
|
411
411
|
;// CONCATENATED MODULE: ./src/posture/fix-honesty-gate.js
|
|
412
412
|
// Deterministic honesty gates on fix / finding output (#7).
|
|
413
413
|
//
|
package/dist/117.index.js
CHANGED
|
@@ -12,6 +12,7 @@ export const modules = {
|
|
|
12
12
|
/* unused harmony exports badgeFromScanRoot, renderSvg, _internal */
|
|
13
13
|
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3024);
|
|
14
14
|
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6760);
|
|
15
|
+
/* harmony import */ var _posture_state_dir_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(1174);
|
|
15
16
|
// Live SVG badge generator (v0.72).
|
|
16
17
|
//
|
|
17
18
|
// Every repo can drop a badge in its README pulling from the latest scan:
|
|
@@ -42,6 +43,7 @@ export const modules = {
|
|
|
42
43
|
|
|
43
44
|
|
|
44
45
|
|
|
46
|
+
|
|
45
47
|
const COLORS = {
|
|
46
48
|
critical: '#e05d44', // red
|
|
47
49
|
high: '#fe7d37', // orange
|
|
@@ -56,7 +58,7 @@ const SEVERITIES = ['critical', 'high', 'medium', 'low', 'info'];
|
|
|
56
58
|
|
|
57
59
|
function _readLastScan(scanRoot) {
|
|
58
60
|
if (!scanRoot) return null;
|
|
59
|
-
const fp =
|
|
61
|
+
const fp = (0,_posture_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, 'last-scan.json');
|
|
60
62
|
if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(fp)) return null;
|
|
61
63
|
try { return JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8')); }
|
|
62
64
|
catch { return null; }
|
package/dist/178.index.js
CHANGED
|
@@ -13,7 +13,7 @@ export const modules = {
|
|
|
13
13
|
/* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1421);
|
|
14
14
|
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3024);
|
|
15
15
|
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6760);
|
|
16
|
-
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(
|
|
16
|
+
/* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2912);
|
|
17
17
|
// Time-travel + counterfactual scanning (v0.68).
|
|
18
18
|
//
|
|
19
19
|
// Two new modes that exploit the pure-input shape of runFullScan:
|
package/dist/220.index.js
CHANGED
|
@@ -15,6 +15,7 @@ export const modules = {
|
|
|
15
15
|
/* harmony import */ var node_fs_promises__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1455);
|
|
16
16
|
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3024);
|
|
17
17
|
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6760);
|
|
18
|
+
/* harmony import */ var _state_dir_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(1174);
|
|
18
19
|
// Watch mode — continuous incremental scan as the developer edits.
|
|
19
20
|
//
|
|
20
21
|
// Spawns a long-running scan watcher that:
|
|
@@ -40,7 +41,7 @@ export const modules = {
|
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
|
|
44
45
|
const STATUS_MD = 'watch-status.md';
|
|
45
46
|
const STATUS_JSON = 'watch-status.json';
|
|
46
47
|
const DEBOUNCE_MS = 350;
|
|
@@ -102,7 +103,8 @@ function renderStatusLine(delta) {
|
|
|
102
103
|
* Persist watch-status.{md,json}. Cheap atomic write (write tmp, rename).
|
|
103
104
|
*/
|
|
104
105
|
function persistStatus(scanRoot, delta) {
|
|
105
|
-
const dir =
|
|
106
|
+
const dir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_3__/* .stateDir */ .Pn)(scanRoot);
|
|
107
|
+
if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_3__.stateWritesEnabled)()) return;
|
|
106
108
|
try { node_fs__WEBPACK_IMPORTED_MODULE_1__.mkdirSync(dir, { recursive: true }); } catch {}
|
|
107
109
|
const status = {
|
|
108
110
|
ts: new Date().toISOString(),
|
|
@@ -139,7 +141,7 @@ function persistStatus(scanRoot, delta) {
|
|
|
139
141
|
* Read the latest watch-status (returns null if none).
|
|
140
142
|
*/
|
|
141
143
|
function readStatus(scanRoot) {
|
|
142
|
-
return _readJsonSafe(
|
|
144
|
+
return _readJsonSafe(statePath(scanRoot, STATUS_JSON));
|
|
143
145
|
}
|
|
144
146
|
|
|
145
147
|
/**
|
package/dist/238.index.js
CHANGED
|
@@ -52,7 +52,6 @@ export const modules = {
|
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
const STATE_DIR = '.agentic-security';
|
|
56
55
|
const LOG_FILE = 'fix-metrics.jsonl';
|
|
57
56
|
|
|
58
57
|
// Below this many samples a percentile is an artifact of the sample, not a
|
|
@@ -65,7 +64,7 @@ const RELIABLE_N = 10;
|
|
|
65
64
|
const FIX_STAGES = Object.freeze(['rescan', 'lint', 'tests', 'honesty', 'poc']);
|
|
66
65
|
|
|
67
66
|
function _logPath(scanRoot) {
|
|
68
|
-
return
|
|
67
|
+
return (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .statePath */ .BQ)(scanRoot, LOG_FILE);
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
/**
|
|
@@ -77,9 +76,10 @@ function _logPath(scanRoot) {
|
|
|
77
76
|
function recordFixAttempt(scanRoot, record) {
|
|
78
77
|
if (!scanRoot || !record || typeof record !== 'object') return false;
|
|
79
78
|
try {
|
|
80
|
-
const dir =
|
|
79
|
+
const dir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .stateDir */ .Pn)(scanRoot);
|
|
81
80
|
if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.isSafeStateDir)(dir)) return false;
|
|
82
|
-
|
|
81
|
+
if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.stateWritesEnabled)()) return;
|
|
82
|
+
node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(dir, { recursive: true });
|
|
83
83
|
// One writeSync of one newline-terminated line: a concurrent reader sees
|
|
84
84
|
// whole records or nothing, and a torn tail is dropped on read.
|
|
85
85
|
node_fs__WEBPACK_IMPORTED_MODULE_0__.appendFileSync(_logPath(scanRoot), JSON.stringify(record) + '\n', 'utf8');
|