@xaccefy/pi-casefile 0.10.1 → 0.11.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/README.md +2 -3
- package/package.json +1 -2
- package/src/confirmation.ts +45 -351
- package/src/evidence.ts +59 -220
- package/src/harness-verify.ts +36 -206
- package/src/index.ts +44 -366
- package/src/ledger.ts +24 -71
- package/src/poc-runner.ts +3 -3
- package/src/oob-oracle.ts +0 -279
package/src/index.ts
CHANGED
|
@@ -12,20 +12,12 @@ import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-age
|
|
|
12
12
|
import { matchesKey, Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
13
13
|
import { type TSchema, Type } from "typebox";
|
|
14
14
|
import {
|
|
15
|
-
CANARY_ASSESSMENT_VALUES,
|
|
16
15
|
CONFIRM_DIFFERENTIAL_VALUES,
|
|
17
16
|
CONFIRM_VERDICT_VALUES,
|
|
18
|
-
PANEL_VERDICT_VALUES,
|
|
19
17
|
SEVERITY_MATCH_VALUES,
|
|
20
18
|
validateMainAgentVerdict,
|
|
21
|
-
validatePanelVotes,
|
|
22
19
|
} from "./evidence.ts";
|
|
23
|
-
import {
|
|
24
|
-
controlTargetAuthorizationError,
|
|
25
|
-
type HarnessVerifyResult,
|
|
26
|
-
replayDifferential,
|
|
27
|
-
replayIntraTarget,
|
|
28
|
-
} from "./harness-verify.ts";
|
|
20
|
+
import { type HarnessVerifyResult, replayIntraTarget } from "./harness-verify.ts";
|
|
29
21
|
import {
|
|
30
22
|
addCaseResult,
|
|
31
23
|
addEvidenceItemResult,
|
|
@@ -54,8 +46,6 @@ import {
|
|
|
54
46
|
getCasefilePath,
|
|
55
47
|
LINK_KIND_VALUES,
|
|
56
48
|
linkCasesResult,
|
|
57
|
-
type MainAgentVerification,
|
|
58
|
-
type OobVerification,
|
|
59
49
|
type PendingConfirmation,
|
|
60
50
|
type PocEvidenceRun,
|
|
61
51
|
PRIORITY_VALUES,
|
|
@@ -71,13 +61,6 @@ import {
|
|
|
71
61
|
updateCaseResult,
|
|
72
62
|
writeCaseContext,
|
|
73
63
|
} from "./ledger.ts";
|
|
74
|
-
import {
|
|
75
|
-
type OobOracleConfig,
|
|
76
|
-
type ProvisionedCallback,
|
|
77
|
-
provisionCallback,
|
|
78
|
-
readOobOracleConfig,
|
|
79
|
-
verifyOobDifferential,
|
|
80
|
-
} from "./oob-oracle.ts";
|
|
81
64
|
import { type PocRun, type PocRunOptions, runPoc } from "./poc-runner.ts";
|
|
82
65
|
import {
|
|
83
66
|
detectWorkspaceRoot,
|
|
@@ -240,12 +223,13 @@ const CoverageAddSchema = Type.Object(
|
|
|
240
223
|
// ── Tool: PromoteFinding (phase 1) / ConfirmFinding (phase 2) ──────────
|
|
241
224
|
//
|
|
242
225
|
// Confirmation is TWO-PHASE and main-agent-owned: PromoteFinding runs the PoC
|
|
243
|
-
//
|
|
244
|
-
//
|
|
226
|
+
// twice against the case target, validates nonce-bound evidence.json, replays
|
|
227
|
+
// the attack and baseline requests itself, and records the pending bundle;
|
|
228
|
+
// ConfirmFinding then performs the main coordinator's semantic review and
|
|
245
229
|
// commits or refuses the verdict. Subagents may gather or challenge evidence,
|
|
246
230
|
// but they cannot run validation or confirmation gates. Zero exit is necessary
|
|
247
|
-
// run integrity and markers are diagnostic only; the machine records
|
|
248
|
-
// predicate
|
|
231
|
+
// run integrity and markers are diagnostic only; the machine records the
|
|
232
|
+
// predicate differential and the main agent owns the semantic judgment.
|
|
249
233
|
|
|
250
234
|
const PromoteSchema = Type.Object(
|
|
251
235
|
{
|
|
@@ -253,55 +237,12 @@ const PromoteSchema = Type.Object(
|
|
|
253
237
|
poc_path: Type.String({
|
|
254
238
|
description: "Absolute path to the PoC script on disk",
|
|
255
239
|
}),
|
|
256
|
-
control_path: Type.Optional(
|
|
257
|
-
Type.String({
|
|
258
|
-
description:
|
|
259
|
-
"Optional absolute path to the SAME script as poc_path (sha256-equality is ENFORCED). Defaults to poc_path. The harness runs it with PI_POC_MODE=control and PI_POC_TARGET=control_target.",
|
|
260
|
-
}),
|
|
261
|
-
),
|
|
262
|
-
mode: Type.Optional(
|
|
263
|
-
Type.String({
|
|
264
|
-
enum: ["inter_host", "intra_target"],
|
|
265
|
-
description:
|
|
266
|
-
"Differential shape. 'inter_host' (default) proves target-dependence with a distinct patched control host — for body-carried proof (file read, injection exfil, info leak, reflection). 'intra_target' proves it with a legitimate same-host baseline request declared in the evidence — for access-control / business-logic classes (IDOR, auth bypass, privilege escalation, logic flaws) where the discriminating variable is identity or a parameter, not the host. In intra_target the evidence must set verify.mode='intra_target' and include a baseline; control_target/control_path are not used.",
|
|
267
|
-
}),
|
|
268
|
-
),
|
|
269
|
-
control_target: Type.Optional(
|
|
270
|
-
Type.String({
|
|
271
|
-
minLength: 1,
|
|
272
|
-
description:
|
|
273
|
-
"REQUIRED for mode='inter_host': a distinct baseline target that lacks the vulnerability and is operator-approved through PI_POC_CONTROL_TARGETS (patched replica, second account, baseline service). Not used for mode='intra_target'.",
|
|
274
|
-
}),
|
|
275
|
-
),
|
|
276
240
|
local: Type.Optional(
|
|
277
241
|
Type.Boolean({
|
|
278
242
|
description:
|
|
279
243
|
"Run with network access instead of --network none. Requires operator authorization via PI_POC_ALLOW_NETWORK=1. True host fallback additionally requires PI_POC_ALLOW_LOCAL=1.",
|
|
280
244
|
}),
|
|
281
245
|
),
|
|
282
|
-
oob: Type.Optional(
|
|
283
|
-
Type.Boolean({
|
|
284
|
-
description:
|
|
285
|
-
"Blind/OOB confirmation via the operator-run oracle (PI_OOB_ORACLE_URL). The harness provisions per-run callback tokens, injects PI_POC_CALLBACK_DOMAIN into the runs, and polls the oracle itself: promotion requires target-token interactions, ZERO control-token interactions, attested source separation (PI_OOB_SOURCE_SEPARATED=1), and self-source/missing-src_ip interactions are rejected. Without an oracle this fails closed.",
|
|
286
|
-
}),
|
|
287
|
-
),
|
|
288
|
-
panel_votes: Type.Optional(
|
|
289
|
-
Type.Array(
|
|
290
|
-
Type.Object(
|
|
291
|
-
{
|
|
292
|
-
verdict: Type.String({ enum: [...PANEL_VERDICT_VALUES] }),
|
|
293
|
-
rationale: Type.String({ description: "Why this voter reached its verdict" }),
|
|
294
|
-
model: Type.String({ description: "Which model voted" }),
|
|
295
|
-
at: Type.Optional(Type.String({ description: "Vote timestamp (ISO)" })),
|
|
296
|
-
},
|
|
297
|
-
{ additionalProperties: false },
|
|
298
|
-
),
|
|
299
|
-
{
|
|
300
|
-
description:
|
|
301
|
-
"Optional pre-gate panel votes (≤5). CONFIRMED later requires a 2/3 exploit quorum or an explicit override note on the verdict; votes never commit anything.",
|
|
302
|
-
},
|
|
303
|
-
),
|
|
304
|
-
),
|
|
305
246
|
},
|
|
306
247
|
{ additionalProperties: false },
|
|
307
248
|
);
|
|
@@ -321,12 +262,12 @@ const ConfirmSchema = Type.Object(
|
|
|
321
262
|
re_execution_note: Type.Optional(
|
|
322
263
|
Type.String({
|
|
323
264
|
description:
|
|
324
|
-
"What the main agent observed during review
|
|
265
|
+
"What the main agent observed during review of the runs and transcripts. Mandatory for CONFIRMED.",
|
|
325
266
|
}),
|
|
326
267
|
),
|
|
327
268
|
differential: Type.String({
|
|
328
269
|
enum: [...CONFIRM_DIFFERENTIAL_VALUES],
|
|
329
|
-
description: "
|
|
270
|
+
description: "Attack vs baseline evidence comparison. CONFIRMED requires target_only.",
|
|
330
271
|
}),
|
|
331
272
|
severity_match: Type.Optional(
|
|
332
273
|
Type.String({
|
|
@@ -340,25 +281,6 @@ const ConfirmSchema = Type.Object(
|
|
|
340
281
|
"The main agent's own failed attempt to disprove — becomes the case's disconfirmation",
|
|
341
282
|
}),
|
|
342
283
|
),
|
|
343
|
-
canary_assessment: Type.Optional(
|
|
344
|
-
Type.String({
|
|
345
|
-
enum: [...CANARY_ASSESSMENT_VALUES],
|
|
346
|
-
description:
|
|
347
|
-
"verified when the replay carried a harness-generated reflection canary; otherwise not_applicable with a concrete reason",
|
|
348
|
-
}),
|
|
349
|
-
),
|
|
350
|
-
canary_reason: Type.Optional(
|
|
351
|
-
Type.String({
|
|
352
|
-
description:
|
|
353
|
-
"Why a causal reflection canary is not meaningful for this exploit class. Required when canary_assessment=not_applicable.",
|
|
354
|
-
}),
|
|
355
|
-
),
|
|
356
|
-
panel_override_note: Type.Optional(
|
|
357
|
-
Type.String({
|
|
358
|
-
description:
|
|
359
|
-
"Why CONFIRMED proceeds without a 2/3 exploit panel quorum (panel skipped, unavailable, or documented disagreement). Required for CONFIRMED whenever quorum was not reached.",
|
|
360
|
-
}),
|
|
361
|
-
),
|
|
362
284
|
model: Type.Optional(
|
|
363
285
|
Type.String({ description: "Which model judged (recorded for the accuracy ledger)" }),
|
|
364
286
|
),
|
|
@@ -1005,19 +927,15 @@ export default function casefileExtension(pi: ExtensionAPI) {
|
|
|
1005
927
|
name: "PromoteFinding",
|
|
1006
928
|
label: "Run PoC Evidence",
|
|
1007
929
|
description:
|
|
1008
|
-
"Main-agent phase 1 of confirmation: run the same PoC twice against the case target
|
|
1009
|
-
promptSnippet:
|
|
1010
|
-
"Phase 1: run PoC evidence (target x2 + control) and record the pending bundle",
|
|
930
|
+
"Main-agent phase 1 of confirmation: run the same PoC twice against the case target, validate nonce-bound evidence.json with a response-body assertion, then have the harness replay the attack request and a legitimate same-host baseline request itself. The machine records a predicate differential (attack matched, baseline did not); that is not automatically a vulnerability verdict. Exit 0 is necessary run integrity, never proof. Networked execution and private replay are operator-gated. Records a pending bundle for main-agent semantic review via ConfirmFinding. Worker/subagent processes are rejected.",
|
|
931
|
+
promptSnippet: "Phase 1: run PoC evidence (target x2) and record the pending bundle",
|
|
1011
932
|
promptGuidelines: [
|
|
1012
933
|
"Use PromoteFinding only from the main/coordinator agent when an investigating case has a concrete PoC script on disk and you are ready to subject its claim to the machine gate.",
|
|
1013
934
|
"Prerequisites: status='investigating' and non-empty poc, evidence, impact, severity, target, plus an artifact-backed EvidenceAdd 'observation' item on the case (the initial signal, with artifact_path). The final disconfirmation comes from the main agent at confirm time.",
|
|
1014
|
-
"The PoC MUST write evidence.json to $PI_POC_EVIDENCE_DIR: { nonce (echo $PI_POC_NONCE), claim, verify: { method, url, expect: { status?, body_contains/body_regex } }, observations }. A non-empty body predicate is mandatory; status-only evidence is rejected. verify.url must belong to the case target.",
|
|
1015
|
-
"
|
|
1016
|
-
"control_path is optional and defaults to poc_path; if supplied, it must be the SAME script as poc_path. control_target must be pre-approved by the operator in PI_POC_CONTROL_TARGETS. The harness derives the control request from the target request, changes only its origin, and applies the same predicates to two conclusive responses.",
|
|
1017
|
-
"oob=true unlocks blind/OOB classes (SSRF, blind XSS, XXE): the harness provisions per-run callback tokens via the operator's oracle (PI_OOB_ORACLE_URL), injects PI_POC_CALLBACK_DOMAIN into the runs, and polls the oracle itself — target-token interactions with ZERO control-token interactions are required. Promotion additionally requires attested source separation (PI_OOB_SOURCE_SEPARATED=1); without it the verification stays diagnostic.",
|
|
935
|
+
"The PoC MUST write evidence.json to $PI_POC_EVIDENCE_DIR: { nonce (echo $PI_POC_NONCE), claim, verify: { method, url, expect: { status?, body_contains/body_regex } }, observations, baseline }. A non-empty body predicate is mandatory; status-only evidence is rejected. verify.url and baseline.url must belong to the case target.",
|
|
936
|
+
"baseline is a legitimate same-host request whose response must NOT satisfy the attack predicate — your own account's resource for IDOR, the request without the payload for injection. It must differ from the attack request (identity or a parameter, not just whitespace).",
|
|
1018
937
|
"local:true requires PI_POC_ALLOW_NETWORK=1. Private/internal harness replay additionally requires PI_POC_ALLOW_PRIVATE_REPLAY=1. Neither silently falls back to a model verdict.",
|
|
1019
|
-
"
|
|
1020
|
-
"After the bundle is recorded, stay in the main agent: inspect the script/evidence, attempt disconfirmation, and call ConfirmFinding itself; that call performs a fresh harness-owned target/control replay. Never delegate validation/confirmation and never CaseUpdate status='confirmed' directly.",
|
|
938
|
+
"After the bundle is recorded, stay in the main agent: inspect the script/evidence, attempt disconfirmation, and call ConfirmFinding yourself. Never delegate validation/confirmation and never CaseUpdate status='confirmed' directly.",
|
|
1021
939
|
],
|
|
1022
940
|
parameters: PromoteSchema,
|
|
1023
941
|
|
|
@@ -1039,155 +957,60 @@ export default function casefileExtension(pi: ExtensionAPI) {
|
|
|
1039
957
|
};
|
|
1040
958
|
|
|
1041
959
|
const pocPath = (params.poc_path as string | undefined)?.trim() ?? "";
|
|
1042
|
-
const controlPath = (params.control_path as string | undefined)?.trim() || pocPath;
|
|
1043
|
-
const controlTarget = (params.control_target as string | undefined)?.trim() ?? "";
|
|
1044
|
-
const mode: "inter_host" | "intra_target" =
|
|
1045
|
-
(params.mode as string | undefined) === "intra_target" ? "intra_target" : "inter_host";
|
|
1046
|
-
const isIntra = mode === "intra_target";
|
|
1047
960
|
if (!pocPath) {
|
|
1048
961
|
return fail("poc_path is REQUIRED: absolute path to the PoC script run by the harness.");
|
|
1049
962
|
}
|
|
1050
963
|
const caseTarget = current.target ?? "";
|
|
1051
|
-
// Panel votes (advisory pre-gate): validated here so a malformed panel
|
|
1052
|
-
// is rejected before any sandboxed run is paid for.
|
|
1053
|
-
let panelVotes: PendingConfirmation["panelVotes"];
|
|
1054
|
-
if (params.panel_votes !== undefined) {
|
|
1055
|
-
const parsedVotes = validatePanelVotes(params.panel_votes);
|
|
1056
|
-
if (!parsedVotes.ok) {
|
|
1057
|
-
return fail(`Invalid panel_votes: ${parsedVotes.error}`);
|
|
1058
|
-
}
|
|
1059
|
-
panelVotes = parsedVotes.votes;
|
|
1060
|
-
}
|
|
1061
|
-
// ── OOB callback (Tier 1, opt-in for blind classes) ──
|
|
1062
|
-
// The operator-run oracle owns the evidence channel; the harness owns
|
|
1063
|
-
// the secret (per-run token, provisioned before the runs and injected
|
|
1064
|
-
// as env — the value does not exist when the script was written).
|
|
1065
|
-
// Without an oracle this stays fail-closed.
|
|
1066
|
-
const oobRequested = params.oob === true;
|
|
1067
|
-
// Intra-target + OOB is rejected up front: intra_target's
|
|
1068
|
-
// discriminating variable is identity/parameter on the SAME host;
|
|
1069
|
-
// mixing it with a callback differential would make precedence
|
|
1070
|
-
// ambiguous. OOB is for inter-host/blind classes.
|
|
1071
|
-
if (isIntra && oobRequested) {
|
|
1072
|
-
return fail(
|
|
1073
|
-
"mode:'intra_target' cannot be combined with oob:true — intra-target proof uses a same-host baseline request, not a callback channel. Use one or the other.",
|
|
1074
|
-
);
|
|
1075
|
-
}
|
|
1076
|
-
let oobConfig: OobOracleConfig | undefined;
|
|
1077
|
-
let targetCallback: ProvisionedCallback | undefined;
|
|
1078
|
-
let controlCallback: ProvisionedCallback | undefined;
|
|
1079
|
-
if (oobRequested) {
|
|
1080
|
-
const oracle = readOobOracleConfig();
|
|
1081
|
-
if (!oracle.config) {
|
|
1082
|
-
return fail(`OOB CONFIRMATION UNAVAILABLE: ${oracle.error}`);
|
|
1083
|
-
}
|
|
1084
|
-
oobConfig = oracle.config;
|
|
1085
|
-
// Provision both identities concurrently — each is an oracle round trip.
|
|
1086
|
-
[targetCallback, controlCallback] = await Promise.all([
|
|
1087
|
-
provisionCallback(oobConfig),
|
|
1088
|
-
provisionCallback(oobConfig),
|
|
1089
|
-
]);
|
|
1090
|
-
}
|
|
1091
|
-
// OOB-only bundles (blind classes, no operator-approved control host)
|
|
1092
|
-
// prove target-dependence via the token differential instead.
|
|
1093
|
-
const oobOnly = oobRequested && !controlTarget;
|
|
1094
|
-
if (!isIntra && !oobOnly) {
|
|
1095
|
-
if (!controlTarget) {
|
|
1096
|
-
return fail(
|
|
1097
|
-
"control_target is REQUIRED for inter-host mode: a distinct baseline target that lacks the vulnerability. For access-control/logic bugs use mode='intra_target' with an evidence baseline instead; for blind/OOB classes pass oob=true (with or without a control target).",
|
|
1098
|
-
);
|
|
1099
|
-
}
|
|
1100
|
-
if (controlTarget === current.target) {
|
|
1101
|
-
return fail(
|
|
1102
|
-
"control_target must differ from the case target; a control run against the vulnerable target proves nothing.",
|
|
1103
|
-
);
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
964
|
if (params.local === true && process.env.PI_POC_ALLOW_NETWORK !== "1") {
|
|
1107
965
|
return fail(
|
|
1108
966
|
"Networked PoC execution is operator-gated. Set PI_POC_ALLOW_NETWORK=1 to authorize the host-network sandbox for this session.",
|
|
1109
967
|
);
|
|
1110
968
|
}
|
|
1111
|
-
if (!isIntra && !oobOnly) {
|
|
1112
|
-
const controlAuthorization = controlTargetAuthorizationError(controlTarget);
|
|
1113
|
-
if (controlAuthorization) {
|
|
1114
|
-
return fail(
|
|
1115
|
-
`CONTROL AUTHORIZATION FAILED: ${controlAuthorization}. ` +
|
|
1116
|
-
"The operator must set PI_POC_CONTROL_TARGETS to the exact approved control host/origin before this control can anchor confirmation.",
|
|
1117
|
-
);
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
969
|
|
|
1121
|
-
// Anti-cheat: hash the PoC
|
|
1122
|
-
//
|
|
970
|
+
// Anti-cheat: hash the PoC so the recorded bundle is bound to the exact
|
|
971
|
+
// bytes that ran (re-verified at confirm time).
|
|
1123
972
|
let pocHash: string | undefined;
|
|
1124
973
|
try {
|
|
1125
974
|
pocHash = createHash("sha256").update(readFileSync(pocPath)).digest("hex");
|
|
1126
975
|
} catch (e) {
|
|
1127
976
|
return fail(`Cannot read PoC script: ${(e as Error).message}`);
|
|
1128
977
|
}
|
|
1129
|
-
if (!isIntra && !oobOnly) {
|
|
1130
|
-
let controlHash: string | undefined;
|
|
1131
|
-
try {
|
|
1132
|
-
controlHash = createHash("sha256").update(readFileSync(controlPath)).digest("hex");
|
|
1133
|
-
} catch (e) {
|
|
1134
|
-
return fail(
|
|
1135
|
-
`Cannot read control script for the same-file check: ${(e as Error).message}`,
|
|
1136
|
-
);
|
|
1137
|
-
}
|
|
1138
|
-
if (pocHash !== controlHash) {
|
|
1139
|
-
return fail(
|
|
1140
|
-
"CONTROL CHECK FAILED: control_path must be the SAME script as poc_path (sha256 mismatch). Case remains investigating.",
|
|
1141
|
-
);
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
978
|
|
|
1145
|
-
|
|
1146
|
-
const runOptions = (pocMode: string, target: string): PocRunOptions => ({
|
|
979
|
+
const runOptions = (target: string): PocRunOptions => ({
|
|
1147
980
|
network: params.local === true ? "host" : "none",
|
|
1148
981
|
local: params.local === true,
|
|
1149
982
|
env: {
|
|
1150
|
-
PI_POC_MODE:
|
|
983
|
+
PI_POC_MODE: "poc",
|
|
1151
984
|
PI_POC_TARGET: target,
|
|
1152
|
-
...(oobRequested && targetCallback && controlCallback
|
|
1153
|
-
? {
|
|
1154
|
-
PI_POC_CALLBACK_DOMAIN:
|
|
1155
|
-
pocMode === "control" ? controlCallback.domain : targetCallback.domain,
|
|
1156
|
-
}
|
|
1157
|
-
: {}),
|
|
1158
985
|
},
|
|
1159
986
|
});
|
|
1160
987
|
|
|
1161
988
|
// Determinism: TWO target runs. Exit 0 is run integrity only; nonce-bound
|
|
1162
|
-
// body evidence plus the harness-owned
|
|
1163
|
-
//
|
|
1164
|
-
const run1 = runPoc(pocPath, runOptions(
|
|
1165
|
-
const run2 = runPoc(pocPath, runOptions(
|
|
1166
|
-
|
|
1167
|
-
const evidenceRun = (
|
|
1168
|
-
r: PocRun,
|
|
1169
|
-
mode: "poc" | "control",
|
|
1170
|
-
target: string,
|
|
1171
|
-
): PocEvidenceRun => {
|
|
989
|
+
// body evidence plus the harness-owned attack/baseline replay form the
|
|
990
|
+
// machine gate.
|
|
991
|
+
const run1 = runPoc(pocPath, runOptions(caseTarget));
|
|
992
|
+
const run2 = runPoc(pocPath, runOptions(caseTarget));
|
|
993
|
+
|
|
994
|
+
const evidenceRun = (r: PocRun, target: string): PocEvidenceRun => {
|
|
1172
995
|
if (!r.completed || !r.outputComplete) {
|
|
1173
996
|
return fail(
|
|
1174
|
-
|
|
997
|
+
`PoC run did not complete or output capture was incomplete` +
|
|
1175
998
|
(r.infraError ? ` (infra: ${r.output.trim()})` : "") +
|
|
1176
999
|
". A crash is not evidence. Case remains investigating.",
|
|
1177
1000
|
);
|
|
1178
1001
|
}
|
|
1179
1002
|
if (r.evidenceError) {
|
|
1180
1003
|
return fail(
|
|
1181
|
-
`EVIDENCE CONTRACT FAILED
|
|
1182
|
-
"The PoC must write evidence.json to $PI_POC_EVIDENCE_DIR — { nonce (echo $PI_POC_NONCE), claim, verify: { method, url, expect: { status?, body_contains / body_regex } }, observations }; a response-body assertion is mandatory — " +
|
|
1004
|
+
`EVIDENCE CONTRACT FAILED: ${r.evidenceError}. ` +
|
|
1005
|
+
"The PoC must write evidence.json to $PI_POC_EVIDENCE_DIR — { nonce (echo $PI_POC_NONCE), claim, verify: { method, url, expect: { status?, body_contains / body_regex } }, observations, baseline }; a response-body assertion is mandatory — " +
|
|
1183
1006
|
"the file is bound to this run and validated by the harness. Case remains investigating.",
|
|
1184
1007
|
);
|
|
1185
1008
|
}
|
|
1186
1009
|
if (!r.evidence || !r.evidenceSha256 || !r.nonce) {
|
|
1187
|
-
return fail(
|
|
1010
|
+
return fail("PoC run produced no evidence. Case remains investigating.");
|
|
1188
1011
|
}
|
|
1189
1012
|
return {
|
|
1190
|
-
mode,
|
|
1013
|
+
mode: "poc",
|
|
1191
1014
|
target,
|
|
1192
1015
|
nonce: r.nonce,
|
|
1193
1016
|
ranAt: r.ranAt,
|
|
@@ -1203,89 +1026,26 @@ export default function casefileExtension(pi: ExtensionAPI) {
|
|
|
1203
1026
|
};
|
|
1204
1027
|
|
|
1205
1028
|
const targetRuns: [PocEvidenceRun, PocEvidenceRun] = [
|
|
1206
|
-
evidenceRun(run1,
|
|
1207
|
-
evidenceRun(run2,
|
|
1029
|
+
evidenceRun(run1, caseTarget),
|
|
1030
|
+
evidenceRun(run2, caseTarget),
|
|
1208
1031
|
];
|
|
1209
1032
|
|
|
1210
|
-
// Reflection canary + OOB is rejected after run 1 (the canary is
|
|
1211
|
-
// declared inside evidence.json): the canary path requires a harness
|
|
1212
|
-
// response transcript, which OOB-only bundles never produce — the
|
|
1213
|
-
// per-run callback token IS the causality signal there.
|
|
1214
|
-
if (oobRequested && targetRuns.some((r) => r.evidence.verify.canary !== undefined)) {
|
|
1215
|
-
return fail(
|
|
1216
|
-
"verify.canary cannot be combined with oob:true — the per-run callback token already provides a harness-owned causality signal. Remove the {{PI_POC_CANARY}} placeholder and verify.canary from evidence.json, then re-promote.",
|
|
1217
|
-
);
|
|
1218
|
-
}
|
|
1219
1033
|
const allowPrivateReplay = process.env.PI_POC_ALLOW_PRIVATE_REPLAY === "1";
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
return fail(
|
|
1229
|
-
"INTRA-TARGET FAILED: the PoC's evidence.json must set verify.mode='intra_target' when promoting in intra-target mode.",
|
|
1230
|
-
);
|
|
1231
|
-
}
|
|
1232
|
-
if (!ev0.baseline) {
|
|
1233
|
-
return fail(
|
|
1234
|
-
"INTRA-TARGET FAILED: evidence.json must include a baseline — a legitimate same-host request whose response must NOT satisfy the attack predicate.",
|
|
1235
|
-
);
|
|
1236
|
-
}
|
|
1237
|
-
harnessVerified = await replayIntraTarget(ev0, caseTarget, {
|
|
1238
|
-
allowPrivate: allowPrivateReplay,
|
|
1239
|
-
});
|
|
1240
|
-
} else if (!oobOnly) {
|
|
1241
|
-
// Inter-host (Tier 2): the harness executes the SAME request template
|
|
1242
|
-
// against target and operator-approved control, applying the target's
|
|
1243
|
-
// predicates to both. DNS is pinned at connect time.
|
|
1244
|
-
controlRun = evidenceRun(
|
|
1245
|
-
runPoc(controlPath, runOptions("control", controlTarget)),
|
|
1246
|
-
"control",
|
|
1247
|
-
controlTarget,
|
|
1248
|
-
);
|
|
1249
|
-
harnessVerified = await replayDifferential(
|
|
1250
|
-
targetRuns[0].evidence,
|
|
1251
|
-
caseTarget,
|
|
1252
|
-
controlTarget,
|
|
1253
|
-
{ allowPrivate: allowPrivateReplay },
|
|
1254
|
-
);
|
|
1255
|
-
}
|
|
1256
|
-
// OOB differential: poll the oracle for both run tokens. The ledger's
|
|
1257
|
-
// assertMachineConfirmation consumes this BEFORE the response-diff
|
|
1258
|
-
// requirement — blind classes pass via this path when the oracle saw
|
|
1259
|
-
// the target token and NOT the control token under attested source
|
|
1260
|
-
// separation.
|
|
1261
|
-
let callbackVerified: OobVerification | undefined;
|
|
1262
|
-
if (oobConfig && targetCallback && controlCallback) {
|
|
1263
|
-
callbackVerified = (
|
|
1264
|
-
await verifyOobDifferential({
|
|
1265
|
-
targetToken: targetCallback.token,
|
|
1266
|
-
controlToken: controlCallback.token,
|
|
1267
|
-
})
|
|
1268
|
-
).verification;
|
|
1269
|
-
}
|
|
1034
|
+
// Intra-target differential: prove target-dependence with the evidence's
|
|
1035
|
+
// same-host baseline request. The harness sends attack + baseline to the
|
|
1036
|
+
// case target and requires the proof on attack only.
|
|
1037
|
+
const harnessVerified: HarnessVerifyResult = await replayIntraTarget(
|
|
1038
|
+
targetRuns[0].evidence,
|
|
1039
|
+
caseTarget,
|
|
1040
|
+
{ allowPrivate: allowPrivateReplay },
|
|
1041
|
+
);
|
|
1270
1042
|
const bundle: PendingConfirmation = {
|
|
1271
1043
|
caseId,
|
|
1272
1044
|
ranAt: new Date().toISOString(),
|
|
1273
1045
|
pocPath,
|
|
1274
1046
|
pocSha256: pocHash,
|
|
1275
|
-
mode,
|
|
1276
1047
|
targetRuns,
|
|
1277
1048
|
harnessVerified,
|
|
1278
|
-
...(panelVotes ? { panelVotes } : {}),
|
|
1279
|
-
...(callbackVerified && targetCallback && controlCallback
|
|
1280
|
-
? {
|
|
1281
|
-
callbackVerified,
|
|
1282
|
-
oobTokens: {
|
|
1283
|
-
targetToken: targetCallback.token,
|
|
1284
|
-
controlToken: controlCallback.token,
|
|
1285
|
-
},
|
|
1286
|
-
}
|
|
1287
|
-
: {}),
|
|
1288
|
-
...(!(isIntra || oobOnly) ? { controlPath, controlTarget, controlRun } : {}),
|
|
1289
1049
|
};
|
|
1290
1050
|
|
|
1291
1051
|
let record: CaseRecord;
|
|
@@ -1301,15 +1061,12 @@ export default function casefileExtension(pi: ExtensionAPI) {
|
|
|
1301
1061
|
type: "text",
|
|
1302
1062
|
text:
|
|
1303
1063
|
`Phase 1 complete — evidence bundle recorded on ${caseId} (expires in 1h).\n` +
|
|
1304
|
-
`
|
|
1064
|
+
`Target runs: 2, same-host baseline differential — all with validated nonce-bound evidence.json.\n` +
|
|
1305
1065
|
`Evidence sha256: ${targetRuns[0].evidenceSha256}\n` +
|
|
1306
1066
|
`PoC script sha256 (at run time): ${pocHash}\n` +
|
|
1307
1067
|
`Harness verify replay: ${harnessVerified?.attempted ? (harnessVerified.pass ? `PASS (status ${harnessVerified.status})` : `FAILED — ${harnessVerified.note}`) : (harnessVerified?.note ?? "not run")}
|
|
1308
1068
|
` +
|
|
1309
|
-
(
|
|
1310
|
-
? `OOB oracle: target-token hits ${callbackVerified.targetHits}, control-token hits ${callbackVerified.controlHits}, source-separated: ${String(callbackVerified.sourceSeparated)} — ${callbackVerified.note}\n`
|
|
1311
|
-
: "") +
|
|
1312
|
-
`\nMAIN-AGENT REVIEW REQUIRED (do not delegate): inspect case ${caseId}, PoC ${pocPath}, ${isIntra ? "same-host baseline" : `control ${controlTarget}`}, evidence ${targetRuns[0].evidenceSha256}, and PoC hash ${pocHash}. Hunt for a trivial predicate or fabricated differential and perform a concrete disconfirmation attempt, then call ConfirmFinding yourself. A CONFIRMED call performs and stores a fresh harness-owned ${isIntra ? "attack/baseline" : "target/control"} replay; NOT_CONFIRMED keeps the case investigating.`,
|
|
1069
|
+
`\nMAIN-AGENT REVIEW REQUIRED (do not delegate): inspect case ${caseId}, PoC ${pocPath}, the same-host baseline, evidence ${targetRuns[0].evidenceSha256}, and PoC hash ${pocHash}. Hunt for a trivial predicate or fabricated differential and perform a concrete disconfirmation attempt, then call ConfirmFinding yourself. NOT_CONFIRMED keeps the case investigating.`,
|
|
1313
1070
|
},
|
|
1314
1071
|
],
|
|
1315
1072
|
details: {
|
|
@@ -1317,10 +1074,7 @@ export default function casefileExtension(pi: ExtensionAPI) {
|
|
|
1317
1074
|
bundle: {
|
|
1318
1075
|
caseId,
|
|
1319
1076
|
ranAt: bundle.ranAt,
|
|
1320
|
-
mode,
|
|
1321
1077
|
pocPath,
|
|
1322
|
-
controlPath: isIntra ? undefined : controlPath,
|
|
1323
|
-
controlTarget: isIntra ? undefined : controlTarget,
|
|
1324
1078
|
pocSha256: pocHash,
|
|
1325
1079
|
evidenceSha256: targetRuns[0].evidenceSha256,
|
|
1326
1080
|
harnessVerified,
|
|
@@ -1358,15 +1112,15 @@ export default function casefileExtension(pi: ExtensionAPI) {
|
|
|
1358
1112
|
name: "ConfirmFinding",
|
|
1359
1113
|
label: "Main-Agent Confirmation",
|
|
1360
1114
|
description:
|
|
1361
|
-
"Phase 2 of confirmation, reserved for the main/coordinator agent: commit or refuse promotion after independently
|
|
1115
|
+
"Phase 2 of confirmation, reserved for the main/coordinator agent: commit or refuse promotion after independently reviewing the recorded evidence. The verdict requires a target-only differential, a concrete re_execution_note and disconfirmation_attempt, and the still-valid PromoteFinding bundle. The machine floor is the promote-time harness replay plus confirm-time bundle re-validation — there is no fresh network replay at verdict time, so weigh bundle age (1h TTL) in your review. The machine transcript is evidence, not the semantic vulnerability verdict. Worker/subagent processes are rejected. Three verdicts: CONFIRMED (you reproduced real impact), NOT_CONFIRMED (you POSITIVELY disproved it), INCONCLUSIVE (you could neither reproduce nor disprove — the case is preserved for manual review, never dropped).",
|
|
1362
1116
|
promptSnippet: "Main agent: independently re-test, then commit or refuse PoC confirmation",
|
|
1363
1117
|
promptGuidelines: [
|
|
1364
1118
|
"Run only in the main/coordinator agent after PromoteFinding returns. Do not dispatch a worker to decide or author this verdict.",
|
|
1365
|
-
"Verify with DISBELIEF: assume the finding is a false positive until
|
|
1119
|
+
"Verify with DISBELIEF: assume the finding is a false positive until the recorded evidence proves otherwise. Read the PoC script, both run transcripts, the attack/baseline replay, and the evidence artifacts (not the hunter's narrative) — a difference you cannot tie to the baseline is not proof.",
|
|
1366
1120
|
"Provenance: the proof must exercise THIS finding's own mechanism. Evidence obtained through a DIFFERENT bug (e.g. 'SQLi' proven by dumping the DB via an RCE) does not confirm it — that is INCONCLUSIVE at best.",
|
|
1367
1121
|
"Kill the cheapest benign explanation: is this the technology's intended behavior? Did the attacker supply the 'secret' themselves (circular)? Is the claimed C/I/A impact actually demonstrated?",
|
|
1368
1122
|
"Want a second pair of eyes? Dispatch a read-only skeptic subagent to re-test — it CANNOT confirm (only the main agent commits). You review its verdict and commit it here.",
|
|
1369
|
-
"CONFIRMED requires differential: 'target_only', re_execution_note, and disconfirmation_attempt (your failed disproof). A verdict missing any of these is rejected.
|
|
1123
|
+
"CONFIRMED requires differential: 'target_only', re_execution_note, and disconfirmation_attempt (your failed disproof). A verdict missing any of these is rejected.",
|
|
1370
1124
|
"NOT_CONFIRMED means you POSITIVELY disproved it (by-design, circular, mislabeled, no impact). Never mark NOT_CONFIRMED merely because you could not reproduce it.",
|
|
1371
1125
|
"INCONCLUSIVE when you could neither reproduce nor disprove (needs auth, a second account, specific state, timing, or a blind/stored trigger you cannot observe). The case stays investigating and is preserved for manual review — dropping a real finding is worse than keeping an unproven one.",
|
|
1372
1126
|
"Every verdict consumes the attempt: a fresh PromoteFinding run is required to try again. Never CaseUpdate status='confirmed' directly — always PromoteFinding + ConfirmFinding.",
|
|
@@ -1384,83 +1138,7 @@ export default function casefileExtension(pi: ExtensionAPI) {
|
|
|
1384
1138
|
if (!parsedVerdict.ok) {
|
|
1385
1139
|
throw new Error(`Invalid main-agent confirmation verdict: ${parsedVerdict.error}`);
|
|
1386
1140
|
}
|
|
1387
|
-
|
|
1388
|
-
if (parsedVerdict.verdict.verdict === "CONFIRMED") {
|
|
1389
|
-
const current = getCaseById(caseId);
|
|
1390
|
-
if (!current) throw new Error(`Case not found: ${caseId}`);
|
|
1391
|
-
const bundle = current.pendingConfirmation;
|
|
1392
|
-
if (!bundle) {
|
|
1393
|
-
throw new Error("No pending confirmation on this case — run PromoteFinding first");
|
|
1394
|
-
}
|
|
1395
|
-
const allowPrivate = process.env.PI_POC_ALLOW_PRIVATE_REPLAY === "1";
|
|
1396
|
-
const caseTargetForReplay = current.target ?? bundle.targetRuns[0].target;
|
|
1397
|
-
let replay: HarnessVerifyResult;
|
|
1398
|
-
if (bundle.mode === "intra_target") {
|
|
1399
|
-
// Same-host attack-vs-baseline replay; no control target to authorize.
|
|
1400
|
-
replay = await replayIntraTarget(bundle.targetRuns[0].evidence, caseTargetForReplay, {
|
|
1401
|
-
allowPrivate,
|
|
1402
|
-
});
|
|
1403
|
-
} else if (bundle.callbackVerified?.attempted && bundle.oobTokens) {
|
|
1404
|
-
// OOB differential: fresh harness-owned re-poll of BOTH run tokens.
|
|
1405
|
-
// Re-polling at confirm time catches interactions that landed after
|
|
1406
|
-
// phase 1 (e.g. a delayed control-token hit) — the verdict is bound
|
|
1407
|
-
// to this fresh observation, not the stored one.
|
|
1408
|
-
const { verification } = await verifyOobDifferential({
|
|
1409
|
-
targetToken: bundle.oobTokens.targetToken,
|
|
1410
|
-
controlToken: bundle.oobTokens.controlToken,
|
|
1411
|
-
});
|
|
1412
|
-
const oobPass =
|
|
1413
|
-
verification.targetHits > 0 &&
|
|
1414
|
-
verification.controlHits === 0 &&
|
|
1415
|
-
verification.sourceSeparated === true;
|
|
1416
|
-
replay = {
|
|
1417
|
-
attempted: true,
|
|
1418
|
-
pass: oobPass,
|
|
1419
|
-
target: {
|
|
1420
|
-
attempted: true,
|
|
1421
|
-
matched: verification.targetHits > 0,
|
|
1422
|
-
url: bundle.targetRuns[0].evidence.verify.url,
|
|
1423
|
-
note: verification.note,
|
|
1424
|
-
},
|
|
1425
|
-
control: {
|
|
1426
|
-
attempted: true,
|
|
1427
|
-
matched: verification.controlHits > 0,
|
|
1428
|
-
url: bundle.targetRuns[0].evidence.verify.url,
|
|
1429
|
-
note: `${verification.controlHits} control-token interaction(s)`,
|
|
1430
|
-
},
|
|
1431
|
-
differential:
|
|
1432
|
-
verification.targetHits > 0
|
|
1433
|
-
? verification.controlHits === 0
|
|
1434
|
-
? "target_only"
|
|
1435
|
-
: "both"
|
|
1436
|
-
: "neither",
|
|
1437
|
-
note: `harness OOB re-poll: ${verification.note}`,
|
|
1438
|
-
};
|
|
1439
|
-
} else if (bundle.callbackVerified?.attempted) {
|
|
1440
|
-
throw new Error(
|
|
1441
|
-
"OOB bundle lacks its provisioned tokens (pre-token-storage ledger) — re-run PromoteFinding for a fresh bundle",
|
|
1442
|
-
);
|
|
1443
|
-
} else {
|
|
1444
|
-
if (!bundle.controlTarget) {
|
|
1445
|
-
throw new Error("inter-host confirmation requires a control target");
|
|
1446
|
-
}
|
|
1447
|
-
const controlAuthorizationError = controlTargetAuthorizationError(bundle.controlTarget);
|
|
1448
|
-
if (controlAuthorizationError) {
|
|
1449
|
-
throw new Error(`CONTROL AUTHORIZATION FAILED: ${controlAuthorizationError}`);
|
|
1450
|
-
}
|
|
1451
|
-
replay = await replayDifferential(
|
|
1452
|
-
bundle.targetRuns[0].evidence,
|
|
1453
|
-
caseTargetForReplay,
|
|
1454
|
-
bundle.controlTarget,
|
|
1455
|
-
{ allowPrivate },
|
|
1456
|
-
);
|
|
1457
|
-
}
|
|
1458
|
-
phase2Verification = {
|
|
1459
|
-
at: new Date().toISOString(),
|
|
1460
|
-
result: replay,
|
|
1461
|
-
};
|
|
1462
|
-
}
|
|
1463
|
-
const result = applyConfirmationResult(caseId, parsedVerdict.verdict, phase2Verification, {
|
|
1141
|
+
const result = applyConfirmationResult(caseId, parsedVerdict.verdict, {
|
|
1464
1142
|
startedAsSubagent: isSubagentProcess(),
|
|
1465
1143
|
});
|
|
1466
1144
|
const record = result.record;
|