@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.
@@ -1,60 +1,47 @@
1
1
  /**
2
- * Harness-side target/control replay — Tier 2 of docs/poc-trust-model.md.
2
+ * Harness-side attack/baseline replay.
3
3
  *
4
4
  * The machine floor cannot trust a caller's self-reported `re_executed`
5
5
  * boolean. This module makes the HARNESS re-send the evidence's `verify`
6
- * request with its own HTTP client and apply the same `expect` predicates
7
- * (status / body_contains / body_regex) to target and control responses. The
8
- * main agent supplies the predicate; the harness owns both evidence acquisition
9
- * and predicate execution before the later semantic review.
6
+ * (attack) request and the evidence's `baseline` (legitimate) request with
7
+ * its own HTTP client, applying the attack's `expect` predicates to BOTH
8
+ * responses. The main agent supplies the predicate and the baseline; the
9
+ * harness owns evidence acquisition and predicate execution before the later
10
+ * semantic review.
10
11
  *
11
12
  * Policy:
12
13
  * - Private/internal hosts require explicit operator authorization; otherwise
13
14
  * replay fails closed.
14
15
  * - Redirects are manual and every hop is checked before it is fetched.
15
- * - Target must match while the identical control request must not.
16
+ * - The attack request must match while the baseline request must not.
16
17
  *
17
18
  * Undici's custom dispatcher pins the approved DNS result through connect;
18
19
  * node:dns and node:net provide resolution and address classification.
19
20
  */
20
21
 
21
- import { createHash, randomBytes } from "node:crypto";
22
+ import { createHash } from "node:crypto";
22
23
  import { lookup as dnsLookup } from "node:dns/promises";
23
24
  import { isIP } from "node:net";
24
25
  import { Worker } from "node:worker_threads";
25
26
  import { isPublicIpAddress } from "@xaccefy/pi-shared";
26
27
  import { Agent, fetch as undiciFetch } from "undici";
27
- import { POC_CANARY_PLACEHOLDER, type PoCEvidence, type VerifyExpect } from "./evidence.ts";
28
+ import type { PoCEvidence, VerifyExpect } from "./evidence.ts";
28
29
 
29
30
  // Public re-export makes the single-source classifier identity testable across
30
31
  // the web tool and confirmation replay paths.
31
32
  export { isPublicIpAddress } from "@xaccefy/pi-shared";
32
33
 
33
34
  export type HarnessVerifyResult = {
34
- /** true = the harness sent both target and control requests and judged them. */
35
+ /** true = the harness sent both attack and baseline requests and judged them. */
35
36
  attempted: boolean;
36
- /** Present when attempted: target matched and control did not. */
37
+ /** Present when attempted: attack matched and baseline did not. */
37
38
  pass?: boolean;
38
39
  /** Backward-compatible target status summary. */
39
40
  status?: number;
40
- /** Machine-observed target/control response summaries. */
41
+ /** Machine-observed attack/baseline response summaries. */
41
42
  target?: HarnessResponseObservation;
42
43
  control?: HarnessResponseObservation;
43
44
  differential?: "target_only" | "both" | "control_only" | "neither";
44
- /** Independent harness-generated reflection signal, when the template supports one. */
45
- canary?: HarnessCanaryResult;
46
- /** Honest machine claim: predicates alone, or predicates plus a causal canary. */
47
- proofStrength?: "predicate_differential" | "canary_differential";
48
- note: string;
49
- };
50
-
51
- export type HarnessCanaryResult = {
52
- mode: "reflection";
53
- attempted: boolean;
54
- pass?: boolean;
55
- tokenSha256: string;
56
- targetObserved?: boolean;
57
- controlObserved?: boolean;
58
45
  note: string;
59
46
  };
60
47
 
@@ -65,7 +52,6 @@ export type HarnessResponseObservation = {
65
52
  url: string;
66
53
  bodySha256?: string;
67
54
  bodyBytes?: number;
68
- canaryObserved?: boolean;
69
55
  note: string;
70
56
  };
71
57
 
@@ -120,38 +106,6 @@ function effectivePort(url: URL): string {
120
106
  return url.port || (url.protocol === "https:" ? "443" : "80");
121
107
  }
122
108
 
123
- function sameTargetIdentity(left: string, right: string): boolean {
124
- const a = parseNetworkTarget(left);
125
- const b = parseNetworkTarget(right);
126
- if (!a || !b) return false;
127
- if (normHost(a.url.hostname) !== normHost(b.url.hostname)) {
128
- return false;
129
- }
130
- if ((a.url.port || b.url.port) && effectivePort(a.url) !== effectivePort(b.url)) return false;
131
- return !(a.explicitProtocol && b.explicitProtocol && a.url.protocol !== b.url.protocol);
132
- }
133
-
134
- /**
135
- * A control is a trust anchor, not an agent invention. The operator supplies
136
- * an allowlist of approved control origins/hosts through the process env.
137
- */
138
- export function controlTargetAuthorizationError(
139
- controlTarget: string,
140
- allowedRaw: string | undefined = process.env.PI_POC_CONTROL_TARGETS,
141
- ): string | undefined {
142
- const allowed = (allowedRaw ?? "")
143
- .split(/[,\n]/)
144
- .map((value) => value.trim())
145
- .filter(Boolean);
146
- if (allowed.length === 0) {
147
- return "no operator-approved controls are configured in PI_POC_CONTROL_TARGETS";
148
- }
149
- if (!allowed.some((candidate) => sameTargetIdentity(candidate, controlTarget))) {
150
- return `control target ${controlTarget} is not present in the operator-approved PI_POC_CONTROL_TARGETS allowlist`;
151
- }
152
- return;
153
- }
154
-
155
109
  /**
156
110
  * Bind a model-authored verify URL to the target the harness actually ran.
157
111
  * A bare target permits either HTTP scheme; an explicit target URL binds the
@@ -183,16 +137,6 @@ export function verifyUrlBindingError(verifyUrl: string, target: string): string
183
137
  return;
184
138
  }
185
139
 
186
- function controlUrlFor(targetVerifyUrl: string, controlTarget: string): URL | undefined {
187
- const control = parseNetworkTarget(controlTarget);
188
- if (!control) return;
189
- const target = new URL(targetVerifyUrl);
190
- const url = new URL(control.url.origin);
191
- url.pathname = target.pathname;
192
- url.search = target.search;
193
- return url;
194
- }
195
-
196
140
  // ── Predicate evaluation ──────────────────────────────────────────────
197
141
 
198
142
  const REGEX_WORKER_SOURCE = `
@@ -356,7 +300,6 @@ async function fetchPinned(
356
300
  async function replayRequest(
357
301
  verify: PoCEvidence["verify"],
358
302
  expect: VerifyExpect,
359
- canaryToken?: string,
360
303
  opts?: ReplayOptions,
361
304
  ): Promise<HarnessResponseObservation> {
362
305
  let url: URL;
@@ -369,8 +312,6 @@ async function replayRequest(
369
312
  note: `verify.url unparseable (${verify.url})`,
370
313
  };
371
314
  }
372
- const observedUrl = () =>
373
- canaryToken ? url.toString().replaceAll(canaryToken, POC_CANARY_PLACEHOLDER) : url.toString();
374
315
  if (!(url.protocol === "http:" || url.protocol === "https:")) {
375
316
  return { attempted: false, url: verify.url, note: `verify.url protocol ${url.protocol}` };
376
317
  }
@@ -383,7 +324,7 @@ async function replayRequest(
383
324
  } catch (error) {
384
325
  return {
385
326
  attempted: false,
386
- url: observedUrl(),
327
+ url: verify.url,
387
328
  note: `verify.headers invalid: ${(error as Error).message}`,
388
329
  };
389
330
  }
@@ -398,7 +339,7 @@ async function replayRequest(
398
339
  if (!opts?.allowPrivate && localName) {
399
340
  return {
400
341
  attempted: false,
401
- url: observedUrl(),
342
+ url: url.toString(),
402
343
  note: `${url.hostname} is a private/internal host; operator authorization is required for harness replay`,
403
344
  };
404
345
  }
@@ -409,14 +350,14 @@ async function replayRequest(
409
350
  } catch (error) {
410
351
  return {
411
352
  attempted: true,
412
- url: observedUrl(),
353
+ url: url.toString(),
413
354
  note: `request errored (DNS): ${(error as Error).message}`,
414
355
  };
415
356
  }
416
357
  if (addresses.length === 0) {
417
358
  return {
418
359
  attempted: true,
419
- url: observedUrl(),
360
+ url: url.toString(),
420
361
  note: `request errored (DNS): ${url.hostname} resolved to no addresses`,
421
362
  };
422
363
  }
@@ -429,7 +370,7 @@ async function replayRequest(
429
370
  if (!opts?.allowPrivate && addresses.some((address) => !isPublicIpAddress(address.address))) {
430
371
  return {
431
372
  attempted: false,
432
- url: observedUrl(),
373
+ url: url.toString(),
433
374
  note: `${url.hostname} is a private/internal host; operator authorization is required for harness replay`,
434
375
  };
435
376
  }
@@ -457,7 +398,7 @@ async function replayRequest(
457
398
  return {
458
399
  attempted: true,
459
400
  status: res.status,
460
- url: observedUrl(),
401
+ url: url.toString(),
461
402
  note: `redirect limit exceeded (${MAX_REDIRECTS})`,
462
403
  };
463
404
  }
@@ -469,7 +410,7 @@ async function replayRequest(
469
410
  return {
470
411
  attempted: true,
471
412
  status: res.status,
472
- url: observedUrl(),
413
+ url: url.toString(),
473
414
  note: `redirected to disallowed protocol ${next.protocol}`,
474
415
  };
475
416
  }
@@ -480,7 +421,7 @@ async function replayRequest(
480
421
  return {
481
422
  attempted: true,
482
423
  status: res.status,
483
- url: observedUrl(),
424
+ url: url.toString(),
484
425
  note: `redirect left the bound host (${url.hostname} -> ${next.hostname})`,
485
426
  };
486
427
  }
@@ -512,22 +453,20 @@ async function replayRequest(
512
453
  return {
513
454
  attempted: true,
514
455
  status: res.status,
515
- url: observedUrl(),
456
+ url: url.toString(),
516
457
  bodySha256: observed.sha256,
517
458
  bodyBytes: observed.bytes,
518
459
  note: "response body exceeded the 2 MiB capture limit; matcher result is inconclusive",
519
460
  };
520
461
  }
521
462
  const failures = await evaluateExpect(expect, res.status, observed.text);
522
- const canaryObserved = canaryToken ? observed.text.includes(canaryToken) : undefined;
523
463
  return {
524
464
  attempted: true,
525
465
  matched: failures.length === 0,
526
466
  status: res.status,
527
- url: observedUrl(),
467
+ url: url.toString(),
528
468
  bodySha256: observed.sha256,
529
469
  bodyBytes: observed.bytes,
530
- canaryObserved,
531
470
  note:
532
471
  failures.length === 0
533
472
  ? `status ${res.status}, all predicates matched`
@@ -537,13 +476,13 @@ async function replayRequest(
537
476
  await closeFetched?.().catch(() => undefined);
538
477
  return {
539
478
  attempted: true,
540
- url: observedUrl(),
479
+ url: url.toString(),
541
480
  note: `request errored (DNS/TLS/timeout): ${(e as Error).message}`,
542
481
  };
543
482
  }
544
483
  }
545
484
 
546
- return { attempted: true, url: observedUrl(), note: "unreachable redirect state" };
485
+ return { attempted: true, url: url.toString(), note: "unreachable redirect state" };
547
486
  }
548
487
 
549
488
  /**
@@ -569,58 +508,14 @@ export function sameRequest(
569
508
  );
570
509
  }
571
510
 
572
- function injectCanary(
573
- verify: PoCEvidence["verify"],
574
- token: string | undefined,
575
- ): PoCEvidence["verify"] {
576
- if (!token) return verify;
577
- const replace = (value: string) => value.replace(POC_CANARY_PLACEHOLDER, token);
578
- return {
579
- ...verify,
580
- url: replace(verify.url),
581
- body: verify.body === undefined ? undefined : replace(verify.body),
582
- headers:
583
- verify.headers === undefined
584
- ? undefined
585
- : Object.fromEntries(
586
- Object.entries(verify.headers).map(([key, value]) => [key, replace(value)]),
587
- ),
588
- };
589
- }
590
-
591
- function canaryResult(
592
- token: string | undefined,
593
- target: HarnessResponseObservation,
594
- control?: HarnessResponseObservation,
595
- ): HarnessCanaryResult | undefined {
596
- if (!token) return;
597
- const attempted =
598
- target.canaryObserved !== undefined && (control ? control.canaryObserved !== undefined : true);
599
- const pass = control
600
- ? attempted && target.canaryObserved === true && control.canaryObserved === false
601
- : attempted && target.canaryObserved === true;
602
- return {
603
- mode: "reflection",
604
- attempted,
605
- pass,
606
- tokenSha256: createHash("sha256").update(token).digest("hex"),
607
- targetObserved: target.canaryObserved,
608
- controlObserved: control?.canaryObserved,
609
- note: control
610
- ? `canary ${pass ? "target-only" : "failed"}: target=${String(target.canaryObserved)}, control=${String(control.canaryObserved)}`
611
- : `canary ${pass ? "observed" : "not observed"} on target`,
612
- };
613
- }
614
-
615
511
  /**
616
512
  * Combine the two observations into the differential verdict. Shared by the
617
- * inter-host (target vs control host) and intra-target (attack vs same-host
618
- * baseline) replays — only the note labels differ.
513
+ * attack-vs-baseline replay: "target" is the attack request, "control" the
514
+ * legitimate baseline request.
619
515
  */
620
516
  function judgeDifferential(
621
517
  target: HarnessResponseObservation,
622
518
  control: HarnessResponseObservation,
623
- token: string | undefined,
624
519
  label: { kind: string; a: string; b: string },
625
520
  ): HarnessVerifyResult {
626
521
  const attempted = target.attempted && control.attempted;
@@ -634,12 +529,7 @@ function judgeDifferential(
634
529
  ? "control_only"
635
530
  : "neither"
636
531
  : undefined;
637
- const canary = canaryResult(token, target, control);
638
- const pass =
639
- attempted &&
640
- conclusive &&
641
- differential === "target_only" &&
642
- (canary === undefined || canary.pass === true);
532
+ const pass = attempted && conclusive && differential === "target_only";
643
533
  return {
644
534
  attempted,
645
535
  pass,
@@ -647,72 +537,16 @@ function judgeDifferential(
647
537
  target,
648
538
  control,
649
539
  differential,
650
- canary,
651
- proofStrength: canary?.pass ? "canary_differential" : "predicate_differential",
652
540
  note:
653
541
  `harness ${label.kind} ${differential ?? "inconclusive"}: ${label.a} (${target.note}); ` +
654
- `${label.b} (${control.note})${canary ? `; ${canary.note}` : ""}`,
542
+ `${label.b} (${control.note})`,
655
543
  };
656
544
  }
657
545
 
658
546
  /**
659
- * Execute one harness-owned request template against both the case target and
660
- * a distinct control origin. The PoC cannot weaken the control request: the
661
- * harness preserves method, path, query, headers, body, and target predicates,
662
- * changing only the origin to the declared control target.
663
- */
664
- export async function replayDifferential(
665
- evidence: PoCEvidence,
666
- caseTarget: string,
667
- controlTarget: string,
668
- opts?: ReplayOptions,
669
- ): Promise<HarnessVerifyResult> {
670
- const bindingError = verifyUrlBindingError(evidence.verify.url, caseTarget);
671
- if (bindingError) {
672
- return { attempted: false, pass: false, note: `target binding failed: ${bindingError}` };
673
- }
674
- if (sameTargetIdentity(caseTarget, controlTarget)) {
675
- return {
676
- attempted: false,
677
- pass: false,
678
- note: "control target resolves to the same network identity as the case target",
679
- };
680
- }
681
- const controlUrl = controlUrlFor(evidence.verify.url, controlTarget);
682
- if (!controlUrl) {
683
- return {
684
- attempted: false,
685
- pass: false,
686
- note: `control target is not an HTTP network target: ${controlTarget}`,
687
- };
688
- }
689
-
690
- const token = evidence.verify.canary
691
- ? `poc_canary_${randomBytes(24).toString("hex")}`
692
- : undefined;
693
- const targetVerify = injectCanary(evidence.verify, token);
694
- const target = await replayRequest(targetVerify, targetVerify.expect, token, opts);
695
- const control = await replayRequest(
696
- {
697
- ...targetVerify,
698
- url: injectCanary({ ...evidence.verify, url: controlUrl.toString() }, token).url,
699
- },
700
- targetVerify.expect,
701
- token,
702
- opts,
703
- );
704
- return judgeDifferential(target, control, token, {
705
- kind: "differential",
706
- a: "target",
707
- b: "control",
708
- });
709
- }
710
-
711
- /**
712
- * Same-host differential (Tier 2, intra-target). For access-control and
713
- * business-logic classes the discriminating variable is the attacker's
714
- * identity or a request parameter, NOT the host — so the sound baseline is a
715
- * legitimate request to the SAME target, not the same request to another host.
547
+ * Same-host differential (intra-target). The discriminating variable is the
548
+ * attacker's identity or a request parameter, not the host the sound
549
+ * baseline is a legitimate request to the SAME target.
716
550
  * The harness sends the attack request and the model-declared `evidence.baseline`
717
551
  * request to the case target, applies the attack's `verify.expect` predicates to
718
552
  * BOTH responses, and passes only when the proof appears on the attack response
@@ -749,13 +583,9 @@ export async function replayIntraTarget(
749
583
  };
750
584
  }
751
585
 
752
- const token = evidence.verify.canary
753
- ? `poc_canary_${randomBytes(24).toString("hex")}`
754
- : undefined;
755
- const attackVerify = injectCanary(evidence.verify, token);
756
- const attack = await replayRequest(attackVerify, attackVerify.expect, token, opts);
586
+ const attack = await replayRequest(evidence.verify, evidence.verify.expect, opts);
757
587
  // The baseline carries the attack's predicates: the proof must be ABSENT here.
758
- const baselineVerify = injectCanary(
588
+ const base = await replayRequest(
759
589
  {
760
590
  ...evidence.verify,
761
591
  method: baseline.method,
@@ -763,11 +593,11 @@ export async function replayIntraTarget(
763
593
  headers: baseline.headers,
764
594
  body: baseline.body,
765
595
  },
766
- token,
596
+ evidence.verify.expect,
597
+ opts,
767
598
  );
768
- const base = await replayRequest(baselineVerify, attackVerify.expect, token, opts);
769
599
 
770
- return judgeDifferential(attack, base, token, {
600
+ return judgeDifferential(attack, base, {
771
601
  kind: "intra-target",
772
602
  a: "attack",
773
603
  b: "baseline",