@xaccefy/pi-casefile 0.9.2 → 0.9.3
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/package.json +2 -2
- package/src/evidence.ts +21 -0
- package/src/harness-verify.ts +136 -31
- package/src/index.ts +140 -68
- package/src/ledger.ts +140 -23
- package/src/workflow.ts +16 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xaccefy/pi-casefile",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "Offensive security case tracker for Pi Agent — bug bounties, CTFs, security audits",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
],
|
|
50
50
|
"main": "src/index.ts",
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@xaccefy/pi-shared": "0.9.
|
|
52
|
+
"@xaccefy/pi-shared": "0.9.3",
|
|
53
53
|
"undici": "^8.9.0"
|
|
54
54
|
},
|
|
55
55
|
"pi": {
|
package/src/evidence.ts
CHANGED
|
@@ -45,6 +45,14 @@ export type PoCEvidence = {
|
|
|
45
45
|
expect: VerifyExpect;
|
|
46
46
|
/** Optional stronger causality dimension, independent of the authored predicate. */
|
|
47
47
|
canary?: VerifyCanary;
|
|
48
|
+
/**
|
|
49
|
+
* Differential shape. "inter_host" (default) = same request to target vs a
|
|
50
|
+
* distinct patched control host (body-carried proof). "intra_target" = attack
|
|
51
|
+
* request vs a legitimate same-host `baseline` request (access-control /
|
|
52
|
+
* business-logic classes, where the discriminating variable is identity or a
|
|
53
|
+
* parameter, not the host) — requires `baseline`.
|
|
54
|
+
*/
|
|
55
|
+
mode?: "inter_host" | "intra_target";
|
|
48
56
|
};
|
|
49
57
|
/** What the script itself saw — corroboration only, never proof. */
|
|
50
58
|
observations: string[];
|
|
@@ -253,6 +261,19 @@ export function parsePoCEvidence(
|
|
|
253
261
|
};
|
|
254
262
|
}
|
|
255
263
|
}
|
|
264
|
+
if (verify.mode !== undefined && verify.mode !== "inter_host" && verify.mode !== "intra_target") {
|
|
265
|
+
return {
|
|
266
|
+
ok: false,
|
|
267
|
+
error: 'evidence.json verify.mode must be "inter_host" or "intra_target"',
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (verify.mode === "intra_target" && !isRecord(raw.baseline)) {
|
|
271
|
+
return {
|
|
272
|
+
ok: false,
|
|
273
|
+
error:
|
|
274
|
+
"evidence.json verify.mode intra_target requires baseline — a legitimate same-host request whose response must NOT satisfy the attack predicate",
|
|
275
|
+
};
|
|
276
|
+
}
|
|
256
277
|
const expect = verify.expect;
|
|
257
278
|
if (!isRecord(expect))
|
|
258
279
|
return { ok: false, error: "evidence.json verify.expect must be an object" };
|
package/src/harness-verify.ts
CHANGED
|
@@ -543,6 +543,29 @@ async function replayRequest(
|
|
|
543
543
|
return { attempted: true, url: observedUrl(), note: "unreachable redirect state" };
|
|
544
544
|
}
|
|
545
545
|
|
|
546
|
+
/**
|
|
547
|
+
* Two requests are "the same" when method, URL, header set, and body all match.
|
|
548
|
+
* An intra-target differential whose attack and baseline are identical proves
|
|
549
|
+
* nothing — the discriminating variable must actually differ.
|
|
550
|
+
*/
|
|
551
|
+
export function sameRequest(
|
|
552
|
+
a: { method: string; url: string; headers?: Record<string, string>; body?: string },
|
|
553
|
+
b: { method: string; url: string; headers?: Record<string, string>; body?: string },
|
|
554
|
+
): boolean {
|
|
555
|
+
const norm = (h?: Record<string, string>) =>
|
|
556
|
+
JSON.stringify(
|
|
557
|
+
Object.entries(h ?? {})
|
|
558
|
+
.map(([k, v]) => [k.toLowerCase(), v] as const)
|
|
559
|
+
.sort(([x], [y]) => (x < y ? -1 : x > y ? 1 : 0)),
|
|
560
|
+
);
|
|
561
|
+
return (
|
|
562
|
+
a.method.toUpperCase() === b.method.toUpperCase() &&
|
|
563
|
+
a.url === b.url &&
|
|
564
|
+
(a.body ?? "") === (b.body ?? "") &&
|
|
565
|
+
norm(a.headers) === norm(b.headers)
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
|
|
546
569
|
function injectCanary(
|
|
547
570
|
verify: PoCEvidence["verify"],
|
|
548
571
|
token: string | undefined,
|
|
@@ -612,6 +635,49 @@ export async function replayVerify(
|
|
|
612
635
|
};
|
|
613
636
|
}
|
|
614
637
|
|
|
638
|
+
/**
|
|
639
|
+
* Combine the two observations into the differential verdict. Shared by the
|
|
640
|
+
* inter-host (target vs control host) and intra-target (attack vs same-host
|
|
641
|
+
* baseline) replays — only the note labels differ.
|
|
642
|
+
*/
|
|
643
|
+
function judgeDifferential(
|
|
644
|
+
target: HarnessResponseObservation,
|
|
645
|
+
control: HarnessResponseObservation,
|
|
646
|
+
token: string | undefined,
|
|
647
|
+
label: { kind: string; a: string; b: string },
|
|
648
|
+
): HarnessVerifyResult {
|
|
649
|
+
const attempted = target.attempted && control.attempted;
|
|
650
|
+
const conclusive = target.matched !== undefined && control.matched !== undefined;
|
|
651
|
+
const differential = conclusive
|
|
652
|
+
? target.matched === true
|
|
653
|
+
? control.matched === true
|
|
654
|
+
? "both"
|
|
655
|
+
: "target_only"
|
|
656
|
+
: control.matched === true
|
|
657
|
+
? "control_only"
|
|
658
|
+
: "neither"
|
|
659
|
+
: undefined;
|
|
660
|
+
const canary = canaryResult(token, target, control);
|
|
661
|
+
const pass =
|
|
662
|
+
attempted &&
|
|
663
|
+
conclusive &&
|
|
664
|
+
differential === "target_only" &&
|
|
665
|
+
(canary === undefined || canary.pass === true);
|
|
666
|
+
return {
|
|
667
|
+
attempted,
|
|
668
|
+
pass,
|
|
669
|
+
status: target.status,
|
|
670
|
+
target,
|
|
671
|
+
control,
|
|
672
|
+
differential,
|
|
673
|
+
canary,
|
|
674
|
+
proofStrength: canary?.pass ? "canary_differential" : "predicate_differential",
|
|
675
|
+
note:
|
|
676
|
+
`harness ${label.kind} ${differential ?? "inconclusive"}: ${label.a} (${target.note}); ` +
|
|
677
|
+
`${label.b} (${control.note})${canary ? `; ${canary.note}` : ""}`,
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
|
|
615
681
|
/**
|
|
616
682
|
* Execute one harness-owned request template against both the case target and
|
|
617
683
|
* a distinct control origin. The PoC cannot weaken the control request: the
|
|
@@ -658,36 +724,75 @@ export async function replayDifferential(
|
|
|
658
724
|
token,
|
|
659
725
|
opts,
|
|
660
726
|
);
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
727
|
+
return judgeDifferential(target, control, token, {
|
|
728
|
+
kind: "differential",
|
|
729
|
+
a: "target",
|
|
730
|
+
b: "control",
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Same-host differential (Tier 2, intra-target). For access-control and
|
|
736
|
+
* business-logic classes the discriminating variable is the attacker's
|
|
737
|
+
* identity or a request parameter, NOT the host — so the sound baseline is a
|
|
738
|
+
* legitimate request to the SAME target, not the same request to another host.
|
|
739
|
+
* The harness sends the attack request and the model-declared `evidence.baseline`
|
|
740
|
+
* request to the case target, applies the attack's `verify.expect` predicates to
|
|
741
|
+
* BOTH responses, and passes only when the proof appears on the attack response
|
|
742
|
+
* and is absent from the baseline (`target_only`, where "target" = attack and
|
|
743
|
+
* "control" = baseline). The baseline is bound to the case target so it cannot
|
|
744
|
+
* be redirected to a weaker origin, and it must differ from the attack request.
|
|
745
|
+
*/
|
|
746
|
+
export async function replayIntraTarget(
|
|
747
|
+
evidence: PoCEvidence,
|
|
748
|
+
caseTarget: string,
|
|
749
|
+
opts?: ReplayOptions,
|
|
750
|
+
): Promise<HarnessVerifyResult> {
|
|
751
|
+
const baseline = evidence.baseline;
|
|
752
|
+
if (!baseline) {
|
|
753
|
+
return {
|
|
754
|
+
attempted: false,
|
|
755
|
+
pass: false,
|
|
756
|
+
note: "intra-target differential requires evidence.baseline (a legitimate same-host request)",
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
const attackBinding = verifyUrlBindingError(evidence.verify.url, caseTarget);
|
|
760
|
+
if (attackBinding) {
|
|
761
|
+
return { attempted: false, pass: false, note: `attack binding failed: ${attackBinding}` };
|
|
762
|
+
}
|
|
763
|
+
const baselineBinding = verifyUrlBindingError(baseline.url, caseTarget);
|
|
764
|
+
if (baselineBinding) {
|
|
765
|
+
return { attempted: false, pass: false, note: `baseline binding failed: ${baselineBinding}` };
|
|
766
|
+
}
|
|
767
|
+
if (sameRequest(evidence.verify, baseline)) {
|
|
768
|
+
return {
|
|
769
|
+
attempted: false,
|
|
770
|
+
pass: false,
|
|
771
|
+
note: "attack and baseline requests are identical — an intra-target differential must vary identity or a parameter",
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const token = evidence.verify.canary
|
|
776
|
+
? `poc_canary_${randomBytes(24).toString("hex")}`
|
|
673
777
|
: undefined;
|
|
674
|
-
const
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
778
|
+
const attackVerify = injectCanary(evidence.verify, token);
|
|
779
|
+
const attack = await replayRequest(attackVerify, attackVerify.expect, token, opts);
|
|
780
|
+
// The baseline carries the attack's predicates: the proof must be ABSENT here.
|
|
781
|
+
const baselineVerify = injectCanary(
|
|
782
|
+
{
|
|
783
|
+
...evidence.verify,
|
|
784
|
+
method: baseline.method,
|
|
785
|
+
url: baseline.url,
|
|
786
|
+
headers: baseline.headers,
|
|
787
|
+
body: baseline.body,
|
|
788
|
+
},
|
|
789
|
+
token,
|
|
790
|
+
);
|
|
791
|
+
const base = await replayRequest(baselineVerify, attackVerify.expect, token, opts);
|
|
792
|
+
|
|
793
|
+
return judgeDifferential(attack, base, token, {
|
|
794
|
+
kind: "intra-target",
|
|
795
|
+
a: "attack",
|
|
796
|
+
b: "baseline",
|
|
797
|
+
});
|
|
693
798
|
}
|
package/src/index.ts
CHANGED
|
@@ -19,7 +19,12 @@ import {
|
|
|
19
19
|
SEVERITY_MATCH_VALUES,
|
|
20
20
|
validateMainAgentVerdict,
|
|
21
21
|
} from "./evidence.ts";
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
controlTargetAuthorizationError,
|
|
24
|
+
type HarnessVerifyResult,
|
|
25
|
+
replayDifferential,
|
|
26
|
+
replayIntraTarget,
|
|
27
|
+
} from "./harness-verify.ts";
|
|
23
28
|
import {
|
|
24
29
|
addCaseResult,
|
|
25
30
|
addEvidenceItemResult,
|
|
@@ -198,11 +203,20 @@ const PromoteSchema = Type.Object(
|
|
|
198
203
|
"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.",
|
|
199
204
|
}),
|
|
200
205
|
),
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
+
mode: Type.Optional(
|
|
207
|
+
Type.String({
|
|
208
|
+
enum: ["inter_host", "intra_target"],
|
|
209
|
+
description:
|
|
210
|
+
"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.",
|
|
211
|
+
}),
|
|
212
|
+
),
|
|
213
|
+
control_target: Type.Optional(
|
|
214
|
+
Type.String({
|
|
215
|
+
minLength: 1,
|
|
216
|
+
description:
|
|
217
|
+
"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'.",
|
|
218
|
+
}),
|
|
219
|
+
),
|
|
206
220
|
local: Type.Optional(
|
|
207
221
|
Type.Boolean({
|
|
208
222
|
description:
|
|
@@ -1181,22 +1195,30 @@ export default function casefileExtension(pi: ExtensionAPI) {
|
|
|
1181
1195
|
const pocPath = (params.poc_path as string | undefined)?.trim() ?? "";
|
|
1182
1196
|
const controlPath = (params.control_path as string | undefined)?.trim() || pocPath;
|
|
1183
1197
|
const controlTarget = (params.control_target as string | undefined)?.trim() ?? "";
|
|
1198
|
+
const mode: "inter_host" | "intra_target" =
|
|
1199
|
+
(params.mode as string | undefined) === "intra_target" ? "intra_target" : "inter_host";
|
|
1200
|
+
const isIntra = mode === "intra_target";
|
|
1184
1201
|
if (!pocPath) {
|
|
1185
1202
|
return fail("poc_path is REQUIRED: absolute path to the PoC script run by the harness.", {
|
|
1186
1203
|
missingPocPath: true,
|
|
1187
1204
|
});
|
|
1188
1205
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
)
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
)
|
|
1206
|
+
// Control-target preconditions apply only to the inter-host differential.
|
|
1207
|
+
// Intra-target proves target-dependence with a same-host baseline request
|
|
1208
|
+
// carried in the evidence, so it needs no control target or control script.
|
|
1209
|
+
if (!isIntra) {
|
|
1210
|
+
if (!controlTarget) {
|
|
1211
|
+
return fail(
|
|
1212
|
+
"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.",
|
|
1213
|
+
{ missingControlTarget: true },
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
if (controlTarget === current.target) {
|
|
1217
|
+
return fail(
|
|
1218
|
+
"control_target must differ from the case target; a control run against the vulnerable target proves nothing.",
|
|
1219
|
+
{ controlTargetEqualsCaseTarget: true },
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1200
1222
|
}
|
|
1201
1223
|
if (params.local === true && process.env.PI_POC_ALLOW_NETWORK !== "1") {
|
|
1202
1224
|
return fail(
|
|
@@ -1204,33 +1226,43 @@ export default function casefileExtension(pi: ExtensionAPI) {
|
|
|
1204
1226
|
{ networkNotAuthorized: true },
|
|
1205
1227
|
);
|
|
1206
1228
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1229
|
+
if (!isIntra) {
|
|
1230
|
+
const controlAuthorization = controlTargetAuthorizationError(controlTarget);
|
|
1231
|
+
if (controlAuthorization) {
|
|
1232
|
+
return fail(
|
|
1233
|
+
`CONTROL AUTHORIZATION FAILED: ${controlAuthorization}. ` +
|
|
1234
|
+
"The operator must set PI_POC_CONTROL_TARGETS to the exact approved control host/origin before this control can anchor confirmation.",
|
|
1235
|
+
{ controlNotAuthorized: true },
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1214
1238
|
}
|
|
1215
1239
|
|
|
1216
|
-
//
|
|
1217
|
-
//
|
|
1240
|
+
// Anti-cheat: hash the PoC (always) and, for inter-host, require the
|
|
1241
|
+
// control script to be the SAME bytes (differing only via harness env).
|
|
1218
1242
|
let pocHash: string | undefined;
|
|
1219
|
-
let controlHash: string | undefined;
|
|
1220
1243
|
try {
|
|
1221
1244
|
pocHash = createHash("sha256").update(readFileSync(pocPath)).digest("hex");
|
|
1222
|
-
controlHash = createHash("sha256").update(readFileSync(controlPath)).digest("hex");
|
|
1223
1245
|
} catch (e) {
|
|
1224
|
-
return fail(
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
);
|
|
1246
|
+
return fail(`Cannot read PoC script: ${(e as Error).message}`, {
|
|
1247
|
+
sameFileCheckFailed: true,
|
|
1248
|
+
});
|
|
1228
1249
|
}
|
|
1229
|
-
if (
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
)
|
|
1250
|
+
if (!isIntra) {
|
|
1251
|
+
let controlHash: string | undefined;
|
|
1252
|
+
try {
|
|
1253
|
+
controlHash = createHash("sha256").update(readFileSync(controlPath)).digest("hex");
|
|
1254
|
+
} catch (e) {
|
|
1255
|
+
return fail(
|
|
1256
|
+
`Cannot read control script for the same-file check: ${(e as Error).message}`,
|
|
1257
|
+
{ sameFileCheckFailed: true },
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
if (pocHash !== controlHash) {
|
|
1261
|
+
return fail(
|
|
1262
|
+
"CONTROL CHECK FAILED: control_path must be the SAME script as poc_path (sha256 mismatch). Case remains investigating.",
|
|
1263
|
+
{ controlHashMismatch: true },
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1234
1266
|
}
|
|
1235
1267
|
|
|
1236
1268
|
// ── OOB callback (Tier 1, opt-in for blind classes) ──
|
|
@@ -1251,12 +1283,11 @@ export default function casefileExtension(pi: ExtensionAPI) {
|
|
|
1251
1283
|
});
|
|
1252
1284
|
|
|
1253
1285
|
const caseTarget = current.target ?? "";
|
|
1254
|
-
// Determinism: TWO target runs
|
|
1255
|
-
//
|
|
1256
|
-
//
|
|
1286
|
+
// Determinism: TWO target runs. Exit 0 is run integrity only; nonce-bound
|
|
1287
|
+
// body evidence plus the harness-owned differential replay (inter-host
|
|
1288
|
+
// control, or intra-target same-host baseline) form the machine gate.
|
|
1257
1289
|
const run1 = runPoc(pocPath, runOptions("poc", caseTarget));
|
|
1258
1290
|
const run2 = runPoc(pocPath, runOptions("poc", caseTarget));
|
|
1259
|
-
const controlRun = runPoc(controlPath, runOptions("control", controlTarget));
|
|
1260
1291
|
|
|
1261
1292
|
const evidenceRun = (
|
|
1262
1293
|
r: PocRun,
|
|
@@ -1304,28 +1335,55 @@ export default function casefileExtension(pi: ExtensionAPI) {
|
|
|
1304
1335
|
evidenceRun(run1, "poc", caseTarget),
|
|
1305
1336
|
evidenceRun(run2, "poc", caseTarget),
|
|
1306
1337
|
];
|
|
1307
|
-
const control = evidenceRun(controlRun, "control", controlTarget);
|
|
1308
|
-
|
|
1309
|
-
// Tier 2 (docs/poc-trust-model.md): the harness executes the SAME
|
|
1310
|
-
// request template against target and operator-approved control, applying
|
|
1311
|
-
// the target's predicates to both. DNS is pinned at connect time.
|
|
1312
|
-
const harnessVerified = await replayDifferential(
|
|
1313
|
-
targetRuns[0].evidence,
|
|
1314
|
-
caseTarget,
|
|
1315
|
-
controlTarget,
|
|
1316
|
-
{ allowPrivate: process.env.PI_POC_ALLOW_PRIVATE_REPLAY === "1" },
|
|
1317
|
-
);
|
|
1318
1338
|
|
|
1339
|
+
const allowPrivateReplay = process.env.PI_POC_ALLOW_PRIVATE_REPLAY === "1";
|
|
1340
|
+
let harnessVerified: HarnessVerifyResult;
|
|
1341
|
+
let controlRun: PocEvidenceRun | undefined;
|
|
1342
|
+
if (isIntra) {
|
|
1343
|
+
// Intra-target: prove target-dependence with the evidence's same-host
|
|
1344
|
+
// baseline request — no separate control run. The harness sends attack +
|
|
1345
|
+
// baseline to the case target and requires the proof on attack only.
|
|
1346
|
+
const ev0 = targetRuns[0].evidence;
|
|
1347
|
+
if (ev0.verify.mode !== "intra_target") {
|
|
1348
|
+
return fail(
|
|
1349
|
+
"INTRA-TARGET FAILED: the PoC's evidence.json must set verify.mode='intra_target' when promoting in intra-target mode.",
|
|
1350
|
+
{ intraModeMismatch: true },
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
if (!ev0.baseline) {
|
|
1354
|
+
return fail(
|
|
1355
|
+
"INTRA-TARGET FAILED: evidence.json must include a baseline — a legitimate same-host request whose response must NOT satisfy the attack predicate.",
|
|
1356
|
+
{ intraBaselineMissing: true },
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
1359
|
+
harnessVerified = await replayIntraTarget(ev0, caseTarget, {
|
|
1360
|
+
allowPrivate: allowPrivateReplay,
|
|
1361
|
+
});
|
|
1362
|
+
} else {
|
|
1363
|
+
// Inter-host (Tier 2): the harness executes the SAME request template
|
|
1364
|
+
// against target and operator-approved control, applying the target's
|
|
1365
|
+
// predicates to both. DNS is pinned at connect time.
|
|
1366
|
+
controlRun = evidenceRun(
|
|
1367
|
+
runPoc(controlPath, runOptions("control", controlTarget)),
|
|
1368
|
+
"control",
|
|
1369
|
+
controlTarget,
|
|
1370
|
+
);
|
|
1371
|
+
harnessVerified = await replayDifferential(
|
|
1372
|
+
targetRuns[0].evidence,
|
|
1373
|
+
caseTarget,
|
|
1374
|
+
controlTarget,
|
|
1375
|
+
{ allowPrivate: allowPrivateReplay },
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1319
1378
|
const bundle: PendingConfirmation = {
|
|
1320
1379
|
caseId,
|
|
1321
1380
|
ranAt: new Date().toISOString(),
|
|
1322
1381
|
pocPath,
|
|
1323
1382
|
pocSha256: pocHash,
|
|
1324
|
-
|
|
1325
|
-
controlTarget,
|
|
1383
|
+
mode,
|
|
1326
1384
|
targetRuns,
|
|
1327
|
-
controlRun: control,
|
|
1328
1385
|
harnessVerified,
|
|
1386
|
+
...(isIntra ? {} : { controlPath, controlTarget, controlRun }),
|
|
1329
1387
|
};
|
|
1330
1388
|
|
|
1331
1389
|
let record: CaseRecord;
|
|
@@ -1343,11 +1401,11 @@ export default function casefileExtension(pi: ExtensionAPI) {
|
|
|
1343
1401
|
type: "text",
|
|
1344
1402
|
text:
|
|
1345
1403
|
`Phase 1 complete — evidence bundle recorded on ${caseId} (expires in 1h).\n` +
|
|
1346
|
-
`Target runs: 2, Control run: 1 — all with validated nonce-bound evidence.json.\n` +
|
|
1404
|
+
`Mode: ${mode}. ${isIntra ? "Target runs: 2, same-host baseline differential" : "Target runs: 2, Control run: 1"} — all with validated nonce-bound evidence.json.\n` +
|
|
1347
1405
|
`Evidence sha256: ${targetRuns[0].evidenceSha256}\n` +
|
|
1348
1406
|
`PoC script sha256 (at run time): ${pocHash}\n` +
|
|
1349
1407
|
`Harness verify replay: ${harnessVerified.attempted ? (harnessVerified.pass ? `PASS (status ${harnessVerified.status})` : `FAILED — ${harnessVerified.note}`) : harnessVerified.note}\n` +
|
|
1350
|
-
`\nMAIN-AGENT REVIEW REQUIRED (do not delegate): inspect case ${caseId}, PoC ${pocPath}, 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 target/control replay; NOT_CONFIRMED keeps the case investigating.`,
|
|
1408
|
+
`\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.`,
|
|
1351
1409
|
},
|
|
1352
1410
|
],
|
|
1353
1411
|
details: {
|
|
@@ -1355,9 +1413,10 @@ export default function casefileExtension(pi: ExtensionAPI) {
|
|
|
1355
1413
|
bundle: {
|
|
1356
1414
|
caseId,
|
|
1357
1415
|
ranAt: bundle.ranAt,
|
|
1416
|
+
mode,
|
|
1358
1417
|
pocPath,
|
|
1359
|
-
controlPath,
|
|
1360
|
-
controlTarget,
|
|
1418
|
+
controlPath: isIntra ? undefined : controlPath,
|
|
1419
|
+
controlTarget: isIntra ? undefined : controlTarget,
|
|
1361
1420
|
pocSha256: pocHash,
|
|
1362
1421
|
evidenceSha256: targetRuns[0].evidenceSha256,
|
|
1363
1422
|
harnessVerified,
|
|
@@ -1426,16 +1485,29 @@ export default function casefileExtension(pi: ExtensionAPI) {
|
|
|
1426
1485
|
if (!bundle) {
|
|
1427
1486
|
throw new Error("No pending confirmation on this case — run PromoteFinding first");
|
|
1428
1487
|
}
|
|
1429
|
-
const
|
|
1430
|
-
|
|
1431
|
-
|
|
1488
|
+
const allowPrivate = process.env.PI_POC_ALLOW_PRIVATE_REPLAY === "1";
|
|
1489
|
+
const caseTargetForReplay = current.target ?? bundle.targetRuns[0].target;
|
|
1490
|
+
let replay: HarnessVerifyResult;
|
|
1491
|
+
if (bundle.mode === "intra_target") {
|
|
1492
|
+
// Same-host attack-vs-baseline replay; no control target to authorize.
|
|
1493
|
+
replay = await replayIntraTarget(bundle.targetRuns[0].evidence, caseTargetForReplay, {
|
|
1494
|
+
allowPrivate,
|
|
1495
|
+
});
|
|
1496
|
+
} else {
|
|
1497
|
+
if (!bundle.controlTarget) {
|
|
1498
|
+
throw new Error("inter-host confirmation requires a control target");
|
|
1499
|
+
}
|
|
1500
|
+
const controlAuthorizationError = controlTargetAuthorizationError(bundle.controlTarget);
|
|
1501
|
+
if (controlAuthorizationError) {
|
|
1502
|
+
throw new Error(`CONTROL AUTHORIZATION FAILED: ${controlAuthorizationError}`);
|
|
1503
|
+
}
|
|
1504
|
+
replay = await replayDifferential(
|
|
1505
|
+
bundle.targetRuns[0].evidence,
|
|
1506
|
+
caseTargetForReplay,
|
|
1507
|
+
bundle.controlTarget,
|
|
1508
|
+
{ allowPrivate },
|
|
1509
|
+
);
|
|
1432
1510
|
}
|
|
1433
|
-
const replay = await replayDifferential(
|
|
1434
|
-
bundle.targetRuns[0].evidence,
|
|
1435
|
-
current.target ?? bundle.targetRuns[0].target,
|
|
1436
|
-
bundle.controlTarget,
|
|
1437
|
-
{ allowPrivate: process.env.PI_POC_ALLOW_PRIVATE_REPLAY === "1" },
|
|
1438
|
-
);
|
|
1439
1511
|
phase2Verification = {
|
|
1440
1512
|
at: new Date().toISOString(),
|
|
1441
1513
|
result: replay,
|
package/src/ledger.ts
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
parsePoCEvidence,
|
|
32
32
|
validateMainAgentVerdict,
|
|
33
33
|
} from "./evidence.ts";
|
|
34
|
-
import { type HarnessVerifyResult, verifyUrlBindingError } from "./harness-verify.ts";
|
|
34
|
+
import { type HarnessVerifyResult, sameRequest, verifyUrlBindingError } from "./harness-verify.ts";
|
|
35
35
|
import {
|
|
36
36
|
assertSafeRegularFile,
|
|
37
37
|
ensureSafeStateDirectory,
|
|
@@ -328,10 +328,21 @@ export type PendingConfirmation = {
|
|
|
328
328
|
pocPath: string;
|
|
329
329
|
/** SHA-256 of the PoC script AT RUN TIME — re-hashed at confirm to catch edits. */
|
|
330
330
|
pocSha256: string;
|
|
331
|
-
|
|
332
|
-
|
|
331
|
+
/**
|
|
332
|
+
* Differential shape. Absent/"inter_host" (default) = same request to target
|
|
333
|
+
* vs a distinct patched control host, proven by a separate control run +
|
|
334
|
+
* `replayDifferential`. "intra_target" = attack vs a legitimate same-host
|
|
335
|
+
* `baseline` request inside each run's evidence, proven by `replayIntraTarget`
|
|
336
|
+
* — no control run or control target (access-control / business-logic classes).
|
|
337
|
+
*/
|
|
338
|
+
mode?: "inter_host" | "intra_target";
|
|
339
|
+
/** inter_host only. */
|
|
340
|
+
controlPath?: string;
|
|
341
|
+
/** inter_host only. */
|
|
342
|
+
controlTarget?: string;
|
|
333
343
|
targetRuns: [PocEvidenceRun, PocEvidenceRun];
|
|
334
|
-
|
|
344
|
+
/** inter_host only — the same PoC run against the control target. */
|
|
345
|
+
controlRun?: PocEvidenceRun;
|
|
335
346
|
/** Harness's own replay of evidence.verify (public targets). Absent = legacy bundle. */
|
|
336
347
|
harnessVerified?: HarnessVerifyResult;
|
|
337
348
|
/** Harness-owned OOB listener log for the run (opt-in blind classes). */
|
|
@@ -2064,13 +2075,19 @@ function validateRunEvidence(run: PocEvidenceRun, label: string): void {
|
|
|
2064
2075
|
}
|
|
2065
2076
|
|
|
2066
2077
|
/** Determinism + differential on normalized evidence (nonce/observations stripped). */
|
|
2067
|
-
function assertEvidenceDifferential(bundle: PendingConfirmation): void {
|
|
2078
|
+
function assertEvidenceDifferential(bundle: PendingConfirmation, isIntra = false): void {
|
|
2068
2079
|
const [r1, r2] = bundle.targetRuns;
|
|
2069
2080
|
if (normalizeEvidence(r1.evidence) !== normalizeEvidence(r2.evidence)) {
|
|
2070
2081
|
throw new Error(
|
|
2071
2082
|
"Target runs produced inconsistent evidence — the exploit did not reproduce deterministically",
|
|
2072
2083
|
);
|
|
2073
2084
|
}
|
|
2085
|
+
// Intra-target target-dependence is proven by the harness attack-vs-baseline
|
|
2086
|
+
// replay (same host), not by comparing a target run to a separate control run.
|
|
2087
|
+
if (isIntra) return;
|
|
2088
|
+
if (!bundle.controlRun) {
|
|
2089
|
+
throw new Error("inter-host confirmation requires a control run");
|
|
2090
|
+
}
|
|
2074
2091
|
if (normalizeEvidence(r1.evidence) === normalizeEvidence(bundle.controlRun.evidence)) {
|
|
2075
2092
|
throw new Error(
|
|
2076
2093
|
"Control run produced identical evidence to the target — the claimed impact is not target-dependent",
|
|
@@ -2143,6 +2160,7 @@ function assertHarnessCanary(
|
|
|
2143
2160
|
function assertMainAgentVerification(
|
|
2144
2161
|
bundle: PendingConfirmation,
|
|
2145
2162
|
verification: MainAgentVerification | undefined,
|
|
2163
|
+
isIntra = false,
|
|
2146
2164
|
): asserts verification is MainAgentVerification {
|
|
2147
2165
|
if (!verification) {
|
|
2148
2166
|
throw new Error(
|
|
@@ -2179,8 +2197,16 @@ function assertMainAgentVerification(
|
|
|
2179
2197
|
if (!targetUrl || verifyUrlBindingError(targetUrl, targetIdentity)) {
|
|
2180
2198
|
throw new Error("MAIN-AGENT REPLAY FAILED: target transcript is not bound to the case target");
|
|
2181
2199
|
}
|
|
2182
|
-
|
|
2183
|
-
|
|
2200
|
+
// Intra-target: the "control" transcript is the legitimate baseline request,
|
|
2201
|
+
// which is bound to the SAME case target. Inter-host: it is bound to the
|
|
2202
|
+
// distinct control target.
|
|
2203
|
+
const controlBindTarget = isIntra ? targetIdentity : bundle.controlTarget;
|
|
2204
|
+
if (!controlUrl || !controlBindTarget || verifyUrlBindingError(controlUrl, controlBindTarget)) {
|
|
2205
|
+
throw new Error(
|
|
2206
|
+
isIntra
|
|
2207
|
+
? "MAIN-AGENT REPLAY FAILED: baseline transcript is not bound to the case target"
|
|
2208
|
+
: "MAIN-AGENT REPLAY FAILED: control transcript is not bound to control_target",
|
|
2209
|
+
);
|
|
2184
2210
|
}
|
|
2185
2211
|
}
|
|
2186
2212
|
|
|
@@ -2230,6 +2256,75 @@ export function assertPromotable(id: string): CaseRecord {
|
|
|
2230
2256
|
return current;
|
|
2231
2257
|
}
|
|
2232
2258
|
|
|
2259
|
+
/**
|
|
2260
|
+
* Phase 1 (intra-target): validate a same-host attack-vs-baseline bundle. The
|
|
2261
|
+
* differential is proven by the harness replay (attack matched, baseline did
|
|
2262
|
+
* not, both against the case target), not by a separate control run — the
|
|
2263
|
+
* discriminating variable is the request's identity or a parameter, not the host.
|
|
2264
|
+
*/
|
|
2265
|
+
function validateIntraTargetBundle(
|
|
2266
|
+
current: CaseRecord,
|
|
2267
|
+
id: string,
|
|
2268
|
+
bundle: PendingConfirmation,
|
|
2269
|
+
): CaseRecord {
|
|
2270
|
+
if (bundle.targetRuns.length !== 2) {
|
|
2271
|
+
throw new Error("Intra-target confirmation requires two target runs");
|
|
2272
|
+
}
|
|
2273
|
+
if (bundle.controlRun || bundle.controlTarget) {
|
|
2274
|
+
throw new Error(
|
|
2275
|
+
"Intra-target confirmation must not carry a control run or control target — the baseline is a same-host request inside the evidence",
|
|
2276
|
+
);
|
|
2277
|
+
}
|
|
2278
|
+
const targetRunTarget = bundle.targetRuns[0]?.target;
|
|
2279
|
+
if (!targetRunTarget || bundle.targetRuns.some((r) => r.target !== targetRunTarget)) {
|
|
2280
|
+
throw new Error("Intra-target confirmation requires both runs against the same case target");
|
|
2281
|
+
}
|
|
2282
|
+
let pocHash: string | undefined;
|
|
2283
|
+
try {
|
|
2284
|
+
pocHash = createHash("sha256").update(readFileSync(bundle.pocPath)).digest("hex");
|
|
2285
|
+
} catch {
|
|
2286
|
+
pocHash = undefined;
|
|
2287
|
+
}
|
|
2288
|
+
if (!pocHash || (bundle.pocSha256 && bundle.pocSha256 !== pocHash)) {
|
|
2289
|
+
throw new Error("pocSha256 does not match the PoC file on disk");
|
|
2290
|
+
}
|
|
2291
|
+
for (const run of bundle.targetRuns) {
|
|
2292
|
+
validateRunEvidence(run, `${run.mode} run`);
|
|
2293
|
+
const ev = run.evidence;
|
|
2294
|
+
if (ev.verify.mode !== "intra_target") {
|
|
2295
|
+
throw new Error(
|
|
2296
|
+
"INTRA-TARGET FAILED: each run's evidence.verify.mode must be 'intra_target'",
|
|
2297
|
+
);
|
|
2298
|
+
}
|
|
2299
|
+
if (!ev.baseline) {
|
|
2300
|
+
throw new Error(
|
|
2301
|
+
"INTRA-TARGET FAILED: evidence.baseline (a legitimate same-host request) is required",
|
|
2302
|
+
);
|
|
2303
|
+
}
|
|
2304
|
+
const attackBinding = verifyUrlBindingError(ev.verify.url, targetRunTarget);
|
|
2305
|
+
if (attackBinding) throw new Error(`ATTACK BINDING FAILED: ${attackBinding}`);
|
|
2306
|
+
const baselineBinding = verifyUrlBindingError(ev.baseline.url, targetRunTarget);
|
|
2307
|
+
if (baselineBinding) throw new Error(`BASELINE BINDING FAILED: ${baselineBinding}`);
|
|
2308
|
+
if (ev.baseline && sameRequest(ev.verify, ev.baseline)) {
|
|
2309
|
+
throw new Error(
|
|
2310
|
+
"INTRA-TARGET FAILED: attack and baseline requests are identical — vary identity or a parameter",
|
|
2311
|
+
);
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
if (bundle.caseId !== id) throw new Error("Pending confirmation caseId mismatch");
|
|
2315
|
+
assertEvidenceDifferential(bundle, true);
|
|
2316
|
+
// Machine floor: attack matched, baseline did not, both against the case target.
|
|
2317
|
+
assertMachineConfirmation(bundle);
|
|
2318
|
+
assertHarnessCanary(
|
|
2319
|
+
bundle.harnessVerified,
|
|
2320
|
+
bundle.targetRuns[0].evidence.verify.canary !== undefined,
|
|
2321
|
+
"PHASE-1 CANARY FAILED",
|
|
2322
|
+
);
|
|
2323
|
+
const next = buildRecord({ pendingConfirmation: bundle }, current);
|
|
2324
|
+
validateCase(next);
|
|
2325
|
+
return next;
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2233
2328
|
/**
|
|
2234
2329
|
* Phase 1: record the harness-observed evidence bundle on the case. The whole
|
|
2235
2330
|
* contract is validated here — same-file control, nonce binding, run
|
|
@@ -2248,6 +2343,11 @@ export function storePendingConfirmation(id: string, bundle: PendingConfirmation
|
|
|
2248
2343
|
);
|
|
2249
2344
|
}
|
|
2250
2345
|
if (bundle.caseId !== id) throw new Error("Pending confirmation caseId mismatch");
|
|
2346
|
+
if (bundle.mode === "intra_target") {
|
|
2347
|
+
const next = validateIntraTargetBundle(current, id, bundle);
|
|
2348
|
+
upsertCase(db, next);
|
|
2349
|
+
return next;
|
|
2350
|
+
}
|
|
2251
2351
|
if (bundle.targetRuns.length !== 2 || !bundle.controlRun) {
|
|
2252
2352
|
throw new Error("Pending confirmation requires two target runs and one control run");
|
|
2253
2353
|
}
|
|
@@ -2423,10 +2523,14 @@ export function applyConfirmationResult(
|
|
|
2423
2523
|
|
|
2424
2524
|
// CONFIRMED — re-validate the whole bundle (defense in depth; the case may
|
|
2425
2525
|
// have been touched between phase 1 and the verdict).
|
|
2426
|
-
|
|
2526
|
+
const isIntra = bundle.mode === "intra_target";
|
|
2527
|
+
const allRuns = isIntra
|
|
2528
|
+
? [...bundle.targetRuns]
|
|
2529
|
+
: [...bundle.targetRuns, ...(bundle.controlRun ? [bundle.controlRun] : [])];
|
|
2530
|
+
for (const run of allRuns) {
|
|
2427
2531
|
validateRunEvidence(run, `${run.mode} run`);
|
|
2428
2532
|
}
|
|
2429
|
-
assertEvidenceDifferential(bundle);
|
|
2533
|
+
assertEvidenceDifferential(bundle, isIntra);
|
|
2430
2534
|
assertMachineConfirmation(bundle);
|
|
2431
2535
|
assertHarnessCanary(bundle.harnessVerified, canaryRequested, "PHASE-1 CANARY FAILED");
|
|
2432
2536
|
let pocHash: string | undefined;
|
|
@@ -2450,7 +2554,7 @@ export function applyConfirmationResult(
|
|
|
2450
2554
|
`(bundle target: ${targetRun.target}, case target: ${current.target ?? "(none)"}).`,
|
|
2451
2555
|
);
|
|
2452
2556
|
}
|
|
2453
|
-
if (current.target === bundle.controlTarget) {
|
|
2557
|
+
if (!isIntra && current.target === bundle.controlTarget) {
|
|
2454
2558
|
throw new Error(
|
|
2455
2559
|
"Case target now equals the control target — the claimed impact is not target-dependent; " +
|
|
2456
2560
|
"re-run PromoteFinding with a distinct control_target.",
|
|
@@ -2469,7 +2573,7 @@ export function applyConfirmationResult(
|
|
|
2469
2573
|
// Phase 1 proves the evidence floor. Phase 2 must freshly replay that same
|
|
2470
2574
|
// request inside the main agent's ConfirmFinding call; a caller-provided
|
|
2471
2575
|
// boolean is not accepted as proof of re-execution.
|
|
2472
|
-
assertMainAgentVerification(bundle, phase2Verification);
|
|
2576
|
+
assertMainAgentVerification(bundle, phase2Verification, isIntra);
|
|
2473
2577
|
|
|
2474
2578
|
const reproductionItem: EvidenceItem = {
|
|
2475
2579
|
id: `ev_${stableShortId(`${id}\nreproduction\n${targetRun.ranAt}`)}`,
|
|
@@ -2480,7 +2584,7 @@ export function applyConfirmationResult(
|
|
|
2480
2584
|
// exists, so the item stays artifact-backed and re-verifiable.
|
|
2481
2585
|
artifactPath: targetRun.evidencePath ? basename(targetRun.evidencePath) : "evidence.json",
|
|
2482
2586
|
sha256: targetRun.evidenceSha256,
|
|
2483
|
-
summary: `PoC evidence accepted (2 target runs + control; ${recorded.proofStrength}) — main agent semantic confirmation${verdict.model ? ` (${verdict.model})` : ""}`,
|
|
2587
|
+
summary: `PoC evidence accepted (2 target runs + ${isIntra ? "same-host baseline" : "control"}; ${recorded.proofStrength}) — main agent semantic confirmation${verdict.model ? ` (${verdict.model})` : ""}`,
|
|
2484
2588
|
createdAt: targetRun.ranAt,
|
|
2485
2589
|
};
|
|
2486
2590
|
|
|
@@ -2506,17 +2610,30 @@ export function applyConfirmationResult(
|
|
|
2506
2610
|
mode: "poc",
|
|
2507
2611
|
target: targetRun.target,
|
|
2508
2612
|
},
|
|
2509
|
-
controlVerified:
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2613
|
+
controlVerified:
|
|
2614
|
+
isIntra || !bundle.controlRun
|
|
2615
|
+
? {
|
|
2616
|
+
path: bundle.pocPath,
|
|
2617
|
+
exitCode: targetRun.exitCode,
|
|
2618
|
+
ranAt: targetRun.ranAt,
|
|
2619
|
+
output: `intra-target baseline (same host): ${bundle.harnessVerified?.control?.note ?? "baseline did not satisfy the attack predicate"}`,
|
|
2620
|
+
sandbox: targetRun.sandbox,
|
|
2621
|
+
completed: true,
|
|
2622
|
+
outputComplete: true,
|
|
2623
|
+
mode: "baseline",
|
|
2624
|
+
target: targetRun.target,
|
|
2625
|
+
}
|
|
2626
|
+
: {
|
|
2627
|
+
path: bundle.controlPath ?? bundle.pocPath,
|
|
2628
|
+
exitCode: bundle.controlRun.exitCode,
|
|
2629
|
+
ranAt: bundle.controlRun.ranAt,
|
|
2630
|
+
output: bundle.controlRun.output,
|
|
2631
|
+
sandbox: bundle.controlRun.sandbox,
|
|
2632
|
+
completed: true,
|
|
2633
|
+
outputComplete: true,
|
|
2634
|
+
mode: "control",
|
|
2635
|
+
target: bundle.controlRun.target,
|
|
2636
|
+
},
|
|
2520
2637
|
disconfirmation: verdict.disconfirmation_attempt,
|
|
2521
2638
|
confirmerVerdict: recorded,
|
|
2522
2639
|
pendingConfirmation: undefined,
|
package/src/workflow.ts
CHANGED
|
@@ -84,6 +84,15 @@ ${d.reference}
|
|
|
84
84
|
|
|
85
85
|
**Swarm delegation boundary:** only auditor (HUNT rounds), tracer (TRACE), skeptic (high-confidence challenge), and chain (CHAIN) run as subagents. You, the main coordinator, own RECON, VALIDATE/PoC writing, ConfirmFinding, patching, final reports, state decisions, and all orchestration.
|
|
86
86
|
|
|
87
|
+
## Dispatch Discipline (fewer calls, batched, verifier-in-the-loop)
|
|
88
|
+
|
|
89
|
+
This pipeline is SEQUENTIAL-dependent: each stage consumes the previous stage's output, so only HUNT genuinely fans out. Do NOT scatter one async subagent call per finding — that pays full coordination cost for no parallel payoff and turns you into a message router. Two rules keep it cheap:
|
|
90
|
+
|
|
91
|
+
1. **Two dispatch points, each ONE batched call.** (a) **HUNT** — one call whose \`runs.all\`/\`tasks\` carries ≤3 batched auditors (related classes grouped by surface/family). (b) **TRACE+SKEPTIC** — one call carrying a trace task per prioritized finding, plus a skeptic task for each \`confidence: high\` finding; batch the whole round in a single dispatch, never one dispatch per finding. RECON, VALIDATE/PoC, ConfirmFinding, CHAIN, and REPORT stay INLINE with you.
|
|
92
|
+
2. **Barrier, then submit the whole batch in one pass.** Let the batched call return ALL of its results, then \`PipelineSubmit\` each output back-to-back before choosing the next stage. Do not interleave fresh dispatches with the delivery of a prior batch. A crash / timeout / unparseable / schema-invalid result for one item is a RETRY for THAT item in the next batch — never a verdict, never a reason to drop the stage.
|
|
93
|
+
|
|
94
|
+
Every stage boundary is a \`PipelineSubmit\` gate (schema + pre-filter, in code): nothing advances on prose. This is the verifier-in-the-loop — the same principle the machine PoC gate applies at CONFIRMED, applied at every stage transition.
|
|
95
|
+
|
|
87
96
|
## Stage Machine (run in order — you are the coordinator)
|
|
88
97
|
|
|
89
98
|
RECON (you, inline) → **HUNT** (2-3 batched auditor subagents) → TRACE (tracer for prioritized findings) → SKEPTIC (bounded high-risk review) → VALIDATE (you, inline) → CHAIN (chain subagent) → REPORT (you, inline)
|
|
@@ -192,7 +201,12 @@ Weak: "Tried to disprove. Could not." — insufficient.
|
|
|
192
201
|
|
|
193
202
|
**Main-agent validation only:** do not dispatch validation. You write the smallest reliable PoC that demonstrates the **maximum reachable impact** of the vulnerability, set the case's poc/evidence/impact/severity/target fields, and run PromoteFinding yourself. "Smallest" means no fragile ceremony, mocks, or unrelated exploit steps — not a weaker impact demonstration. Do not stop at a benign marker if a stronger in-scope, non-destructive primitive is reachable (read/write, privilege change, account takeover path, data exposure, etc.). If the PoC fails, refine it yourself up to the local budget; if proof cannot meet the gate, kill or keep the case investigating with the exact blocker.
|
|
194
203
|
|
|
195
|
-
**PromoteFinding (phase 1) — evidence bundle, not markers.**
|
|
204
|
+
**PromoteFinding (phase 1) — evidence bundle, not markers.** Pick the differential \`mode\` that fits the class:
|
|
205
|
+
|
|
206
|
+
- **\`mode: "inter_host"\` (default)** — body-carried proof that is the same on any host (file read, injection exfil, info leak, reflection). Call with \`poc_path\`, an operator-approved \`control_target\` from \`PI_POC_CONTROL_TARGETS\`, optional same-byte \`control_path\` (defaults to \`poc_path\`), and \`local: true\` when the bug needs network. The harness sends the SAME request to target and control and requires \`target_only\`.
|
|
207
|
+
- **\`mode: "intra_target"\`** — access-control and business-logic classes (IDOR/BOLA, auth bypass, privilege escalation, mass assignment, logic/price tampering) where the discriminating variable is the attacker's IDENTITY or a PARAMETER, not the host. A different host lacks the victim's object/state, so inter-host proves nothing. Instead the evidence declares \`verify.mode: "intra_target"\` and a \`baseline\` (a legitimate SAME-host request — the attacker's own object, a properly-authorized request, the field omitted); the harness sends attack + baseline to the case target and requires the proof on the attack response only. No \`control_target\`/\`control_path\`.
|
|
208
|
+
|
|
209
|
+
Every run must complete with fully captured output and write nonce-bound \`evidence.json\` whose \`expect\` includes \`body_contains\` or \`body_regex\`; status-only evidence is rejected. The harness pins DNS at connect time and keeps redirects on the bound host. Private replay requires operator authorization. Blind/OOB classes fail closed until a source-separated oracle exists.
|
|
196
210
|
|
|
197
211
|
**ConfirmFinding (phase 2) — main-agent-only commit.** After PromoteFinding succeeds, do not dispatch confirmation. The main/coordinator agent must inspect the exact PoC/evidence, hunt trivial predicates/fabrication, attempt disconfirmation, and call \`ConfirmFinding(case_id, verdict)\` itself. A CONFIRMED call performs and stores a fresh harness-owned target/control replay; a caller-supplied re-execution checkbox is not accepted. CONFIRMED requires \`re_execution_note\`, \`differential: "target_only"\`, and the main agent's \`disconfirmation_attempt\`. Worker processes are rejected. **Never \`CaseUpdate(status: "confirmed")\` directly.**
|
|
198
212
|
|
|
@@ -345,7 +359,7 @@ Write the final report as a self-contained markdown file at the report path Case
|
|
|
345
359
|
- **No finding is confirmed until its target is verified in scope** per the program's scope instruction. Out-of-scope findings are killed, not confirmed.
|
|
346
360
|
- **No finding is validated without a reachability trace** showing REACHABLE. UNREACHABLE requires a concrete blocker; unresolved auth/WAF/source ambiguity stays INVESTIGATING or BLOCKED, not killed.
|
|
347
361
|
- **High-confidence findings: do your own adversarial disconfirmation.** No skeptic subagent in lite mode — actively try to disprove your own finding and document the attempt in \`disconfirmation\`. Failing to disprove is the expected outcome.
|
|
348
|
-
- **Confirmed requires** evidence + poc + impact + severity + target + disconfirmation, via the two-phase gate: **PromoteFinding**
|
|
362
|
+
- **Confirmed requires** evidence + poc + impact + severity + target + disconfirmation, via the two-phase gate: **PromoteFinding** in the differential \`mode\` that fits the class — \`inter_host\` (default) with an operator-approved \`control_target\` for body-carried proof (file read, injection exfil, info leak, reflection), or \`intra_target\` for access-control/logic classes (IDOR, auth bypass, privilege escalation, logic), where the evidence declares \`verify.mode:"intra_target"\` + a same-host \`baseline\` and the harness requires the proof on the attack response only (no control target). Then you, the main agent, inspect the bundle, attempt disconfirmation, and call **ConfirmFinding** yourself. That call captures a fresh second replay before commit. Do not delegate validation or confirmation. The machine gate requires zero-exit complete runs, nonce binding, body evidence, determinism, a DNS-pinned conclusive \`target_only\` differential, and script identity; zero exit is never proof and markers are diagnostic only. \`local:true\` and private replay remain operator-gated. No mocks and no direct \`CaseUpdate(status: "confirmed")\`.
|
|
349
363
|
- **Severity is derived from proven PoC impact, not theory.** Demonstrate and claim the highest impact the attacker can actually reach; claiming less than a proven escalation is wrong, and over-claiming an unproven one gets the finding rejected at triage.
|
|
350
364
|
- **Evidence-first:** every claim must be traceable to observed/reproduced behavior, source code, or documented platform behavior.
|
|
351
365
|
- **Design & runtime check (mandatory before CONFIRMED):** actively search the target's docs, git history, changelog, and runtime/framework docs for evidence the behavior is BY DESIGN or already FIXED IN THE RUNTIME. Found it → KILL (\`intended_behavior\` / \`framework_protection\`), unless the documented intent is itself the flaw with real attacker impact. Not found → document the search in \`disconfirmation\` as non-intentionality proof.
|