@clear-capabilities/agentic-security-scanner 0.195.29 → 0.195.30

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/bin/agentic-security.js +6 -3
  3. package/dist/1340.index.js +11 -6
  4. package/dist/3500.index.js +943 -0
  5. package/dist/5616.index.js +146 -19
  6. package/dist/{c06a1b4c20155c06fb48.json → 7e58a58b8e6ccdde1659.json} +6 -5
  7. package/dist/9560.index.js +29 -5
  8. package/dist/agentic-security.mjs +2 -2
  9. package/dist/agentic-security.mjs.sha256 +1 -1
  10. package/package.json +6 -5
  11. package/src/egress/policy.js +79 -1
  12. package/src/lineage/CLAUDE.md +3 -1
  13. package/src/lineage/deployment/adapter-cloud.js +5 -2
  14. package/src/lineage/deployment/attack-paths.js +107 -30
  15. package/src/lineage/deployment/boundaries-run.js +2 -2
  16. package/src/lineage/deployment/iam-eval.js +105 -0
  17. package/src/lineage/deployment/projection.js +10 -4
  18. package/src/lineage/deployment/proof-binding.js +79 -0
  19. package/src/llm-validator/bounded-context.js +2 -1
  20. package/src/mcp/CLAUDE.md +6 -0
  21. package/src/mcp/tools.js +27 -3
  22. package/src/pipeline/evidence-record.js +1 -1
  23. package/src/posture/CLAUDE.md +2 -0
  24. package/src/posture/assurance/candidate-qualification.js +155 -0
  25. package/src/posture/assurance/release-package-cli.js +44 -2
  26. package/src/posture/auditor-walkthrough.js +2 -1
  27. package/src/posture/dnr/detection-ir.js +2 -2
  28. package/src/posture/dnr/hunt.js +22 -6
  29. package/src/posture/dnr/log-reader.js +47 -3
  30. package/src/posture/dnr/matchers.js +43 -3
  31. package/src/posture/dnr/outputs.js +16 -2
  32. package/src/posture/dnr/payload-classes.js +12 -2
  33. package/src/posture/dnr/validate.js +17 -4
  34. package/src/posture/evaluation/report.js +2 -1
  35. package/src/posture/evaluation/score.js +24 -7
  36. package/src/posture/logic-claims.js +15 -14
  37. package/src/posture/sibling-queue.js +69 -0
  38. package/src/posture/triage-memory.js +106 -26
  39. package/src/posture/verification-separation.js +55 -7
  40. package/src/report/index.js +4 -0
  41. package/src/runScan.js +5 -0
  42. package/src/scope/flags.js +1 -0
  43. package/src/scope/inventory.js +17 -1
  44. package/src/scope/release-scope.js +80 -0
package/CHANGELOG.md CHANGED
@@ -9,6 +9,37 @@
9
9
  > make the history less accurate, not more.
10
10
 
11
11
 
12
+ ## 0.195.30 - Threat model hardening: verdicts bound to what they tested, honest path limits, a fail-closed restricted profile, and candidate qualification
13
+
14
+ **Measured, 0.195.29:** `TIMINGS preflight=6s push=13s pr=4s checks=737s merge=5s verify=10s tag=2s release=254s npm=146s total=1177s release-clock=271s`, shipped from the merge to the published release in 271 s; the npm listing lagged the publish and `ship --resume` confirmed it.
15
+
16
+ The locally verifiable part of the threat model hardening plan. Each of the nine reproduced baseline behaviours (TM-01 to TM-09) has a failing control that now passes. Staging delivery, independent accuracy measurement, attested execution and owner approvals are separate qualifications and are not claimed: the candidate packet records each as blocked. Nothing moved a maturity. Per-requirement state: `docs/implementation-status/threat-model-hardening.md`.
17
+
18
+ - **Changed results, listed first.**
19
+ - A hunt's target counter counts only events with the matched route template and method, so an unrelated route no longer inflates it.
20
+ - An authoritative `authenticated: false` survives a redacted authorization header: a header alone shows a credential was present, not that it was accepted.
21
+ - An IAM deny blocks only the action, resource and condition it matches; a conditional or opaque deny that can affect the action leaves the path `unresolved` instead of blocked.
22
+ - A family or directory triage bucket is advisory: a new high finding keeps its confidence.
23
+ - Corroboration is counted by independent evidence group, not by lens.
24
+ - **Detection (FR-07).** Credential presence and authentication outcome are separate. A new `object-authz` rule detects an authenticated actor receiving another tenant's object. Hunts report window gaps, and a detection signal is never reported as a corroborated effect on its own. CWE-639 moved to the new payload class; the detection IR documents the change.
25
+ - **Scope (FR-01).** Standard JSON carries the `scope` object and a deterministic `inventoryDigest` over every eligibility decision before display caps. Scope preview agrees with it. A release scope reconciliation reports each critical route or unit as covered, excluded, unsupported or unknown.
26
+ - **IAM and paths (FR-03).** Per-action deny evaluation, the `StringEquals`, `StringNotEquals`, `Bool` and `ArnEquals` condition subset, and `truncation.reasons` that tell depth, expansion, result-count and deadline cutoffs apart. A search that hit a bound cannot certify the absence of a path. `boundaries --action` queries one action. The boundary context version is 1.1.0, with optional fields only.
27
+ - **Proof binding (FR-04).** A deployed path is promoted only by an Ed25519-signed proof binding from a trusted issuer. The binding covers the revision, artifact, policy, actor, tenant, route, scenario and oracle. A receipt from another revision or context keeps its stable id with a distinct diagnostic.
28
+ - **Learned decisions (FR-05).** Exact, scoped, HMAC-signed triage decisions apply; bucket matches, expired, copied, cross-tenant and unsigned records do not. A confirmed root cause yields a sibling review queue with six separate states. The legacy `mitigated` count keeps its detector-coverage meaning.
29
+ - **Restricted egress (FR-06).** `AGENTIC_SECURITY_EGRESS_PROFILE=restricted` requires its authoritative policy and fails closed on a malformed, unreadable, missing or unsupported one before any model request. An operator deny always wins. High-impact MCP approvals can be bound to tool, action, target and change, with an expiry; the restricted profile requires that binding.
30
+ - **Pilot (FR-02, FR-08).** Loopback fixtures cover:
31
+ - a two-tenant records service (PILOT-01 to PILOT-08);
32
+ - the order approval invariant (wrong guard, then patched);
33
+ - an app-to-cloud chain whose object store adjudicates with its own evaluator;
34
+ - a bounded SQLite injection proof (PILOT-12).
35
+ - **Candidate qualification (FR-10).**
36
+ - A signed candidate packet; gates G-01 to G-07 are recomputed from case evidence, never read from the packet.
37
+ - `agentic-security release-package qualify <packet> --public-key <pem>` exits 0 only when qualified.
38
+ - Each case carries a `local`, `staging` or `independent` tier, and a gate that requires a tier (`--require-tier G-05=staging`) counts only cases of that tier.
39
+ - `revalidateDecision` refuses a decision for another artifact, policy, scanner bundle or packet, or after its evidence expires.
40
+ - `npm run pilot:qualify` writes one signed packet per deployment identity. On a developer host it reports "not qualified", with the blocked staging, independent and attested-runner cases named.
41
+ - **Evaluation scoring (FR-09.2).** Every enrolled label is reconciled to one status. A negative label whose target the run never reached used to leave the count without a trace; it is now counted as `negativesOutsideRun`.
42
+
12
43
  ## 0.195.29 - `scan --watch` watches, the validator collects callers and configuration, and pilot tooling for the measured targets
13
44
 
14
45
  **Measured, 0.195.28:** `TIMINGS preflight=3s push=6s pr=1s checks=430s merge=6s verify=6s tag=2s release=131s npm=450s total=1035s release-clock=145s`, shipped from the merge to the published release in 145 s; the run stopped once on a git index lock held by the post-commit gate warm-up, and `ship --resume` continued after it.
@@ -147,6 +147,7 @@ Commands:
147
147
  release-view <package.json> Developer, CISO and auditor views of one signed release package (--view, --format text|json|html, --previous, --public-key)
148
148
  metrics [path] Assurance Coverage, Evidence Conversion and Verified Remediation Rate for the last scan, each with numerator and denominator (--format text|json)
149
149
  release-package build|verify Build a signed release package from the last scan (--private-key, --output, --fix-records), or check one offline (--public-key)
150
+ release-package qualify <packet.json> --public-key <pem>: recompute candidate gates G-01..G-07 from a signed candidate packet (exit 0 only when qualified; --require-case, --require-gate, --require-tier G-05=staging, --output, --format json)
150
151
  reset [--yes] [--keep ...] Right-to-delete: wipe accumulated learned state under .agentic-security/ (preserves operator-authored config)
151
152
  --expired only remove artifacts past their retention-class TTL
152
153
  Every run writes a deletion-report.json proving what was planned/deleted/preserved/failed.
@@ -181,7 +182,7 @@ Commands:
181
182
  scan-baseline --current <f> --previous <f>
182
183
  Finding-level diff between two scan JSON outputs.
183
184
  Reports added / removed / changed findings.
184
- boundaries --from <dir> [--findings <last-scan.json>] [--environment <name>] [--out <file>] [--json]
185
+ boundaries --from <dir> [--findings <last-scan.json>] [--environment <name>] [--out <file>] [--json] [--action <iam-action>]
185
186
  Build a deployment boundary graph from local configuration
186
187
  (and optional sanitized traces) and attach a boundary context
187
188
  to each finding of a scan result. Read-only, local files only.
@@ -4166,9 +4167,9 @@ Do not suppress anything you are not certain is a false positive. When in doubt,
4166
4167
  // 3 the feature is disabled, blocked or unsupported on this platform (nothing was read).
4167
4168
  async function cmdBoundaries(args) {
4168
4169
  const f = args.flags;
4169
- const usage = 'Usage: agentic-security boundaries --from <dir> [--findings <last-scan.json>] [--environment <name>] [--repository <name>] [--revision <sha>] [--root <project>] [--out <file>] [--json] [--now <iso>]';
4170
+ const usage = 'Usage: agentic-security boundaries --from <dir> [--findings <last-scan.json>] [--environment <name>] [--repository <name>] [--revision <sha>] [--root <project>] [--out <file>] [--json] [--now <iso>] [--action <iam-action>]';
4170
4171
  if (typeof f.from !== 'string' || !f.from) { console.error(usage); return 2; }
4171
- for (const k of ['findings', 'environment', 'repository', 'revision', 'out', 'now', 'root']) {
4172
+ for (const k of ['findings', 'environment', 'repository', 'revision', 'out', 'now', 'root', 'action']) {
4172
4173
  if (f[k] !== undefined && (typeof f[k] !== 'string' || !f[k])) { console.error(`agentic-security boundaries: --${k} needs a value\n${usage}`); return 2; }
4173
4174
  }
4174
4175
  const scanRoot = path.resolve(f.root || '.');
@@ -4178,6 +4179,8 @@ async function cmdBoundaries(args) {
4178
4179
  const result = runBoundaries({
4179
4180
  config, from: path.resolve(f.from), environment: f.environment || 'prod', repository: f.repository || null, revision: f.revision || null,
4180
4181
  findingsFile: f.findings ? path.resolve(f.findings) : null, verifySignature: _verifyLastScan, now: f.now,
4182
+ // threat-model FR-03.2: an action-specific query (`--action s3:GetObject`) decides each IAM hop for that action only
4183
+ action: f.action || null,
4181
4184
  });
4182
4185
  if (result.status === 'disabled' || result.status === 'blocked' || result.status === 'unsupported') {
4183
4186
  console.error(`agentic-security boundaries: not run (${result.status}): ${result.reason}`);
@@ -666,6 +666,8 @@ function parseCompose(text, fileCtx) {
666
666
  return kit.result();
667
667
  }
668
668
 
669
+ // EXTERNAL MODULE: ./src/lineage/deployment/iam-eval.js
670
+ var iam_eval = __webpack_require__(1086);
669
671
  ;// CONCATENATED MODULE: ./src/lineage/deployment/adapter-cloud.js
670
672
  // adapter-cloud.js: Terraform plan JSON and IAM policy documents into
671
673
  // deployment boundary nodes and edges (X-302).
@@ -679,6 +681,7 @@ function parseCompose(text, fileCtx) {
679
681
 
680
682
 
681
683
 
684
+
682
685
  const TERRAFORM_PARSER = 'terraform-plan-json';
683
686
  const TERRAFORM_PARSER_VERSION = '1';
684
687
  const IAM_PARSER = 'iam-policy-json';
@@ -708,10 +711,12 @@ function applyPolicy(kit, identity, policy, subject) {
708
711
  const conditioned = isObject(st.Condition) && Object.keys(st.Condition).length > 0;
709
712
  read += 1;
710
713
  if (resources.length > MAX_RESOURCES_PER_STATEMENT) kit.gap('limit-exceeded', subject, `a statement lists ${resources.length} resources; only the first ${MAX_RESOURCES_PER_STATEMENT} are read`);
714
+ if (actions.join(',').length > 250) kit.gap('limit-exceeded', subject, 'a statement\'s action list is longer than the graph records; the clipped list leaves decisions about the missing actions unresolved');
715
+ if (conditioned && (0,iam_eval/* statementAttrs */.c2)(actions, st.Condition).conditionSupported !== true) kit.gap('unsupported-syntax', subject, 'a statement condition uses an operator or value outside the supported subset (StringEquals, StringNotEquals, Bool, ArnEquals); decisions it can affect stay unresolved');
711
716
  for (const r of resources.slice(0, MAX_RESOURCES_PER_STATEMENT)) {
712
717
  const target = kit.node('resource', `aws/${r}`, { trustZone: 'internal', attrs: r === '*' ? { wildcard: true } : {} });
713
- const attrs = { actions: actions.join(',').slice(0, 250) };
714
- if (conditioned) attrs.conditioned = true;
718
+ // threat-model FR-03.1: the action list (and whether it was clipped) and the condition, in a form the path search can decide
719
+ const attrs = (0,iam_eval/* statementAttrs */.c2)(actions, conditioned ? st.Condition : null);
715
720
  kit.edge('grants', identity, target, {
716
721
  effect, confidence: conditioned ? 'low' : 'high', discriminator: `${actions.join(',')}${conditioned ? ';cond' : ''}`.slice(0, 250), attrs,
717
722
  });
@@ -1638,8 +1643,8 @@ function applyCorrelation(graph, correlation) {
1638
1643
  return (0,boundary_graph/* buildBoundaryGraph */.v4)({ repository: graph.repository, revision: graph.revision, nodes, edges, gaps: graph.gaps, sources: graph.sources });
1639
1644
  }
1640
1645
 
1641
- // EXTERNAL MODULE: ./src/lineage/deployment/projection.js + 1 modules
1642
- var projection = __webpack_require__(73596);
1646
+ // EXTERNAL MODULE: ./src/lineage/deployment/projection.js + 2 modules
1647
+ var projection = __webpack_require__(83929);
1643
1648
  ;// CONCATENATED MODULE: ./src/lineage/deployment/boundaries-run.js
1644
1649
  // boundaries-run.js: the operator entry point that builds a boundary context for scanned findings (X-308 wiring).
1645
1650
  //
@@ -1741,7 +1746,7 @@ function readScanFindings(file, { maxBytes, verify } = {}) {
1741
1746
  * @param {string} [o.now] ISO time used to judge trace staleness; defaults to the current time
1742
1747
  * @returns {object} typed result; `status` is ok | disabled | blocked | unsupported | error. Never throws.
1743
1748
  */
1744
- function runBoundaries({ config, from, environment = 'prod', repository = null, revision = null, findingsFile = null, findings: suppliedFindings = null, verifySignature, now } = {}) {
1749
+ function runBoundaries({ config, from, environment = 'prod', repository = null, revision = null, findingsFile = null, findings: suppliedFindings = null, verifySignature, now, action = null } = {}) {
1745
1750
  const gate = (0,assurance_config/* featureStatus */.FX)(config, DEPLOYMENT_FEATURE);
1746
1751
  if (gate.status !== 'ok') return { ...gate, report: null };
1747
1752
  const limits = (0,assurance_config/* limitValues */.OH)(config);
@@ -1818,7 +1823,7 @@ function runBoundaries({ config, from, environment = 'prod', repository = null,
1818
1823
  if (integrity === 'unsigned') notes.push('the scan result carries no signature, so its integrity was not verified');
1819
1824
  }
1820
1825
 
1821
- const attached = (0,projection/* attachBoundaryContext */.Nu)({ config, findings, graph, bindings, now: clock });
1826
+ const attached = (0,projection/* attachBoundaryContext */.Nu)({ config, findings, graph, bindings, now: clock, action });
1822
1827
  if (attached.status !== 'ok') return { ...attached, report: null };
1823
1828
  const contexts = attached.findings.map((f) => f.boundaryContext).filter(Boolean);
1824
1829
  const rows = attached.findings.map((f) => ({