Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
e15c023
fix(scenario-verify): withhold recorded_replay pending isolation evid…
tnunamak Sep 2, 2026
af22b47
fix(isolation): resolve unshare/bwrap launchers from a trusted absolu…
tnunamak Sep 2, 2026
639afa9
fix(isolation): remount every submount of a ro bind read-only, not ju…
tnunamak Sep 2, 2026
073454b
fix(isolation): reconcile the repository-UDS exception with a bounded…
tnunamak Sep 2, 2026
8dc7f06
fix(scenario-verify): state recorded_replay's preconditions inline
tnunamak Sep 2, 2026
2ea95ca
fix(isolation): resolve the sh interpreter through the trusted absolu…
tnunamak Sep 2, 2026
3553ddc
fix(isolation): fail closed on an unreadable socket-scan subtree, nar…
tnunamak Sep 2, 2026
99b7b60
fix(isolation): decode mountinfo octal escapes, iterate newline-safe,…
tnunamak Sep 2, 2026
19ed6dd
fix(isolation): don't fail the in-namespace socket scan on a benign E…
tnunamak Sep 2, 2026
eef3072
test(isolation): shorten socket-scan test paths under the AF_UNIX 108…
tnunamak Sep 2, 2026
3f10037
style(isolation): satisfy biome format/lint (verify+test CI was failing)
tnunamak Sep 2, 2026
e25960b
style(isolation): sort claims.ts interface members, fix import order …
tnunamak Sep 2, 2026
10e9064
ci(polyfill-connectors): raise verify+test timeout from 10 to 15 minutes
tnunamak Sep 2, 2026
6398cc9
fix(isolation): make file writability probe non-destructive
tnunamak Sep 3, 2026
cc9b96d
fix(isolation): harden generated scripts against PATH poisoning
tnunamak Sep 3, 2026
3a490c1
fix(isolation): make the submount enumerator byte-safe and fail closed
tnunamak Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/polyfill-connectors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
verify:
name: verify + test
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 15

steps:
- name: Checkout
Expand Down
223 changes: 223 additions & 0 deletions packages/polyfill-connectors/bin/scenario-verify-strict.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,10 @@ function eligibleDigestObservations(): {
currentSourceDigestComputed: boolean;
observedUnsupportedEvidenceSurface: boolean;
driverEvidenceSatisfied: boolean;
isolationEvidenceBoundaryProven: boolean;
preexistingSocketsUnderReadOnlyBinds: readonly string[];
preexistingSocketScanIncomplete: boolean;
preexistingSocketScanUnreadablePaths: readonly string[];
} {
return {
capturedDeclarationDigestPresent: true,
Expand All @@ -865,6 +869,10 @@ function eligibleDigestObservations(): {
currentSourceDigestComputed: true,
observedUnsupportedEvidenceSurface: false,
driverEvidenceSatisfied: true,
isolationEvidenceBoundaryProven: true,
preexistingSocketScanIncomplete: false,
preexistingSocketScanUnreadablePaths: [],
preexistingSocketsUnderReadOnlyBinds: [],
};
}

Expand Down Expand Up @@ -954,6 +962,10 @@ test("evaluateClaimEligibility negative control: source-only historical scenario
currentSourceDigestComputed: true,
observedUnsupportedEvidenceSurface: false,
driverEvidenceSatisfied: true,
isolationEvidenceBoundaryProven: true,
preexistingSocketsUnderReadOnlyBinds: [],
preexistingSocketScanIncomplete: false,
preexistingSocketScanUnreadablePaths: [],
isNamespaceIsolationActive: true,
});
assert.equal(decision.claim, "diagnostic_replay");
Expand All @@ -971,6 +983,10 @@ test("evaluateClaimEligibility negative control: declaration-only scenario (sour
currentSourceDigestComputed: true,
observedUnsupportedEvidenceSurface: false,
driverEvidenceSatisfied: true,
isolationEvidenceBoundaryProven: true,
preexistingSocketsUnderReadOnlyBinds: [],
preexistingSocketScanIncomplete: false,
preexistingSocketScanUnreadablePaths: [],
isNamespaceIsolationActive: true,
});
assert.equal(decision.claim, "diagnostic_replay");
Expand All @@ -988,6 +1004,10 @@ test("evaluateClaimEligibility negative control: missing current manifest (decla
currentSourceDigestComputed: true,
observedUnsupportedEvidenceSurface: false,
driverEvidenceSatisfied: true,
isolationEvidenceBoundaryProven: true,
preexistingSocketsUnderReadOnlyBinds: [],
preexistingSocketScanIncomplete: false,
preexistingSocketScanUnreadablePaths: [],
isNamespaceIsolationActive: true,
});
assert.equal(decision.claim, "diagnostic_replay");
Expand All @@ -1005,6 +1025,10 @@ test("evaluateClaimEligibility negative control: missing current connector sourc
currentSourceDigestComputed: false,
observedUnsupportedEvidenceSurface: false,
driverEvidenceSatisfied: true,
isolationEvidenceBoundaryProven: true,
preexistingSocketsUnderReadOnlyBinds: [],
preexistingSocketScanIncomplete: false,
preexistingSocketScanUnreadablePaths: [],
isNamespaceIsolationActive: true,
});
assert.equal(decision.claim, "diagnostic_replay");
Expand All @@ -1029,6 +1053,10 @@ test("evaluateClaimEligibility negative control: legacy top-level digests only (
currentSourceDigestComputed: true,
observedUnsupportedEvidenceSurface: false,
driverEvidenceSatisfied: true,
isolationEvidenceBoundaryProven: true,
preexistingSocketsUnderReadOnlyBinds: [],
preexistingSocketScanIncomplete: false,
preexistingSocketScanUnreadablePaths: [],
isNamespaceIsolationActive: true,
});
assert.equal(decision.claim, "diagnostic_replay");
Expand Down Expand Up @@ -1111,6 +1139,10 @@ test("evaluateClaimEligibility: multiple failing conditions are all reported at
observedUnsupportedEvidenceSurface: true,
driverEvidenceSatisfied: false,
isNamespaceIsolationActive: false,
isolationEvidenceBoundaryProven: false,
preexistingSocketsUnderReadOnlyBinds: [],
preexistingSocketScanIncomplete: false,
preexistingSocketScanUnreadablePaths: [],
});
assert.equal(decision.claim, "diagnostic_replay");
assert.ok(decision.claim === "diagnostic_replay");
Expand Down Expand Up @@ -1152,6 +1184,197 @@ test("evaluateClaimEligibility: with every other condition eligible, the claim t
}
});

// ─── Bounded P1 repair (external review of ab415be6c): isolation evidence
// boundary — launcher trust + recursive read-only, on TOP of namespace
// activity alone ────────────────────────────────────────────────────────────
//
// The review found that `isNamespaceIsolationActive: true` (the OS
// namespaces genuinely exist) was being treated as sufficient for
// `recorded_replay`, even though two separate defects meant the FILESYSTEM
// half of that isolation could be unproven: the `unshare`/`bwrap` launcher
// binaries were resolved through the caller's inherited `$PATH` (a
// PATH-prepended fake launcher could be selected), and the unshare
// mechanism's `--rbind` submounts only had their top mount remounted
// read-only, leaving nested mounts under a `ro` bind writable. These tests
// pin that namespace-active alone can never reach `recorded_replay` — the
// new `isolationEvidenceBoundaryProven` observation must ALSO be true.

test("evaluateClaimEligibility: namespace isolation active but isolationEvidenceBoundaryProven false withholds recorded_replay (launcher trust / recursive-ro not proven)", () => {
const decision = evaluateClaimEligibility({
scenario: eligibleScenario(),
isEntrypointOverride: false,
...eligibleDigestObservations(),
isNamespaceIsolationActive: true,
isolationEvidenceBoundaryProven: false,
});
assert.equal(decision.claim, "diagnostic_replay");
assert.ok(decision.claim === "diagnostic_replay");
assert.deepEqual(decision.limitations, [
"network isolation: launcher trust or recursive read-only filesystem closure not proven for this run",
]);
});

test("evaluateClaimEligibility: namespace isolation active AND isolationEvidenceBoundaryProven true — recorded_replay is reachable again", () => {
const decision = evaluateClaimEligibility({
scenario: eligibleScenario(),
isEntrypointOverride: false,
...eligibleDigestObservations(),
isNamespaceIsolationActive: true,
isolationEvidenceBoundaryProven: true,
preexistingSocketsUnderReadOnlyBinds: [],
});
assert.deepEqual(decision, { claim: "recorded_replay" });
});

test("evaluateClaimEligibility: namespace isolation NOT active reports only the coarser process-local limitation, never BOTH isolation limitations at once", () => {
// When isolation isn't active at all, the boundary-proof limitation is
// redundant with (and would be confusing alongside) the coarser
// process-local-only limitation — evaluateClaimEligibility's `else if`
// must report exactly one of the two, never both.
const decision = evaluateClaimEligibility({
scenario: eligibleScenario(),
isEntrypointOverride: false,
...eligibleDigestObservations(),
isNamespaceIsolationActive: false,
isolationEvidenceBoundaryProven: false,
});
assert.equal(decision.claim, "diagnostic_replay");
assert.ok(decision.claim === "diagnostic_replay");
assert.deepEqual(decision.limitations, ["network isolation: process-local only - descendant escape not excluded"]);
});

// ─── Repository-UDS exception, reconciled (P1, external review of ab415be6c)
// ────────────────────────────────────────────────────────────────────────────
//
// Recursive read-only closes the ability to CREATE a socket under a ro
// bind, but not the ability to DIAL one that already existed at spawn time
// — see claims.ts's `preexistingSocketsUnderReadOnlyBinds` doc comment.
// These tests pin the eligibility gate's own handling of the scan result:
// a non-empty result withholds recorded_replay and names every path found;
// an empty result does not withhold on this condition at all.

test("evaluateClaimEligibility: a non-empty preexistingSocketsUnderReadOnlyBinds withholds recorded_replay and names the socket path", () => {
const decision = evaluateClaimEligibility({
scenario: eligibleScenario(),
isEntrypointOverride: false,
...eligibleDigestObservations(),
isNamespaceIsolationActive: true,
isolationEvidenceBoundaryProven: true,
preexistingSocketsUnderReadOnlyBinds: ["/repo/root/.leftover.sock"],
});
assert.equal(decision.claim, "diagnostic_replay");
assert.ok(decision.claim === "diagnostic_replay");
assert.deepEqual(decision.limitations, [
"pre-existing socket(s) found under a read-only bind at spawn time, dialable despite recursive read-only: /repo/root/.leftover.sock",
]);
});

test("evaluateClaimEligibility: multiple preexistingSocketsUnderReadOnlyBinds are all named in one limitation, comma-joined", () => {
const decision = evaluateClaimEligibility({
scenario: eligibleScenario(),
isEntrypointOverride: false,
...eligibleDigestObservations(),
isNamespaceIsolationActive: true,
isolationEvidenceBoundaryProven: true,
preexistingSocketsUnderReadOnlyBinds: ["/repo/root/a.sock", "/repo/root/nested/b.sock"],
});
assert.ok(decision.claim === "diagnostic_replay");
assert.deepEqual(decision.limitations, [
"pre-existing socket(s) found under a read-only bind at spawn time, dialable despite recursive read-only: /repo/root/a.sock, /repo/root/nested/b.sock",
]);
});

test("evaluateClaimEligibility: an empty preexistingSocketsUnderReadOnlyBinds does not withhold on this condition — recorded_replay reachable", () => {
const decision = evaluateClaimEligibility({
scenario: eligibleScenario(),
isEntrypointOverride: false,
...eligibleDigestObservations(),
isNamespaceIsolationActive: true,
isolationEvidenceBoundaryProven: true,
preexistingSocketsUnderReadOnlyBinds: [],
});
assert.deepEqual(decision, { claim: "recorded_replay" });
});

// ─── Scan-completeness gate (P1-2, external review of ced8300be) ──────────
//
// A scan that could not fully enumerate a subtree is NOT the same fact as
// "scanned, found nothing" — `preexistingSocketScanIncomplete` must
// independently withhold `recorded_replay`, on the same severity as a
// non-empty `preexistingSocketsUnderReadOnlyBinds`, even when the socket
// list itself is empty (an incomplete scan means that empty list cannot be
// trusted as exhaustive).

test("evaluateClaimEligibility: preexistingSocketScanIncomplete withholds recorded_replay even when preexistingSocketsUnderReadOnlyBinds is empty", () => {
const decision = evaluateClaimEligibility({
scenario: eligibleScenario(),
isEntrypointOverride: false,
...eligibleDigestObservations(),
isNamespaceIsolationActive: true,
isolationEvidenceBoundaryProven: true,
preexistingSocketsUnderReadOnlyBinds: [],
preexistingSocketScanIncomplete: true,
preexistingSocketScanUnreadablePaths: ["/repo/.pdpp-blocked-subtree"],
});
assert.equal(decision.claim, "diagnostic_replay");
assert.ok(decision.claim === "diagnostic_replay");
assert.deepEqual(decision.limitations, [
"pre-existing-socket scan could not fully enumerate one or more read-only bind subtrees (unreadable path(s), possibly hiding a dialable socket): /repo/.pdpp-blocked-subtree",
]);
});

test("evaluateClaimEligibility: a non-empty preexistingSocketsUnderReadOnlyBinds takes priority over preexistingSocketScanIncomplete (the more specific, more actionable limitation wins)", () => {
const decision = evaluateClaimEligibility({
scenario: eligibleScenario(),
isEntrypointOverride: false,
...eligibleDigestObservations(),
isNamespaceIsolationActive: true,
isolationEvidenceBoundaryProven: true,
preexistingSocketsUnderReadOnlyBinds: ["/repo/found.sock"],
preexistingSocketScanIncomplete: true,
preexistingSocketScanUnreadablePaths: ["/repo/.pdpp-blocked-subtree"],
});
assert.equal(decision.claim, "diagnostic_replay");
assert.ok(decision.claim === "diagnostic_replay");
assert.deepEqual(decision.limitations, [
"pre-existing socket(s) found under a read-only bind at spawn time, dialable despite recursive read-only: /repo/found.sock",
]);
});

test("evaluateClaimEligibility: preexistingSocketScanIncomplete is irrelevant when isolation itself is not active (the coarser limitation fires instead)", () => {
const decision = evaluateClaimEligibility({
scenario: eligibleScenario(),
isEntrypointOverride: false,
...eligibleDigestObservations(),
isNamespaceIsolationActive: false,
isolationEvidenceBoundaryProven: false,
preexistingSocketsUnderReadOnlyBinds: [],
preexistingSocketScanIncomplete: true,
preexistingSocketScanUnreadablePaths: ["/repo/.pdpp-blocked-subtree"],
});
assert.equal(decision.claim, "diagnostic_replay");
assert.ok(decision.claim === "diagnostic_replay");
assert.deepEqual(decision.limitations, ["network isolation: process-local only - descendant escape not excluded"]);
});

test("evaluateClaimEligibility: the socket-scan limitation only fires when isolation is active AND the evidence boundary is proven (not a fourth, independent gate)", () => {
// If isolation isn't active at all, the coarser process-local limitation
// must fire instead — a non-empty socket scan result is meaningless
// (and, in bin/scenario-verify.ts's real wiring, always empty) when
// isolation was never active for this run.
const decision = evaluateClaimEligibility({
scenario: eligibleScenario(),
isEntrypointOverride: false,
...eligibleDigestObservations(),
isNamespaceIsolationActive: false,
isolationEvidenceBoundaryProven: false,
preexistingSocketsUnderReadOnlyBinds: ["/repo/root/.leftover.sock"],
});
assert.equal(decision.claim, "diagnostic_replay");
assert.ok(decision.claim === "diagnostic_replay");
assert.deepEqual(decision.limitations, ["network isolation: process-local only - descendant escape not excluded"]);
});

// ─── Repair wave 6, P1-1: driver-evidence prerequisite ─────────────────────
//
// `wire-registry.ts`'s `DRIVER_EVIDENCE_POLICIES` map — `recorded-http`'s
Expand Down
Loading