Skip to content

Commit a98fa9f

Browse files
committed
feat: outline pre-Phase-2 hardening gates and implementation sequence for JavaScript rewriting
1 parent 71537d2 commit a98fa9f

1 file changed

Lines changed: 158 additions & 0 deletions

File tree

PHASE2_PLAN.md

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,156 @@ This is a compatibility layer, not a claim of perfect browser-origin spoofing. N
2222
- Executing unparsed original target JavaScript as a compatibility fallback in strict mode.
2323
- Server-side session state or server-side target JavaScript storage.
2424

25+
26+
## Pre-Phase-2 hardening gates
27+
28+
Phase 2 must not start by assuming the rewriter will close every current gap. The existing Phase 0/1 boundary must first fail closed when rewriting is absent, late, or broken. The following gates are prerequisites for the Phase 2 implementation sequence.
29+
30+
### P0: boundary hardening required before rewriter work
31+
32+
1. **Tighten target-response CSP**
33+
34+
- Current risk: `web/zp-core.js` emits `connect-src * blob: data: <proxy-ws-origin>` for Service Worker-constructed target responses.
35+
- Why it matters: Phase 2 rewriting cannot be the only egress boundary. If a script source is missed, CSP must still provide defense-in-depth against direct native connections.
36+
- Required change:
37+
- Align `ZP.fixedCSP()` with the stricter server-side `zeroCSP`.
38+
- Restrict `connect-src` to `'self'` plus the proxy WebSocket origin.
39+
- Re-evaluate `script-src blob: data:` and document any temporary compatibility exception.
40+
- Required tests:
41+
- Static policy test rejecting `connect-src *`.
42+
- Browser E2E fixture attempting direct external `fetch`, XHR, EventSource, and WebSocket egress.
43+
44+
2. **Add Service Worker message capabilities**
45+
46+
- Current risk: target scripts execute on the proxy origin and can call `navigator.serviceWorker.controller.postMessage()` directly.
47+
- Sensitive message types include `ZP_WS_OPEN`, `ZP_COOKIE_SET`, `ZP_HISTORY_UPDATE`, `ZP_BASE_UPDATE`, `ZP_SCROLL_UPDATE`, and `ZP_RESOLVE_ENTRY`.
48+
- Required change:
49+
- Generate a per-tab runtime capability token.
50+
- Pass it to the runtime prelude through `__ZP_BOOT`, then keep it closure-private.
51+
- Require that token on every runtime-originated `ZP_*` message.
52+
- Validate `event.source.id` against `clientContext` where possible.
53+
- Remove `firstTab()` fallback from privileged operations such as `openRuntimeStream`.
54+
- Required tests:
55+
- Forged page-level `postMessage` to the Service Worker is rejected.
56+
- Runtime WebSocket and cookie bridge messages still succeed with the valid token.
57+
58+
3. **Close blob/data script execution gaps**
59+
60+
- Current risk: `URL.createObjectURL` wrapping is worker-oriented and MIME-dependent; blob/data scripts can also be introduced through DOM script loaders.
61+
- Required change:
62+
- Before the rewriter exists, block or neutralize target-created blob/data JavaScript execution paths that cannot be routed through the rewrite pipeline.
63+
- Include empty or unrecognized Blob MIME types in worker bootstrap handling, or wrap all target-created Blob URLs until Phase 2 can classify them.
64+
- Revisit `script-src blob: data:` together with the CSP gate above.
65+
- Required tests:
66+
- `new Worker(URL.createObjectURL(new Blob(["..."], { type: "" })))` is contained or blocked.
67+
- `<script src=blob:...>` and data URL script attempts do not execute unrewritten target code in strict mode.
68+
69+
4. **Fail closed for dynamic JavaScript compilation until the foreground rewriter exists**
70+
71+
- Current risk: dynamic compilation paths are listed in this plan, but Phase 0 runtime does not yet block or rewrite all of them.
72+
- Required change:
73+
- In strict/pre-strict hardening mode, block or neutralize:
74+
- `eval` and indirect eval;
75+
- `Function`, `AsyncFunction`, `GeneratorFunction`, and `AsyncGeneratorFunction`;
76+
- constructor-constructor escapes such as `({}).constructor.constructor(...)`;
77+
- string `setTimeout` and `setInterval`;
78+
- `document.write` / `document.writeln`;
79+
- `outerHTML`, `template.innerHTML`, `Range.prototype.createContextualFragment`, and `DOMParser.prototype.parseFromString`;
80+
- inline event handler mutation APIs including `setAttribute`, `setAttributeNS`, `NamedNodeMap.setNamedItem`, `Attr.value`, and handler IDL setters.
81+
- Required tests:
82+
- `Function('return location.href')()` and constructor-constructor variants are rewritten or blocked.
83+
- String timers and runtime-created inline event handlers cannot execute unrewritten code.
84+
85+
### P1: reliability and correctness hardening before broad Phase 2 compatibility work
86+
87+
5. **Prepare inline event handler handling in the HTML transformer**
88+
89+
- Current state: `internal/htmltx` rewrites URL-bearing attributes and `srcdoc`, but does not identify `on*` handler attributes.
90+
- Required change:
91+
- Add explicit detection for inline event handler attributes.
92+
- Preserve current behavior until the rewriter callback exists, but make the Phase 2 hook point explicit.
93+
- In strict mode, fail closed if an event handler body cannot be rewritten before browser compilation.
94+
- Required tests:
95+
- HTML transform test covering `onclick`, `onload`, `onerror`, and mixed-case handler attributes.
96+
- Phase 2 golden tests parsing handler bodies in event-handler grammar mode.
97+
98+
6. **Prove or harden `srcdoc` prelude ordering**
99+
100+
- Current state: `srcdoc` content is protected by prefixing ZeroProxy scripts before target content.
101+
- Required change:
102+
- Add browser coverage proving that inline scripts and event handlers inside `srcdoc` cannot run before `runtime-prelude.js` installs containment.
103+
- If ordering is not reliable, block or rewrite executable `srcdoc` content until the Phase 2 rewriter handles it.
104+
- Required tests:
105+
- `iframe.srcdoc` with immediate inline script and `body onload` cannot reach native WebRTC/WebSocket or native `location` before containment.
106+
107+
7. **Cap or stream request/upload bodies**
108+
109+
- Current state: request bodies are buffered through Service Worker/WASM bridges.
110+
- Required change:
111+
- Short term: enforce an explicit maximum body size and return a safe ZeroProxy error for oversized uploads.
112+
- Medium term: implement `ReadableStream` request body bridging instead of full buffering.
113+
- Required tests:
114+
- Oversized POST/upload returns a safe error, not OOM or process instability.
115+
- Non-oversized POST body survives the Service Worker -> WASM -> target path.
116+
117+
8. **Define redirect body replay semantics**
118+
119+
- Current risk: 307/308 redirects preserve method and body, but non-replayable bodies cannot be resent safely after the first attempt consumes them.
120+
- Required change:
121+
- For replayable small bodies, buffer once and replay across 307/308 redirects.
122+
- For non-replayable or oversized bodies, fail closed with a safe error.
123+
- Do not silently send an empty or partial body after redirect.
124+
- Required tests:
125+
- 307/308 POST redirect with a small body reaches the redirected target intact.
126+
- Non-replayable body redirect fails safely.
127+
128+
9. **Make relay cancellation immediate and bidirectional**
129+
130+
- Current risk: relay goroutines using `io.Copy` may stay blocked after request/context cancellation.
131+
- Required change:
132+
- On context cancellation or one relay direction finishing, close both stream endpoints to interrupt blocked reads/writes.
133+
- Prefer a context-aware relay loop or reuse an existing relay helper with explicit close semantics.
134+
- Required tests:
135+
- Closing the browser page or WebSocket pipe tears down both yamux/Tor relay directions without goroutine leaks.
136+
137+
### P2: coverage and fidelity improvements that should accompany Phase 2
138+
139+
10. **Expand direct-egress E2E coverage**
140+
141+
- Required browser fixtures:
142+
- Direct external `fetch`.
143+
- `XMLHttpRequest`.
144+
- `EventSource`.
145+
- Native WebSocket attempts.
146+
- Each fixture must prove the request is blocked or routed through ZeroProxy, never through an unclassified native path.
147+
148+
11. **Improve WebSocket wrapper fidelity**
149+
150+
- Current minimal wrapper is enough for basic echo but not browser-compatible enough for broad sites.
151+
- Required improvements:
152+
- Preserve negotiated subprotocol where available.
153+
- Improve close code/reason and error sequencing.
154+
- Cover binary `ArrayBuffer` / `Blob` message behavior.
155+
- Add conformance-lite E2E tests for open, message, binary, close, protocol, and error paths.
156+
157+
12. **Clarify HTML tokenizer error policy**
158+
159+
- Current doc comments imply parser-recoverable markup is emitted, while tokenizer errors currently fail.
160+
- Required change:
161+
- Decide and document whether malformed HTML is strict fail-closed or recovery-oriented.
162+
- Align the behavior with Phase 2 strict rewrite failure policy.
163+
- Required tests:
164+
- Malformed but common HTML either recovers predictably or produces a safe `MALFORMED_HTML` error document without partial unsafe execution.
165+
166+
13. **Validate share URL schemes in the Go share-url package**
167+
168+
- Current risk: callers usually validate `http`/`https`, but `internal/shareurl.New()` itself only encrypts a string.
169+
- Required change:
170+
- Enforce `http:` and `https:` inside `shareurl.New()` / `NewWithRand()`.
171+
- Reject `ws:`, `wss:`, `javascript:`, `data:`, and empty or malformed URLs.
172+
- Required tests:
173+
- Go unit tests proving HTTP/HTTPS are accepted and WebSocket/non-HTTP schemes are rejected.
174+
25175
## Design constraint: why rewriting is necessary but not sufficient
26176

27177
`window.location` cannot be fully virtualized by descriptor patching alone:
@@ -418,6 +568,10 @@ Golden tests for:
418568
- A test page reading `location.href`, `window.location.href`, `document.URL`, and `document.defaultView.location.href` observes the virtual target URL from rewritten code.
419569
- A hostile test page using `window['loca' + 'tion']`, `Reflect.get(window, 'location')`, iframe clean realms, and `Function('return location.href')` is rewritten or blocked according to strict mode.
420570
- No tested path changes the top-level URL to the target origin.
571+
- Direct external `fetch`, XHR, EventSource, and native WebSocket attempts are blocked or routed through ZeroProxy; none use an unclassified native path.
572+
- Blob/data worker and script fixtures are contained, rewritten, or blocked.
573+
- `srcdoc` inline scripts and event handlers cannot execute before iframe containment.
574+
- Request/upload size limits, 307/308 redirect replay behavior, and relay cancellation semantics are covered by integration tests.
421575

422576
### Performance tests
423577

@@ -427,6 +581,8 @@ Golden tests for:
427581

428582
## Implementation sequence
429583

584+
Before starting item 1, complete the P0 hardening gates above. P1 gates should be completed before broad-site compatibility evaluation, and P2 gates should be tracked as required coverage for Phase 2 acceptance.
585+
430586
1. Add rewriter mode configuration and diagnostics plumbing.
431587
2. Add Rust SWC rewriter crate and WASM build target.
432588
3. Load rewriter WASM from the Service Worker and expose `rewriteScript()`.
@@ -439,6 +595,7 @@ Golden tests for:
439595
10. Add worker/importScripts/blob/data script rewriting.
440596
11. Add browser E2E tests and hostile escape fixtures.
441597
12. Tighten CSP once compatibility data is available.
598+
13. Re-run and update the pre-Phase-2 hardening gates; no P0 item may remain open for strict mode.
442599

443600
## Acceptance criteria
444601

@@ -453,3 +610,4 @@ Phase 2 is accepted when:
453610
- `gosuda.org` click navigation and language dropdown navigation remain on proxy-origin `/p` routes.
454611
- Hostile tests for `window['loca' + 'tion']`, `Reflect.get`, iframe clean realms, and constructor-constructor dynamic code do not expose a native direct-egress path.
455612
- All strict-mode rewrite failures produce safe ZeroProxy errors instead of executing original target code.
613+
- P0 hardening gates are complete: strict CSP, Service Worker message capabilities, blob/data script handling, and fail-closed dynamic compilation paths.

0 commit comments

Comments
 (0)