@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.
- package/CHANGELOG.md +46 -0
- package/bin/agentic-security.js +98 -2
- package/dist/1340.index.js +1871 -0
- package/dist/3477.index.js +727 -0
- package/dist/3499.index.js +23 -3
- package/dist/4113.index.js +59 -8
- package/dist/5014.index.js +833 -0
- package/dist/5756.index.js +2 -2
- package/dist/6257.index.js +1 -1
- package/dist/6907.index.js +248 -0
- package/dist/6994.index.js +1 -1
- package/dist/7033.index.js +33 -5
- package/dist/7439.index.js +1822 -0
- package/dist/7838.index.js +38 -2
- package/dist/8798.index.js +1 -1
- package/dist/9207.index.js +5 -0
- package/dist/9560.index.js +2336 -27
- package/dist/957.index.js +1 -1
- package/dist/{1905.index.js → 9999.index.js} +306 -13
- package/dist/agentic-security.mjs +12 -12
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +39 -7
- package/src/capabilities/CLAUDE.md +114 -0
- package/src/capabilities/address.js +73 -0
- package/src/capabilities/attack-coverage.js +93 -0
- package/src/capabilities/decide.js +256 -0
- package/src/capabilities/delegate.js +69 -0
- package/src/capabilities/egress.js +20 -0
- package/src/capabilities/hook-advice.js +50 -0
- package/src/capabilities/index.js +32 -0
- package/src/capabilities/manifest.js +344 -0
- package/src/capabilities/outbound.js +189 -0
- package/src/capabilities/paths.js +57 -0
- package/src/capabilities/probes.js +211 -0
- package/src/capabilities/proxy.js +182 -0
- package/src/capabilities/reasons.js +67 -0
- package/src/capabilities/receipts.js +356 -0
- package/src/capabilities/records.js +58 -0
- package/src/capabilities/recovery.js +367 -0
- package/src/capabilities/report.js +81 -0
- package/src/capabilities/runner.js +236 -0
- package/src/capabilities/secrets.js +26 -0
- package/src/capabilities/tool-gate.js +119 -0
- package/src/capabilities/tool-registry.js +60 -0
- package/src/dataflow/CLAUDE.md +29 -0
- package/src/dataflow/catalog.js +21 -0
- package/src/dataflow/engine.js +20 -0
- package/src/dataflow/guard-dominance.js +352 -0
- package/src/discovery/CLAUDE.md +18 -0
- package/src/discovery/index.js +43 -1
- package/src/discovery/lenses.js +18 -0
- package/src/discovery/memory.js +7 -5
- package/src/engine.js +80 -15
- package/src/fix/apply-fix-service.js +33 -3
- package/src/integrations/index.js +5 -2
- package/src/ir/CLAUDE.md +14 -0
- package/src/ir/implicit-handler-params.js +74 -0
- package/src/ir/parser-cs.js +20 -0
- package/src/ir/parser-go.js +30 -13
- package/src/ir/parser-java.js +11 -0
- package/src/language/nix-eval-isolation.js +33 -5
- package/src/lineage/CLAUDE.md +31 -0
- package/src/lineage/deployment/adapter-cloud.js +202 -0
- package/src/lineage/deployment/adapter-compose.js +90 -0
- package/src/lineage/deployment/adapter-kit.js +142 -0
- package/src/lineage/deployment/adapter-kubernetes.js +247 -0
- package/src/lineage/deployment/attack-paths.js +443 -0
- package/src/lineage/deployment/boundaries-run.js +223 -0
- package/src/lineage/deployment/boundary-graph.js +567 -0
- package/src/lineage/deployment/cross-repo.js +253 -0
- package/src/lineage/deployment/drift.js +383 -0
- package/src/lineage/deployment/ingest.js +221 -0
- package/src/lineage/deployment/projection.js +244 -0
- package/src/lineage/deployment/trace-correlation.js +541 -0
- package/src/lineage/source-registry.js +19 -0
- package/src/mcp/CLAUDE.md +14 -3
- package/src/mcp/invariant-tools.js +64 -0
- package/src/mcp/portfolio-tools.js +79 -0
- package/src/mcp/redact.js +15 -0
- package/src/mcp/server.js +24 -3
- package/src/mcp/tools.js +30 -5
- package/src/posture/CLAUDE.md +505 -0
- package/src/posture/accuracy-scorecard.js +95 -4
- package/src/posture/assurance/baseline-inventory.js +218 -0
- package/src/posture/assurance/baseline.js +322 -0
- package/src/posture/assurance/bounded-io.js +145 -0
- package/src/posture/assurance/config.js +299 -0
- package/src/posture/assurance/contracts.js +315 -0
- package/src/posture/assurance/identity.js +62 -0
- package/src/posture/assurance/migrations.js +259 -0
- package/src/posture/assurance/rollback.js +214 -0
- package/src/posture/assurance/rollout.js +200 -0
- package/src/posture/assurance/schema-kit.js +111 -0
- package/src/posture/assurance/verification-record.js +258 -0
- package/src/posture/attestation.js +5 -0
- package/src/posture/autopilot.js +28 -1
- package/src/posture/business-logic.js +26 -4
- package/src/posture/evaluation/bakeoff.js +148 -0
- package/src/posture/evaluation/custody.js +200 -0
- package/src/posture/evaluation/deployment-ablation.js +416 -0
- package/src/posture/evaluation/dev-cases.js +65 -0
- package/src/posture/evaluation/economics.js +85 -0
- package/src/posture/evaluation/gates.js +161 -0
- package/src/posture/evaluation/grouping.js +101 -0
- package/src/posture/evaluation/interval.js +132 -0
- package/src/posture/evaluation/invariant-ablation.js +325 -0
- package/src/posture/evaluation/labels.js +200 -0
- package/src/posture/evaluation/protocol.js +308 -0
- package/src/posture/evaluation/release-gate.js +209 -0
- package/src/posture/evaluation/report.js +158 -0
- package/src/posture/evaluation/runner.js +273 -0
- package/src/posture/evaluation/scan-child.js +80 -0
- package/src/posture/evaluation/score.js +174 -0
- package/src/posture/evaluation/synthetic.js +90 -0
- package/src/posture/evaluation/why-missed.js +219 -0
- package/src/posture/evidence-bundle.js +93 -7
- package/src/posture/execution-proof.js +11 -1
- package/src/posture/fix-verify-loop.js +20 -6
- package/src/posture/fix-verify.js +37 -2
- package/src/posture/fleet.js +11 -2
- package/src/posture/invariants/cli.js +95 -0
- package/src/posture/invariants/coverage.js +164 -0
- package/src/posture/invariants/export.js +178 -0
- package/src/posture/invariants/expressions.js +46 -0
- package/src/posture/invariants/lifecycle.js +289 -0
- package/src/posture/invariants/project-input.js +55 -0
- package/src/posture/invariants/repair.js +340 -0
- package/src/posture/invariants/run.js +45 -0
- package/src/posture/invariants/scenarios.js +311 -0
- package/src/posture/invariants/schema.js +167 -0
- package/src/posture/invariants/shrink.js +407 -0
- package/src/posture/invariants/state-assertions.js +165 -0
- package/src/posture/logic-claims.js +12 -0
- package/src/posture/model-routing.js +40 -0
- package/src/posture/oracles/adapters.js +531 -0
- package/src/posture/oracles/conformance.js +276 -0
- package/src/posture/oracles/oracle.js +453 -0
- package/src/posture/oracles/registry.js +76 -0
- package/src/posture/oracles/scenario-classes.js +223 -0
- package/src/posture/portfolio/backend.js +279 -0
- package/src/posture/portfolio/bundle.js +314 -0
- package/src/posture/portfolio/cli.js +142 -0
- package/src/posture/portfolio/manifest.js +263 -0
- package/src/posture/portfolio/progress.js +267 -0
- package/src/posture/portfolio/resume.js +130 -0
- package/src/posture/portfolio/retention.js +207 -0
- package/src/posture/portfolio/scheduler.js +488 -0
- package/src/posture/portfolio/signing.js +155 -0
- package/src/posture/portfolio/wording.js +59 -0
- package/src/posture/portfolio/work-units.js +405 -0
- package/src/posture/replay/replay.js +250 -0
- package/src/posture/routing/adapter.js +140 -0
- package/src/posture/routing/calibration.js +278 -0
- package/src/posture/routing/canary.js +141 -0
- package/src/posture/routing/control.js +35 -0
- package/src/posture/routing/decide.js +211 -0
- package/src/posture/routing/drift.js +137 -0
- package/src/posture/routing/economics.js +185 -0
- package/src/posture/routing/feedback.js +207 -0
- package/src/posture/routing/index.js +31 -0
- package/src/posture/routing/outcomes.js +281 -0
- package/src/posture/routing/promotion.js +291 -0
- package/src/posture/routing/receipts.js +59 -0
- package/src/posture/routing/report.js +142 -0
- package/src/posture/routing/shadow.js +53 -0
- package/src/posture/specification-mining.js +26 -0
- package/src/posture/verification/advisory-state.js +71 -0
- package/src/posture/verification/emit.js +257 -0
- package/src/posture/verification/hypothesis-promotion.js +211 -0
- package/src/posture/verification/patch-negative.js +226 -0
- package/src/posture/verification/patch-promotion.js +113 -0
- package/src/posture/verification/projection.js +183 -0
- package/src/posture/verification/repair-records.js +124 -0
- package/src/posture/verifier.js +16 -0
- package/src/report/index.js +100 -1
- package/src/sandbox/CLAUDE.md +51 -0
- package/src/sandbox/backend-namespace.js +7 -0
- package/src/sandbox/backend-userspace.js +151 -34
- package/src/sandbox/control-probes.js +215 -0
- package/src/sandbox/supervise.js +201 -0
- package/src/sandbox/trust-boundary.js +113 -0
- 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__(
|
|
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 =
|
|
2
|
-
export const ids = [
|
|
1
|
+
export const id = 9999;
|
|
2
|
+
export const ids = [9999,3499];
|
|
3
3
|
export const modules = {
|
|
4
4
|
|
|
5
|
-
/***/
|
|
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 */.
|
|
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
|
-
/**
|
|
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
|
-
|
|
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 */.
|
|
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 */
|
|
1578
|
+
/* harmony export */ Hy: () => (/* binding */ lensByKey),
|
|
1306
1579
|
/* harmony export */ LENSES: () => (/* binding */ LENSES),
|
|
1307
|
-
/* harmony export */
|
|
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__(
|
|
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;
|