@clear-capabilities/agentic-security-scanner 0.161.0 → 0.163.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.
Files changed (182) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/bin/agentic-security.js +98 -2
  3. package/dist/1340.index.js +1871 -0
  4. package/dist/3477.index.js +727 -0
  5. package/dist/3499.index.js +23 -3
  6. package/dist/4113.index.js +59 -8
  7. package/dist/5014.index.js +833 -0
  8. package/dist/5756.index.js +2 -2
  9. package/dist/6257.index.js +1 -1
  10. package/dist/6907.index.js +248 -0
  11. package/dist/6994.index.js +1 -1
  12. package/dist/7033.index.js +33 -5
  13. package/dist/7439.index.js +1822 -0
  14. package/dist/7838.index.js +38 -2
  15. package/dist/8798.index.js +1 -1
  16. package/dist/9207.index.js +5 -0
  17. package/dist/9560.index.js +2336 -27
  18. package/dist/957.index.js +1 -1
  19. package/dist/{1905.index.js → 9999.index.js} +306 -13
  20. package/dist/agentic-security.mjs +12 -12
  21. package/dist/agentic-security.mjs.sha256 +1 -1
  22. package/package.json +39 -7
  23. package/src/capabilities/CLAUDE.md +114 -0
  24. package/src/capabilities/address.js +73 -0
  25. package/src/capabilities/attack-coverage.js +93 -0
  26. package/src/capabilities/decide.js +256 -0
  27. package/src/capabilities/delegate.js +69 -0
  28. package/src/capabilities/egress.js +20 -0
  29. package/src/capabilities/hook-advice.js +50 -0
  30. package/src/capabilities/index.js +32 -0
  31. package/src/capabilities/manifest.js +344 -0
  32. package/src/capabilities/outbound.js +189 -0
  33. package/src/capabilities/paths.js +57 -0
  34. package/src/capabilities/probes.js +211 -0
  35. package/src/capabilities/proxy.js +182 -0
  36. package/src/capabilities/reasons.js +67 -0
  37. package/src/capabilities/receipts.js +356 -0
  38. package/src/capabilities/records.js +58 -0
  39. package/src/capabilities/recovery.js +367 -0
  40. package/src/capabilities/report.js +81 -0
  41. package/src/capabilities/runner.js +236 -0
  42. package/src/capabilities/secrets.js +26 -0
  43. package/src/capabilities/tool-gate.js +119 -0
  44. package/src/capabilities/tool-registry.js +60 -0
  45. package/src/dataflow/CLAUDE.md +29 -0
  46. package/src/dataflow/catalog.js +21 -0
  47. package/src/dataflow/engine.js +20 -0
  48. package/src/dataflow/guard-dominance.js +352 -0
  49. package/src/discovery/CLAUDE.md +18 -0
  50. package/src/discovery/index.js +43 -1
  51. package/src/discovery/lenses.js +18 -0
  52. package/src/discovery/memory.js +7 -5
  53. package/src/engine.js +80 -15
  54. package/src/fix/apply-fix-service.js +33 -3
  55. package/src/integrations/index.js +5 -2
  56. package/src/ir/CLAUDE.md +14 -0
  57. package/src/ir/implicit-handler-params.js +74 -0
  58. package/src/ir/parser-cs.js +20 -0
  59. package/src/ir/parser-go.js +30 -13
  60. package/src/ir/parser-java.js +11 -0
  61. package/src/language/nix-eval-isolation.js +33 -5
  62. package/src/lineage/CLAUDE.md +31 -0
  63. package/src/lineage/deployment/adapter-cloud.js +202 -0
  64. package/src/lineage/deployment/adapter-compose.js +90 -0
  65. package/src/lineage/deployment/adapter-kit.js +142 -0
  66. package/src/lineage/deployment/adapter-kubernetes.js +247 -0
  67. package/src/lineage/deployment/attack-paths.js +443 -0
  68. package/src/lineage/deployment/boundaries-run.js +223 -0
  69. package/src/lineage/deployment/boundary-graph.js +567 -0
  70. package/src/lineage/deployment/cross-repo.js +253 -0
  71. package/src/lineage/deployment/drift.js +383 -0
  72. package/src/lineage/deployment/ingest.js +221 -0
  73. package/src/lineage/deployment/projection.js +244 -0
  74. package/src/lineage/deployment/trace-correlation.js +541 -0
  75. package/src/lineage/source-registry.js +19 -0
  76. package/src/mcp/CLAUDE.md +14 -3
  77. package/src/mcp/invariant-tools.js +64 -0
  78. package/src/mcp/portfolio-tools.js +79 -0
  79. package/src/mcp/redact.js +15 -0
  80. package/src/mcp/server.js +24 -3
  81. package/src/mcp/tools.js +30 -5
  82. package/src/posture/CLAUDE.md +505 -0
  83. package/src/posture/accuracy-scorecard.js +95 -4
  84. package/src/posture/assurance/baseline-inventory.js +218 -0
  85. package/src/posture/assurance/baseline.js +322 -0
  86. package/src/posture/assurance/bounded-io.js +145 -0
  87. package/src/posture/assurance/config.js +299 -0
  88. package/src/posture/assurance/contracts.js +315 -0
  89. package/src/posture/assurance/identity.js +62 -0
  90. package/src/posture/assurance/migrations.js +259 -0
  91. package/src/posture/assurance/rollback.js +214 -0
  92. package/src/posture/assurance/rollout.js +200 -0
  93. package/src/posture/assurance/schema-kit.js +111 -0
  94. package/src/posture/assurance/verification-record.js +258 -0
  95. package/src/posture/attestation.js +5 -0
  96. package/src/posture/autopilot.js +28 -1
  97. package/src/posture/business-logic.js +26 -4
  98. package/src/posture/evaluation/bakeoff.js +148 -0
  99. package/src/posture/evaluation/custody.js +200 -0
  100. package/src/posture/evaluation/deployment-ablation.js +416 -0
  101. package/src/posture/evaluation/dev-cases.js +65 -0
  102. package/src/posture/evaluation/economics.js +85 -0
  103. package/src/posture/evaluation/gates.js +161 -0
  104. package/src/posture/evaluation/grouping.js +101 -0
  105. package/src/posture/evaluation/interval.js +132 -0
  106. package/src/posture/evaluation/invariant-ablation.js +325 -0
  107. package/src/posture/evaluation/labels.js +200 -0
  108. package/src/posture/evaluation/protocol.js +308 -0
  109. package/src/posture/evaluation/release-gate.js +209 -0
  110. package/src/posture/evaluation/report.js +158 -0
  111. package/src/posture/evaluation/runner.js +273 -0
  112. package/src/posture/evaluation/scan-child.js +80 -0
  113. package/src/posture/evaluation/score.js +174 -0
  114. package/src/posture/evaluation/synthetic.js +90 -0
  115. package/src/posture/evaluation/why-missed.js +219 -0
  116. package/src/posture/evidence-bundle.js +93 -7
  117. package/src/posture/execution-proof.js +11 -1
  118. package/src/posture/fix-verify-loop.js +20 -6
  119. package/src/posture/fix-verify.js +37 -2
  120. package/src/posture/fleet.js +11 -2
  121. package/src/posture/invariants/cli.js +95 -0
  122. package/src/posture/invariants/coverage.js +164 -0
  123. package/src/posture/invariants/export.js +178 -0
  124. package/src/posture/invariants/expressions.js +46 -0
  125. package/src/posture/invariants/lifecycle.js +289 -0
  126. package/src/posture/invariants/project-input.js +55 -0
  127. package/src/posture/invariants/repair.js +340 -0
  128. package/src/posture/invariants/run.js +45 -0
  129. package/src/posture/invariants/scenarios.js +311 -0
  130. package/src/posture/invariants/schema.js +167 -0
  131. package/src/posture/invariants/shrink.js +407 -0
  132. package/src/posture/invariants/state-assertions.js +165 -0
  133. package/src/posture/logic-claims.js +12 -0
  134. package/src/posture/model-routing.js +40 -0
  135. package/src/posture/oracles/adapters.js +531 -0
  136. package/src/posture/oracles/conformance.js +276 -0
  137. package/src/posture/oracles/oracle.js +453 -0
  138. package/src/posture/oracles/registry.js +76 -0
  139. package/src/posture/oracles/scenario-classes.js +223 -0
  140. package/src/posture/portfolio/backend.js +279 -0
  141. package/src/posture/portfolio/bundle.js +314 -0
  142. package/src/posture/portfolio/cli.js +142 -0
  143. package/src/posture/portfolio/manifest.js +263 -0
  144. package/src/posture/portfolio/progress.js +267 -0
  145. package/src/posture/portfolio/resume.js +130 -0
  146. package/src/posture/portfolio/retention.js +207 -0
  147. package/src/posture/portfolio/scheduler.js +488 -0
  148. package/src/posture/portfolio/signing.js +155 -0
  149. package/src/posture/portfolio/wording.js +59 -0
  150. package/src/posture/portfolio/work-units.js +405 -0
  151. package/src/posture/replay/replay.js +250 -0
  152. package/src/posture/routing/adapter.js +140 -0
  153. package/src/posture/routing/calibration.js +278 -0
  154. package/src/posture/routing/canary.js +141 -0
  155. package/src/posture/routing/control.js +35 -0
  156. package/src/posture/routing/decide.js +211 -0
  157. package/src/posture/routing/drift.js +137 -0
  158. package/src/posture/routing/economics.js +185 -0
  159. package/src/posture/routing/feedback.js +207 -0
  160. package/src/posture/routing/index.js +31 -0
  161. package/src/posture/routing/outcomes.js +281 -0
  162. package/src/posture/routing/promotion.js +291 -0
  163. package/src/posture/routing/receipts.js +59 -0
  164. package/src/posture/routing/report.js +142 -0
  165. package/src/posture/routing/shadow.js +53 -0
  166. package/src/posture/specification-mining.js +26 -0
  167. package/src/posture/verification/advisory-state.js +71 -0
  168. package/src/posture/verification/emit.js +257 -0
  169. package/src/posture/verification/hypothesis-promotion.js +211 -0
  170. package/src/posture/verification/patch-negative.js +226 -0
  171. package/src/posture/verification/patch-promotion.js +113 -0
  172. package/src/posture/verification/projection.js +183 -0
  173. package/src/posture/verification/repair-records.js +124 -0
  174. package/src/posture/verifier.js +16 -0
  175. package/src/report/index.js +100 -1
  176. package/src/sandbox/CLAUDE.md +51 -0
  177. package/src/sandbox/backend-namespace.js +7 -0
  178. package/src/sandbox/backend-userspace.js +151 -34
  179. package/src/sandbox/control-probes.js +215 -0
  180. package/src/sandbox/supervise.js +201 -0
  181. package/src/sandbox/trust-boundary.js +113 -0
  182. package/src/sandbox/trust-domains.js +171 -0
package/dist/957.index.js CHANGED
@@ -10,7 +10,7 @@ export const modules = {
10
10
  /* harmony export */ proposeOllamaExplanation: () => (/* binding */ proposeOllamaExplanation)
11
11
  /* harmony export */ });
12
12
  /* unused harmony export buildExplainPrompt */
13
- /* harmony import */ var _egress_redact_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(74831);
13
+ /* harmony import */ var _egress_redact_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(11723);
14
14
  /* harmony import */ var _egress_policy_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(45712);
15
15
  /* harmony import */ var _providers_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(38947);
16
16
  /* harmony import */ var _ollama_provider_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(23837);
@@ -1,8 +1,8 @@
1
- export const id = 1905;
2
- export const ids = [1905,3499];
1
+ export const id = 9999;
2
+ export const ids = [9999,3499];
3
3
  export const modules = {
4
4
 
5
- /***/ 11905:
5
+ /***/ 49999:
6
6
  /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
7
 
8
8
 
@@ -503,7 +503,7 @@ async function runHunter(focusArea, lens, ctx = {}, opts = {}) {
503
503
 
504
504
  let prompt;
505
505
  try {
506
- prompt = (0,discovery_lenses/* buildHunterPrompt */.j)(focusArea, lens, ctx);
506
+ prompt = (0,discovery_lenses/* buildHunterPrompt */.jM)(focusArea, lens, ctx);
507
507
  } catch (err) {
508
508
  const reason = `failed to build hunter prompt: ${err?.message || String(err)}`;
509
509
  appendEntry(transcript, { phase: 'prompt_error', reason });
@@ -788,6 +788,8 @@ var external_node_fs_ = __webpack_require__(73024);
788
788
  var external_node_path_ = __webpack_require__(76760);
789
789
  // EXTERNAL MODULE: ./src/posture/state-dir.js
790
790
  var state_dir = __webpack_require__(31174);
791
+ // EXTERNAL MODULE: ./src/posture/verification/advisory-state.js
792
+ var advisory_state = __webpack_require__(73826);
791
793
  ;// CONCATENATED MODULE: ./src/discovery/memory.js
792
794
  // Cross-run discovery memory — PRD Phase 3 / C4.
793
795
  //
@@ -831,6 +833,7 @@ var state_dir = __webpack_require__(31174);
831
833
 
832
834
 
833
835
 
836
+
834
837
  const MEMORY_SCHEMA = 'agentic-security/discovery-memory@1';
835
838
  const MEMORY_FILE = external_node_path_.join('.agentic-security', 'discovery-memory.json');
836
839
 
@@ -864,14 +867,15 @@ function loadMemory(scanRoot) {
864
867
  }
865
868
  }
866
869
 
867
- /** Persist. Failure is non-fatal — the run still produced its report. */
870
+ /**
871
+ * Persist. Failure is non-fatal -- the run still produced its report. X-207: the write goes through the advisory-state writer
872
+ * (closed file-name allowlist, atomic rename, never through a link), so hunt output cannot reach `last-scan.json` or its
873
+ * signature even when a hostile tree planted a link at the memory file.
874
+ */
868
875
  function saveMemory(scanRoot, memory) {
869
876
  try {
870
- const p = external_node_path_.join(scanRoot, MEMORY_FILE);
871
877
  if (!(0,state_dir.stateWritesEnabled)()) return;
872
- external_node_fs_.mkdirSync(external_node_path_.dirname(p), { recursive: true });
873
- external_node_fs_.writeFileSync(p, JSON.stringify(memory, null, 2) + '\n');
874
- return true;
878
+ return (0,advisory_state/* writeAdvisoryState */.iS)(scanRoot, external_node_path_.basename(MEMORY_FILE), JSON.stringify(memory, null, 2) + '\n').ok;
875
879
  } catch {
876
880
  return false;
877
881
  }
@@ -953,6 +957,233 @@ function forgetRefuted(memory) {
953
957
  return { ...emptyMemory(), ...memory, candidates };
954
958
  }
955
959
 
960
+ // EXTERNAL MODULE: ./src/posture/verification/emit.js
961
+ var emit = __webpack_require__(85578);
962
+ // EXTERNAL MODULE: ./src/posture/oracles/oracle.js + 1 modules
963
+ var oracle = __webpack_require__(14642);
964
+ // EXTERNAL MODULE: ./src/posture/assurance/verification-record.js
965
+ var verification_record = __webpack_require__(29668);
966
+ // EXTERNAL MODULE: ./src/posture/assurance/identity.js
967
+ var identity = __webpack_require__(41877);
968
+ // EXTERNAL MODULE: ./src/posture/assurance/schema-kit.js
969
+ var schema_kit = __webpack_require__(53353);
970
+ // EXTERNAL MODULE: ./src/posture/verification-separation.js
971
+ var verification_separation = __webpack_require__(3107);
972
+ ;// CONCATENATED MODULE: ./src/posture/verification/hypothesis-promotion.js
973
+ // Promoting an advisory hypothesis into a finding (X-207.AC02, AC03).
974
+ //
975
+ // A hunt hypothesis is advisory. It becomes a FINDING only through `promoteHypothesis`, which needs all three of:
976
+ //
977
+ // 1. a trusted verifier receipt, VERIFIED here (not merely present): issued by `runOracle` in this process
978
+ // (`isIssuedReceipt`: a copy, a JSON round trip or a hand-built object is not), bound to this hypothesis and one exact
979
+ // commit, carrying a valid version-1 record whose id is the receipt's `recordId`, settled `confirmed` at the
980
+ // `runtime-confirmed` level with trusted-runtime-proof evidence from the trusted runner;
981
+ // 2. an explicit policy evaluation. The caller passes a policy; the result carries every rule that was evaluated with its
982
+ // outcome and a digest of the policy. The mandatory rules cannot be removed by a policy, and a policy key this module does
983
+ // not know is refused (fail closed);
984
+ // 3. an audit record, returned for EVERY decision (promoted or not), that links the source hypothesis to the promoted finding
985
+ // (or to none) and names the receipt, the record and the policy evaluation. It carries no clock, so identical inputs give
986
+ // an identical record.
987
+ //
988
+ // What can never satisfy the policy: a confidence score, a repeated or unanimous model agreement, a refutation panel's votes,
989
+ // a taint-probe tier, or a severity. None of them is an input to any rule except the optional `minConfidence` / `minAgreement`
990
+ // rules, which can only ADD a requirement: the receipt rules are always evaluated, so a hypothesis with perfect confidence and
991
+ // no receipt is rejected, and a policy made only of those rules still rejects it.
992
+ //
993
+ // The producer of a hunt hypothesis and the verifier that confirms it must be different parties (`verification-separation.js`
994
+ // is reused for that check). The audit record is returned, not persisted: persisting an audit trail needs an artifact-registry
995
+ // classification and a signing domain, which are not decided here.
996
+
997
+
998
+
999
+
1000
+
1001
+
1002
+
1003
+ const AUDIT_SCHEMA = 'agentic-security/promotion-audit';
1004
+ const AUDIT_VERSION = '1.0.0';
1005
+ const POLICY_SCHEMA = 'agentic-security/promotion-policy';
1006
+
1007
+ const DECISIONS = Object.freeze(['promoted', 'rejected', 'unsupported', 'inconclusive']);
1008
+ const SEVERITY_ORDER = ['info', 'low', 'medium', 'high', 'critical'];
1009
+
1010
+ /** The default policy: runtime-confirmed by a class oracle other than the functional-regression one, severity capped at high. */
1011
+ const DEFAULT_PROMOTION_POLICY = Object.freeze({
1012
+ schema: POLICY_SCHEMA, id: 'default', version: '1',
1013
+ allowedOracleClasses: Object.freeze(oracle/* ORACLE_CLASSES */.aI.filter((c) => c !== 'functional-regression')),
1014
+ maxSeverity: 'high',
1015
+ });
1016
+
1017
+ const POLICY_KEYS = ['schema', 'id', 'version', 'allowedOracleClasses', 'maxSeverity', 'expectedCommit', 'minConfidence', 'minAgreement'];
1018
+
1019
+ const PROMOTED = new WeakSet();
1020
+ /** True only for a finding `promoteHypothesis` itself returned. */
1021
+ function isPromotedFinding(f) { return isPlainObject(f) && PROMOTED.has(f); }
1022
+
1023
+ function validatePolicy(policy) {
1024
+ if (!(0,schema_kit/* isPlainObject */.Qd)(policy)) return ['no policy was supplied: promotion needs an explicit policy evaluation'];
1025
+ const errs = [];
1026
+ for (const k of Object.keys(policy)) if (!POLICY_KEYS.includes(k)) errs.push(`unknown policy key '${k}'`);
1027
+ if (policy.schema !== POLICY_SCHEMA) errs.push(`policy.schema must be '${POLICY_SCHEMA}'`);
1028
+ if (typeof policy.id !== 'string' || !policy.id) errs.push('policy.id is required');
1029
+ if (typeof policy.version !== 'string' || !policy.version) errs.push('policy.version is required');
1030
+ if (!Array.isArray(policy.allowedOracleClasses) || policy.allowedOracleClasses.length === 0 || !policy.allowedOracleClasses.every((c) => oracle/* ORACLE_CLASSES */.aI.includes(c))) errs.push('policy.allowedOracleClasses must list known oracle classes');
1031
+ if (!SEVERITY_ORDER.includes(policy.maxSeverity)) errs.push('policy.maxSeverity must be a severity');
1032
+ if (policy.expectedCommit !== undefined && !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(String(policy.expectedCommit))) errs.push('policy.expectedCommit must be an exact commit');
1033
+ for (const k of ['minConfidence', 'minAgreement']) if (policy[k] !== undefined && !(typeof policy[k] === 'number' && policy[k] >= 0 && policy[k] <= 1)) errs.push(`policy.${k} must be a number between 0 and 1`);
1034
+ return errs;
1035
+ }
1036
+
1037
+ function agreementOf(h) {
1038
+ const votes = h?.discovery?.refutation?.votes;
1039
+ if (!Array.isArray(votes) || votes.length === 0) return null;
1040
+ const upheld = votes.filter((v) => v && (v.verdict === 'upheld' || v.upheld === true)).length;
1041
+ return upheld / votes.length;
1042
+ }
1043
+
1044
+ /**
1045
+ * Evaluate the policy for one hypothesis and one oracle result. Every rule is evaluated and listed.
1046
+ * @returns {{ ok: boolean, rules: {id: string, pass: boolean, detail: string}[], status: string, policyDigest: string|null, record: object|null }}
1047
+ */
1048
+ function evaluatePromotionPolicy({ hypothesis, result, policy } = {}) {
1049
+ const rules = [];
1050
+ const rule = (id, pass, detail) => rules.push({ id, pass: !!pass, detail });
1051
+ const bad = validatePolicy(policy);
1052
+ if (bad.length) {
1053
+ rule('policy-valid', false, bad.join('; '));
1054
+ return { ok: false, rules, status: 'rejected', policyDigest: null, record: null };
1055
+ }
1056
+ rule('policy-valid', true, `policy '${policy.id}' v${policy.version}`);
1057
+ const policyDigest = (0,identity/* digestOf */.ol)(policy);
1058
+
1059
+ const receipt = result?.receipt ?? null;
1060
+ const record = result?.record ?? null;
1061
+ const hypothesisId = (0,schema_kit/* isPlainObject */.Qd)(hypothesis) ? (0,identity/* hypothesisIdFromFinding */.XC)(hypothesis) : null;
1062
+
1063
+ rule('hypothesis-advisory', (0,advisory_state/* isAdvisoryHypothesis */.Si)(hypothesis) && !!hypothesisId, 'the source must be an unpromoted hunt hypothesis with a stable id');
1064
+ const issued = (0,oracle/* isIssuedReceipt */.AH)(receipt) && receipt.issuedBy === 'verifier' && receipt.schema === 'agentic-security/oracle-receipt';
1065
+ rule('receipt-issued', issued, issued ? 'the receipt was issued by the verifier domain in this process' : 'no receipt issued by the verifier domain was supplied (a copy, a parsed file or a constructed object is not valid)');
1066
+
1067
+ const recordOk = (0,schema_kit/* isPlainObject */.Qd)(record) && (0,verification_record/* validateVerificationRecord */.LL)(record).ok;
1068
+ rule('record-valid', recordOk, recordOk ? 'the verification record validates' : 'the verification record is missing or invalid');
1069
+ const bound = issued && recordOk && receipt.recordId === record.id && receipt.request.hypothesisId === hypothesisId && record.hypothesisId === hypothesisId;
1070
+ rule('receipt-binds-hypothesis', bound, bound ? 'the receipt, the record and the hypothesis name one another' : 'the receipt or record is for a different hypothesis, or they do not reference each other');
1071
+ const commit = issued ? receipt.request.commit : null;
1072
+ rule('commit-bound', !!commit && (!policy.expectedCommit || policy.expectedCommit === commit) && recordOk && record.commit === commit,
1073
+ commit ? (policy.expectedCommit && policy.expectedCommit !== commit ? `the receipt is for a different commit than the policy expects` : 'the receipt is bound to one exact commit') : 'the receipt is not bound to an exact commit');
1074
+
1075
+ const outcome = bound ? receipt.settled.outcome : null;
1076
+ rule('outcome-confirmed', outcome === 'confirmed', `the verifier settled '${outcome ?? 'nothing'}'`);
1077
+ const runtime = bound && record.confirmationLevel === 'runtime-confirmed'
1078
+ && record.evidence.some((e) => e.kind === 'trusted-runtime-proof' && e.producer === 'trusted-runner' && record.evidenceRefs.includes(e.id));
1079
+ rule('runtime-confirmed', runtime, runtime ? 'trusted runtime proof from the trusted runner is referenced' : 'no referenced trusted runtime proof at the runtime-confirmed level');
1080
+ const classOk = bound && policy.allowedOracleClasses.includes(receipt.oracle.class);
1081
+ rule('class-allowed', classOk, classOk ? `oracle class '${receipt.oracle.class}' is allowed` : 'the oracle class is not allowed by the policy');
1082
+
1083
+ // producer and verifier must be different parties
1084
+ const probe = { ...hypothesis };
1085
+ let sep = { ok: false, reason: 'no receipt' };
1086
+ if (bound) {
1087
+ const stamped = (0,verification_separation/* recordProducer */.Ms)(probe, `hunter:${hypothesis?.discovery?.lens ?? 'unknown'}`);
1088
+ sep = stamped.ok ? (0,verification_separation/* assertSeparation */.jm)(probe, `verifier:oracle:${receipt.oracle.id}`) : stamped;
1089
+ }
1090
+ rule('producer-verifier-separation', sep.ok, sep.ok ? 'the hunter and the verifying oracle are different parties' : String(sep.reason));
1091
+
1092
+ // additive-only rules: they can add a requirement, never satisfy one
1093
+ if (policy.minConfidence !== undefined) {
1094
+ const c = typeof hypothesis?.confidence === 'number' ? hypothesis.confidence : null;
1095
+ rule('min-confidence', c !== null && c >= policy.minConfidence, `an additional requirement only: confidence ${c ?? 'absent'} against ${policy.minConfidence}`);
1096
+ }
1097
+ if (policy.minAgreement !== undefined) {
1098
+ const a = agreementOf(hypothesis);
1099
+ rule('min-agreement', a !== null && a >= policy.minAgreement, `an additional requirement only: panel agreement ${a ?? 'absent'} against ${policy.minAgreement}`);
1100
+ }
1101
+
1102
+ const ok = rules.every((r) => r.pass);
1103
+ let status = 'promoted';
1104
+ if (!ok) {
1105
+ const settled = bound ? outcome : ((0,schema_kit/* isPlainObject */.Qd)(record) && typeof record.outcome === 'string' ? record.outcome : null);
1106
+ if (settled === 'unsupported') status = 'unsupported';
1107
+ else if (['inconclusive', 'not-run', 'error'].includes(settled)) status = 'inconclusive';
1108
+ else status = 'rejected';
1109
+ }
1110
+ return { ok, rules, status, policyDigest, record: recordOk ? record : null };
1111
+ }
1112
+
1113
+ const capSeverity = (sev, max) => (SEVERITY_ORDER.indexOf(sev) > SEVERITY_ORDER.indexOf(max) ? max : (SEVERITY_ORDER.includes(sev) ? sev : max));
1114
+
1115
+ /**
1116
+ * Promote one hypothesis, or decline to. Never throws.
1117
+ *
1118
+ * @param {object} args
1119
+ * @param {object} args.hypothesis a hunt finding (`discovery/judge.js` shape)
1120
+ * @param {object} args.result the `runOracle` result for it (`{ record, receipt, outcome }`)
1121
+ * @param {object} args.policy an explicit policy (see DEFAULT_PROMOTION_POLICY)
1122
+ * @returns {{ ok: boolean, decision: string, finding: object|null, audit: object, evaluation: object }}
1123
+ */
1124
+ function promoteHypothesis({ hypothesis, result, policy } = {}) {
1125
+ let evaluation;
1126
+ try { evaluation = evaluatePromotionPolicy({ hypothesis, result, policy }); }
1127
+ catch (e) { evaluation = { ok: false, status: 'rejected', policyDigest: null, record: null, rules: [{ id: 'evaluation', pass: false, detail: `the policy evaluation failed: ${String(e?.message || e).slice(0, 120)}` }] }; }
1128
+ const hypothesisId = (0,schema_kit/* isPlainObject */.Qd)(hypothesis) ? (0,identity/* hypothesisIdFromFinding */.XC)(hypothesis) : null;
1129
+ const decision = evaluation.ok ? 'promoted' : evaluation.status;
1130
+
1131
+ let finding = null;
1132
+ if (evaluation.ok) {
1133
+ const h = hypothesis;
1134
+ const base = {
1135
+ id: `promoted-${hypothesisId}`, stableId: hypothesisId,
1136
+ severity: capSeverity(h.severity, policy.maxSeverity),
1137
+ file: h.file, line: h.line, vuln: h.vuln, cwe: h.cwe, description: h.description, remediation: h.remediation,
1138
+ // A promoted finding is no longer a hunt hypothesis: it carries neither the hunt parser nor the `discovery` stamp, so the
1139
+ // advisory exclusion does not apply to it, and its origin lives in `promotedFrom`.
1140
+ parser: 'PROMOTED-HYPOTHESIS', family: h.family ?? 'other', ruleId: h.ruleId ?? 'promoted-hypothesis', snippet: h.snippet ?? '',
1141
+ };
1142
+ finding = { ...base, verificationRecord: JSON.parse(JSON.stringify(evaluation.record)) };
1143
+ }
1144
+
1145
+ const audit = {
1146
+ schema: AUDIT_SCHEMA, schemaVersion: AUDIT_VERSION, decision,
1147
+ sourceHypothesis: {
1148
+ id: hypothesisId, digest: (0,identity/* digestOf */.ol)((0,schema_kit/* isPlainObject */.Qd)(hypothesis) ? hypothesis : null),
1149
+ origin: { lens: hypothesis?.discovery?.lens ?? null, parser: hypothesis?.parser ?? null, family: hypothesis?.family ?? null },
1150
+ },
1151
+ promotedFinding: finding ? { stableId: finding.stableId, id: finding.id } : null,
1152
+ verification: {
1153
+ recordId: evaluation.record?.id ?? null, receiptDigest: (0,oracle/* isIssuedReceipt */.AH)(result?.receipt) ? result.receipt.receiptDigest : null,
1154
+ oracle: evaluation.record?.oracle ?? null, outcome: evaluation.record?.outcome ?? null,
1155
+ confirmationLevel: evaluation.record?.confirmationLevel ?? null, commit: evaluation.record?.commit ?? null,
1156
+ },
1157
+ policy: { id: (0,schema_kit/* isPlainObject */.Qd)(policy) ? policy.id ?? null : null, version: (0,schema_kit/* isPlainObject */.Qd)(policy) ? policy.version ?? null : null, digest: evaluation.policyDigest, rules: evaluation.rules },
1158
+ };
1159
+ audit.id = (0,identity/* semanticId */.YN)('padt', audit, ['decision', 'sourceHypothesis', 'promotedFinding', 'verification', 'policy']);
1160
+ deepFreeze(audit);
1161
+ if (finding) {
1162
+ finding.promotedFrom = { hypothesisId, auditId: audit.id, lens: hypothesis?.discovery?.lens ?? null };
1163
+ deepFreeze(finding);
1164
+ PROMOTED.add(finding);
1165
+ }
1166
+ return { ok: !!finding, decision, finding, audit, evaluation };
1167
+ }
1168
+
1169
+ /** Do an audit record, the hypothesis it names and the promoted finding it names agree? (The link, checked from both ends.) */
1170
+ function auditLinksHold({ audit, hypothesis, finding }) {
1171
+ if (!isPlainObject(audit) || audit.schema !== AUDIT_SCHEMA) return false;
1172
+ const hid = isPlainObject(hypothesis) ? hypothesisIdFromFinding(hypothesis) : null;
1173
+ if (!hid || audit.sourceHypothesis?.id !== hid || audit.sourceHypothesis?.digest !== digestOf(hypothesis)) return false;
1174
+ if (audit.id !== semanticId('padt', audit, ['decision', 'sourceHypothesis', 'promotedFinding', 'verification', 'policy'])) return false;
1175
+ if (audit.decision !== 'promoted') return finding == null && audit.promotedFinding === null;
1176
+ return isPromotedFinding(finding) && finding.promotedFrom?.auditId === audit.id && finding.promotedFrom?.hypothesisId === hid
1177
+ && audit.promotedFinding?.stableId === finding.stableId && finding.verificationRecord?.id === audit.verification.recordId;
1178
+ }
1179
+
1180
+ function deepFreeze(o) {
1181
+ for (const v of Object.values(o)) if (v && typeof v === 'object' && !Object.isFrozen(v)) deepFreeze(v);
1182
+ return Object.freeze(o);
1183
+ }
1184
+
1185
+ // EXTERNAL MODULE: ./src/posture/assurance/config.js
1186
+ var config = __webpack_require__(90385);
956
1187
  ;// CONCATENATED MODULE: ./src/discovery/index.js
957
1188
  //
958
1189
  // Compose the discovery pipeline:
@@ -971,6 +1202,9 @@ function forgetRefuted(memory) {
971
1202
 
972
1203
 
973
1204
 
1205
+
1206
+
1207
+
974
1208
  // Bridge a candidate to the deterministic layer. A taint finding at or within
975
1209
  // two lines of the candidate corroborates it; a modelled sink on the line
976
1210
  // without a full path is weaker corroboration ("sink-adjacent").
@@ -1113,7 +1347,7 @@ async function runDiscovery(ctx = {}, opts = {}) {
1113
1347
 
1114
1348
  const lenses = [];
1115
1349
  for (const key of lensKeys) {
1116
- const lens = (0,discovery_lenses/* lensByKey */.H)(key);
1350
+ const lens = (0,discovery_lenses/* lensByKey */.Hy)(key);
1117
1351
  // An unknown key must degrade visibly, not vanish via a silent filter.
1118
1352
  if (lens) lenses.push(lens);
1119
1353
  else reasons.push(`unresolved lens key: "${key}"`);
@@ -1239,11 +1473,50 @@ async function runDiscovery(ctx = {}, opts = {}) {
1239
1473
  }));
1240
1474
  }
1241
1475
 
1476
+ // X-201: one version-1 verification record per fresh hypothesis, ADDITIVE and advisory (hunt output never enters
1477
+ // last-scan.json). A taint-corroborated candidate is `inconclusive` (a static probe is corroboration, not
1478
+ // execution) and an uncorroborated one is `not-run`; a model refutation panel is recorded as inference only.
1479
+ const hypothesisCommit = (0,emit.headCommit)(opts.scanRoot);
1480
+ const verificationRecords = [];
1481
+ for (const f of fresh) {
1482
+ const e = (0,emit/* emitVerification */.T)('hunt', f, { finding: f, commit: hypothesisCommit });
1483
+ if (e.ok) verificationRecords.push(e.record);
1484
+ }
1485
+
1486
+ // X-402: opt-in invariant proposals. A candidate from a state-oriented lens can suggest a contract; the suggestion is advisory,
1487
+ // `proposed`, authored by the model and never approved here. Off unless `opts.invariantProposals = { config }` and the
1488
+ // `invariant-scenarios` feature is enabled in that config.
1489
+ let invariantProposals = null;
1490
+ if (opts.invariantProposals && (0,config/* featureStatus */.FX)(opts.invariantProposals.config, 'invariant-scenarios').status === 'ok') {
1491
+ invariantProposals = [];
1492
+ for (const f of fresh) {
1493
+ const inv = (0,discovery_lenses/* invariantProposalFor */.gj)({ lens: f.discovery?.lens, title: f.vuln, description: f.description, file: f.file, line: f.line });
1494
+ if (inv) invariantProposals.push({ hypothesisId: f.stableId, invariant: inv });
1495
+ }
1496
+ }
1497
+
1498
+ // X-207: opt-in promotion. A hypothesis crosses into a finding ONLY through `promoteHypothesis` (trusted verifier receipt,
1499
+ // explicit policy, audit record). `fresh` is unchanged; promoted findings and every decision's audit record are reported
1500
+ // separately, and nothing here writes `last-scan.json`. `opts.promote = { policy, verify(hypothesis) -> runOracle result }`.
1501
+ let promotions = null;
1502
+ if (opts.promote && typeof opts.promote.verify === 'function') {
1503
+ promotions = [];
1504
+ for (const f of fresh) {
1505
+ let result = null;
1506
+ try { result = await opts.promote.verify(f); } catch { result = null; }
1507
+ const p = promoteHypothesis({ hypothesis: f, result, policy: opts.promote.policy });
1508
+ promotions.push({ hypothesisId: p.audit.sourceHypothesis.id, decision: p.decision, audit: p.audit, finding: p.finding });
1509
+ }
1510
+ }
1511
+
1242
1512
  return {
1243
1513
  schema: 'agentic-security/discovery@1',
1244
1514
  focusAreas: areas.map(a => ({ id: a.id, label: a.label, files: a.files.length, size: a.size })),
1245
1515
  runs,
1246
1516
  fresh,
1517
+ verificationRecords,
1518
+ ...(promotions ? { promotions } : {}),
1519
+ ...(invariantProposals ? { invariantProposals } : {}),
1247
1520
  duplicates,
1248
1521
  suppressed,
1249
1522
  // `refutedCandidates` holds RAW candidates straight from `disprovePanel`,
@@ -1302,11 +1575,13 @@ async function runDiscovery(ctx = {}, opts = {}) {
1302
1575
  /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
1303
1576
 
1304
1577
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
1305
- /* harmony export */ H: () => (/* binding */ lensByKey),
1578
+ /* harmony export */ Hy: () => (/* binding */ lensByKey),
1306
1579
  /* harmony export */ LENSES: () => (/* binding */ LENSES),
1307
- /* harmony export */ j: () => (/* binding */ buildHunterPrompt)
1580
+ /* harmony export */ gj: () => (/* binding */ invariantProposalFor),
1581
+ /* harmony export */ jM: () => (/* binding */ buildHunterPrompt)
1308
1582
  /* harmony export */ });
1309
- /* harmony import */ var _egress_redact_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(74831);
1583
+ /* harmony import */ var _egress_redact_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(11723);
1584
+ /* harmony import */ var _posture_invariants_lifecycle_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(80891);
1310
1585
  //
1311
1586
  // The seven hunting lenses. Each hunter run is one (focus area × lens) pair.
1312
1587
  //
@@ -1317,6 +1592,7 @@ async function runDiscovery(ctx = {}, opts = {}) {
1317
1592
  // no single prompt reaches. `wildcard` exists because a fixed taxonomy is a
1318
1593
  // ceiling, and the classes worth finding are the ones not on the list.
1319
1594
 
1595
+
1320
1596
  const LENSES = Object.freeze([
1321
1597
  { key: 'injection', title: 'Injection', family: 'injection', cwe: 'CWE-74',
1322
1598
  brief: 'Untrusted input reaching an interpreter: SQL, shell, template, XPath, LDAP, or deserialization. Follow the value, not the function name.' },
@@ -1334,6 +1610,23 @@ const LENSES = Object.freeze([
1334
1610
  brief: 'Anything the other lenses do not cover. Prefer the surprising and specific over the generic; report nothing rather than something already obvious.' },
1335
1611
  ]);
1336
1612
 
1613
+ // X-402: the lenses that reason about who may do what to whose data can propose an INVARIANT for a candidate they raise. The
1614
+ // proposal is a skeleton contract authored by the model (`proposed`, with the candidate as source evidence and a high uncertainty),
1615
+ // advisory until a reviewer approves it in the signed ledger. A lens whose candidates are not about state (injection, crypto) has
1616
+ // no mapping and proposes nothing.
1617
+ const TENANT_HINT = /tenant|idor|ownership|object reference|cross[- ]?(?:account|user|org)|leak|another user|other user/i;
1618
+ const WORKFLOW_HINT = /refund|order|status|workflow|state machine|out[- ]of[- ]order|step|approval|checkout|paid|ship/i;
1619
+
1620
+ function invariantProposalFor(candidate) {
1621
+ const text = `${candidate?.title || ''} ${candidate?.description || ''} ${candidate?.rationale || ''}`;
1622
+ let cls = null;
1623
+ if (candidate?.lens === 'authz') cls = TENANT_HINT.test(text) ? 'tenant-isolation' : 'privilege-constraint';
1624
+ else if (candidate?.lens === 'feature-abuse' && TENANT_HINT.test(text)) cls = 'tenant-isolation';
1625
+ else if (candidate?.lens === 'business-logic' && WORKFLOW_HINT.test(text)) cls = 'workflow-order';
1626
+ if (!cls || typeof candidate.file !== 'string') return null;
1627
+ return (0,_posture_invariants_lifecycle_js__WEBPACK_IMPORTED_MODULE_1__/* .inferredInvariant */ .mB)({ class: cls, source: 'discovery-lens', evidence: { file: candidate.file, line: Number.isInteger(candidate.line) ? candidate.line : null, lens: candidate.lens }, uncertainty: 0.85 });
1628
+ }
1629
+
1337
1630
  function lensByKey(key) {
1338
1631
  if (typeof key !== 'string') return null;
1339
1632
  return LENSES.find(l => l.key === key) || null;