Remove the module "xxx" { source = "./module" } indirection layer
from all 66 service stacks. Resources are now defined directly in
each stack's main.tf instead of through a wrapper module.
- Merge module/main.tf contents into stack main.tf
- Apply variable replacements (var.tier -> local.tiers.X, renamed vars)
- Fix shared module paths (one fewer ../ at each level)
- Move extra files/dirs (factory/, chart_values, subdirs) to stack root
- Update state files to strip module.<name>. prefix
- Update CLAUDE.md to reflect flat structure
Verified: terragrunt plan shows 0 add, 0 destroy across all stacks.
9.4 KiB
| phase | verified | status | score | re_verification |
|---|---|---|---|---|
| 05-sandbox-proxy-hardening | 2026-02-17T23:15:00Z | passed | 8/8 must-haves verified | false |
Phase 5: Sandbox and Proxy Hardening Verification Report
Phase Goal: When direct video extraction fails, the proxied page is rendered safely in a sandbox that blocks popups, ads, and access to the parent page
Verified: 2026-02-17T23:15:00Z
Status: PASSED
Re-verification: No — initial verification
Goal Achievement
Observable Truths
| # | Truth | Status | Evidence |
|---|---|---|---|
| 1 | When direct video extraction fails, the full proxied page renders inside a shadow DOM sandbox on the app's page | ✓ VERIFIED | streams.js line 188: tryExtractVideo calls renderSandboxFallback; line 241: attachShadow({ mode: 'closed' }) |
| 2 | The sandbox blocks window.open, top-frame navigation, popup creation, and alert/confirm/prompt dialogs | ✓ VERIFIED | streams.js lines 252-257: overrides for window.open, alert, confirm, prompt, top, parent all present |
| 3 | The sandbox prevents proxied content from accessing parent page cookies and localStorage | ✓ VERIFIED | streams.js lines 258-260: document.cookie, localStorage, sessionStorage all blocked via Object.defineProperty |
| 4 | Known ad/tracker scripts and domains are stripped from proxied content before serving | ✓ VERIFIED | sanitize.go lines 26-51: removes script/link/img/iframe/object/embed from blocked domains; blocklist.go: 49+ blocked domains |
| 5 | Relative URLs in proxied content are rewritten to route through the proxy | ✓ VERIFIED | sanitize.go lines 114,119: relative URLs rewritten with proxyPrefix + QueryEscape |
| 6 | All proxied content is served with strict CSP headers scoped to the sandbox context | ✓ VERIFIED | proxy.go lines 195,207,216: sandboxCSP header set on all responses from ServeSandbox |
Score: 6/6 truths verified (100%)
Required Artifacts
Plan 05-01 Artifacts
| Artifact | Expected | Status | Details |
|---|---|---|---|
internal/proxy/sanitize.go |
HTML sanitizer that strips ads, rewrites URLs, and adds CSP | ✓ VERIFIED | Exists, 149 lines; exports Sanitize function; contains rewriteAttrs, hostBlocked, resolveURL helpers |
internal/proxy/blocklist.go |
Ad/tracker domain blocklist for script filtering | ✓ VERIFIED | Exists, 98 lines; 49 blocked domains in map; IsBlockedDomain with parent-domain walk-up |
internal/proxy/proxy.go |
New /proxy/sandbox endpoint serving sanitized content | ✓ VERIFIED | Exists; ServeSandbox method at line 140; calls Sanitize at line 213; sets CSP headers |
internal/server/server.go |
Route registration for sandbox proxy endpoint | ✓ VERIFIED | Line 61: route registered "GET /proxy/sandbox" -> s.proxy.ServeSandbox |
Plan 05-02 Artifacts
| Artifact | Expected | Status | Details |
|---|---|---|---|
static/js/streams.js |
Shadow DOM sandbox fallback replacing iframe fallback | ✓ VERIFIED | Exists; renderSandboxFallback at line 230; attachShadow({ mode: 'closed' }) at line 241; sandbox script injection lines 249-263 |
Key Link Verification
| From | To | Via | Status | Details |
|---|---|---|---|---|
internal/server/server.go |
internal/proxy/proxy.go |
route registration for /proxy/sandbox | ✓ WIRED | server.go:61 registers "GET /proxy/sandbox" to s.proxy.ServeSandbox |
internal/proxy/proxy.go |
internal/proxy/sanitize.go |
ServeSandbox calls Sanitize | ✓ WIRED | proxy.go:213 calls Sanitize(doc, parsed, "/proxy/sandbox") |
static/js/streams.js |
/proxy/sandbox |
fetch call to get sanitized HTML for shadow DOM injection | ✓ WIRED | streams.js:235 fetches '/proxy/sandbox?url=' + encodeURIComponent(streamURL) |
static/js/streams.js |
static/js/streams.js |
tryExtractVideo falls back to renderSandboxFallback | ✓ WIRED | streams.js:188 calls renderSandboxFallback when extraction fails |
Requirements Coverage
| Requirement | Source Plan | Description | Status | Evidence |
|---|---|---|---|---|
| EMBED-03 | 05-02 | When direct extraction fails, fall back to rendering the full proxied page in a shadow DOM sandbox | ✓ SATISFIED | tryExtractVideo calls renderSandboxFallback; shadow DOM created with mode: 'closed' |
| EMBED-04 | 05-02 | Shadow DOM sandbox blocks window.open, window.top navigation, popup creation, and alert/confirm/prompt | ✓ SATISFIED | Sandbox script overrides all 7 dangerous APIs (window.open, alert, confirm, prompt, top, parent, location) |
| EMBED-05 | 05-02 | Shadow DOM sandbox prevents access to parent page cookies and localStorage | ✓ SATISFIED | document.cookie returns '', localStorage/sessionStorage return null via Object.defineProperty |
| EMBED-06 | 05-01 | Proxy strips known ad/tracker scripts and domains from proxied content before serving | ✓ SATISFIED | Sanitizer removes script/link/img/iframe/object/embed from 49+ blocked domains |
| EMBED-07 | 05-01 | Proxy rewrites relative URLs in proxied content to route through the proxy | ✓ SATISFIED | rewriteAttrs rewrites src/href/action/poster/data attributes through /proxy/sandbox |
| EMBED-08 | 05-01 | All proxied content served with strict CSP headers scoped to the sandbox context | ✓ SATISFIED | sandboxCSP constant applied to all ServeSandbox responses (HTML and non-HTML) |
Coverage: 6/6 requirements satisfied (100%)
Orphaned requirements: None — all requirements from REQUIREMENTS.md phase 5 mapping are claimed by plans
Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
|---|---|---|---|---|
| - | - | None detected | - | - |
Scanned files:
- internal/proxy/sanitize.go — clean, no placeholders or stubs
- internal/proxy/blocklist.go — clean, 49 real domains
- internal/proxy/proxy.go — clean, full ServeSandbox implementation
- static/js/streams.js — clean, complete shadow DOM sandbox with all overrides
Checks performed:
- No TODO/FIXME/PLACEHOLDER comments found
- No empty return statements (return null, return {}, return [])
- No console.log-only implementations
- All functions have substantive implementations
Human Verification Required
1. Shadow DOM Isolation Effectiveness
Test: Open a stream that fails video extraction. Inspect browser DevTools Console while the sandbox content loads.
Expected:
- No popup windows appear
- No alert/confirm/prompt dialogs appear
- Parent page cookies remain accessible to parent JavaScript (verify document.cookie in parent console)
- Shadow DOM content cannot access parent cookies (blocked by override)
Why human: Requires real browser testing to verify runtime isolation behavior. Cannot be verified by static analysis.
2. Ad/Tracker Blocking Effectiveness
Test: Load a stream page through /proxy/sandbox that is known to have ads (use a popular streaming site). Inspect Network tab.
Expected:
- Requests to doubleclick.net, googlesyndication.com, etc. are NOT made
- Ad scripts stripped from DOM before rendering
- Page still functional (video player scripts kept)
Why human: Requires visual inspection and network monitoring to verify ads are actually blocked without breaking video playback.
3. Relative URL Rewriting
Test: Load a stream page through /proxy/sandbox that uses relative URLs for images, CSS, or sub-resources.
Expected:
- All relative URLs (src="img/logo.png") are rewritten to route through /proxy/sandbox?url=...
- Sub-resources load correctly (no broken images or missing CSS)
- Protocol-relative URLs (//cdn.example.com/style.css) are rewritten
Why human: Requires testing with real stream pages that use various URL patterns. Static analysis confirms the rewrite logic exists but cannot verify it handles all edge cases.
4. CSP Header Enforcement
Test: Open browser DevTools Console while a sandboxed stream loads. Check Network tab response headers.
Expected:
- All /proxy/sandbox responses have Content-Security-Policy header
- Header value matches sandboxCSP constant (default-src 'self'; script-src 'unsafe-inline'; ...)
- CSP violations (if any) appear in console but don't break video playback
Why human: CSP enforcement is browser-side. Need to verify headers are present and effective without breaking player functionality.
5. Graduated Fallback Chain
Test: Test with three types of streams:
- Stream with extractable HLS/MP4 source
- Stream where extraction fails but proxy works
- Stream URL that returns 404/500
Expected:
- Native HTML5 video player renders
- Shadow DOM sandbox renders the proxied page
- "Open stream directly" link appears
Why human: Requires testing multiple failure scenarios to verify the fallback chain works correctly at each level.
Gaps Summary
No gaps found. All must-haves verified, all requirements satisfied, no anti-patterns detected.
Implementation quality:
- ✓ Backend: Complete sanitizer with 49 blocked domains, URL rewriting, and CSP enforcement
- ✓ Frontend: Closed shadow DOM with 7 dangerous API overrides (window.open, alert, confirm, prompt, top, parent, cookie, localStorage, sessionStorage)
- ✓ Wiring: All key links verified (route registration, Sanitize call, fetch to /proxy/sandbox)
- ✓ Fallback: Graduated fallback chain (native player > sandbox > direct link)
Ready to proceed: Yes. Phase 5 goal achieved. All 6 EMBED requirements (EMBED-03 through EMBED-08) satisfied.
Verified: 2026-02-17T23:15:00Z
Verifier: Claude (gsd-verifier)