import { afterEach, describe, expect, it, vi } from "vitest"; import { env } from "cloudflare:workers"; import { createExecutionContext, waitOnExecutionContext } from "cloudflare:test"; import worker from "../src/index"; import { assertSameOrigin, parseHeaderObject, readBareHeaders, setBareHeaders, validateTarget } from "../src/policy"; import { authenticated, unlock } from "../src/session"; import { requestBodyForHttp1 } from "../public/body.js"; import { renderRelayEndpoint, useRenderRelay } from "../src/render"; import { jimmyRelayEndpoint, useJimmyRelay } from "../src/jimmy"; const secret="test-only-passphrase-at-least-24-characters"; const allowed="discord.com,discord.gg,hcaptcha.com"; const testEnv={...env,PROXY_PASSWORD:secret}; const context=()=>createExecutionContext(); afterEach(()=>vi.unstubAllGlobals()); describe("Destination policy",()=>{ it("accepts Discord domains and genuine subdomains",()=>{ expect(validateTarget("https://discord.com/api/v10/gateway",allowed).hostname).toBe("discord.com"); expect(validateTarget("wss://gateway.discord.gg/?v=10",allowed,true).hostname).toBe("gateway.discord.gg"); }); it.each(["https://discord.com.evil.example/","https://evildiscord.com/","https://127.0.0.1/", "https://169.254.169.254/","https://discord.com:8443/","https://user:pass@discord.com/", "file:///etc/passwd","http://discord.com/","ftp://discord.com/"])("blocks %s",(url)=>{ expect(()=>validateTarget(url,allowed)).toThrow(); }); it("permits the public HTTPBingo fixture only on loopback development",async()=>{ const target=encodeURIComponent("https://httpbingo.org/html"); const local=await worker.fetch( new Request("http://localhost/api/target?url="+target),testEnv,context() ); expect(local.status).toBe(200); const session=await unlock(new Request("https://relay.example/api/session",{ method:"POST",body:new URLSearchParams({password:secret}), }),secret); const online=await worker.fetch(new Request( "https://relay.example/api/target?url="+target,{ headers:{Cookie:session.headers.get("set-cookie")!} } ),testEnv,context()); expect(online.status).toBe(403); }); it("rejects cross-site use of the relay",()=>{ expect(()=>assertSameOrigin(new Request("https://relay.example/bare/",{headers:{Origin:"https://elsewhere.example"}}))).toThrow(); }); it("does not copy ambient proxy cookies or authorization headers",async()=>{ const mock=vi.fn(async(input,init)=>{ const h=new Headers(init.headers); expect(h.get("cookie")).toBeNull(); expect(h.get("authorization")).toBeNull(); expect(h.get("cf-access-jwt-assertion")).toBeNull(); return new Response("ok"); }); vi.stubGlobal("fetch",mock); const r=await worker.fetch(new Request("http://localhost/bare/v3/",{ headers:{"x-bare-url":"https://discord.com/","x-bare-headers":"{}", Cookie:"__Host-relay-session=private",Authorization:"Bearer proxy-private","cf-access-jwt-assertion":"private"}, }),testEnv,context()); expect(await r.text()).toBe("ok"); expect(mock).toHaveBeenCalledOnce(); }); }); describe("Bare HTTP transport",()=>{ it("forwards original status, multiple cookies, binary bytes and POST body",async()=>{ const mock=vi.fn(async(input,init)=>{ expect(String(input)).toBe("https://discord.com/api/v10/test"); expect(init.method).toBe("POST"); expect(new Headers(init.headers).get("authorization")).toBe("Bearer example-fixture"); expect(await new Response(init.body).text()).toBe('{"hello":"world"}'); const headers=new Headers({"content-type":"application/octet-stream"}); headers.append("set-cookie","first=one; Path=/; HttpOnly"); headers.append("set-cookie","second=two; Path=/; Secure"); return new Response(new Uint8Array([0,255,4,128]),{status:201,statusText:"Created",headers}); }); vi.stubGlobal("fetch",mock); const response=await worker.fetch(new Request("http://localhost/bare/v3/",{ method:"POST",headers:{"x-bare-url":"https://discord.com/api/v10/test", "x-bare-headers":JSON.stringify({Authorization:"Bearer example-fixture","content-type":"application/json"})}, body:'{"hello":"world"}', }),testEnv,context()); expect(response.status).toBe(200); expect(response.headers.get("x-bare-status")).toBe("201"); expect(response.headers.get("set-cookie")).toBeNull(); expect(JSON.parse(response.headers.get("x-bare-headers")!)["set-cookie"]).toHaveLength(2); expect([...new Uint8Array(await response.arrayBuffer())]).toEqual([0,255,4,128]); }); it("returns redirects as metadata without following to another host",async()=>{ vi.stubGlobal("fetch",vi.fn(async(input,init)=>{ expect(init.redirect).toBe("manual"); return new Response(null,{status:302,headers:{location:"https://example.invalid/"}}); })); const r=await worker.fetch(new Request("http://localhost/bare/v3/",{ headers:{"x-bare-url":"https://discord.com/","x-bare-headers":"{}"}, }),testEnv,context()); expect(r.headers.get("x-bare-status")).toBe("302"); expect(JSON.parse(r.headers.get("x-bare-headers")!).location).toBe("https://example.invalid/"); }); it("round-trips split response metadata and refuses broken request sequences",()=>{ const input=new Headers({"x-long":"A".repeat(7000),"set-cookie":"foo=bar"}); const output=new Headers(); setBareHeaders(output,input); const rejoined=readBareHeaders(output); expect(rejoined.get("x-long")).toHaveLength(7000); expect(()=>readBareHeaders(new Headers({"x-bare-headers-1":';{"a":"b"}'}))).toThrow(); }); it("strips transport and Cloudflare Access headers from requested headers",()=>{ const h=parseHeaderObject({Host:"evil",Connection:"upgrade","cf-access-client-secret":"secret",Cookie:"discord=session",Origin:"https://discord.com"}); expect(h.get("host")).toBeNull(); expect(h.get("cf-access-client-secret")).toBeNull(); expect(h.get("cookie")).toBe("discord=session"); expect(h.get("origin")).toBe("https://discord.com"); }); it("returns a controlled error for malformed metadata",async()=>{ const r=await worker.fetch(new Request("http://localhost/bare/v3/",{ headers:{"x-bare-url":"https://discord.com","x-bare-headers":"{broken"}, }),testEnv,context()); expect(r.status).toBe(400); }); }); describe("Private online sessions",()=>{ it("rejects an oversized unlock form before reading it all",async()=>{ const r=await unlock(new Request("https://relay.example/api/session",{method:"POST",body:"password="+"x".repeat(2048)}),secret); expect(r.status).toBe(413); }); it("fails closed online without a configured secret",async()=>{ expect(await authenticated(new Request("https://relay.example/"))).toBe(false); }); it("allows development only on loopback hostnames",async()=>{ expect(await authenticated(new Request("http://localhost/"))).toBe(true); expect(await authenticated(new Request("https://localhost.evil.example/"))).toBe(false); }); it("signs a secure cookie, rejects a tampered cookie, and never stores the password in it",async()=>{ const response=await unlock(new Request("https://relay.example/api/session",{ method:"POST",body:new URLSearchParams({password:secret}), }),secret); expect(response.status).toBe(204); const cookie=response.headers.get("set-cookie")!; expect(cookie).toContain("HttpOnly; SameSite=Strict"); expect(cookie).not.toContain(secret); expect(await authenticated(new Request("https://relay.example/",{headers:{cookie}}),secret)).toBe(true); const token=cookie.split(";")[0]; const tampered=token.slice(0,-1)+(token.endsWith("a")?"b":"a"); expect(await authenticated(new Request("https://relay.example/",{headers:{cookie:tampered}}),secret)).toBe(false); }); it("remembers an opted-in device for 30 days and rejects it after expiry",async()=>{ const response=await unlock(new Request("https://relay.example/api/session",{method:"POST",body:new URLSearchParams({password:secret,remember:"1"})}),secret); const cookie=response.headers.get("set-cookie")!; expect(cookie).toContain("Max-Age=2592000"); expect(cookie).toContain("Secure; HttpOnly; SameSite=Strict"); expect(cookie).not.toContain(secret); const request=new Request("https://relay.example/",{headers:{cookie}}); const now=Date.now(); const clock=vi.spyOn(Date,"now"); try { clock.mockReturnValue(now+2*86400000); expect(await authenticated(request,secret)).toBe(true); clock.mockReturnValue(now+31*86400000); expect(await authenticated(request,secret)).toBe(false); } finally { clock.mockRestore(); } }); it("keeps the ordinary unlock limited to one day",async()=>{ const response=await unlock(new Request("https://relay.example/api/session",{method:"POST",body:new URLSearchParams({password:secret,remember:"0"})}),secret); expect(response.headers.get("set-cookie")).toContain("Max-Age=86400"); }); it("rejects a wrong passphrase",async()=>{ const r=await unlock(new Request("https://relay.example/api/session",{method:"POST",body:"password=wrong"}),secret); expect(r.status).toBe(401); }); it("requires a session for online HTTP and WebSocket relay routes",async()=>{ const r=await worker.fetch(new Request("https://relay.example/bare/v3/"),testEnv,context()); expect(r.status).toBe(401); }); }); describe("Browser request bodies over localhost HTTP/1",()=>{ it("preserves binary chunks while converting a stream to a native Blob",async()=>{ const stream=new ReadableStream({start(controller){ controller.enqueue(new Uint8Array([0,255])); controller.enqueue(new Uint8Array([17,128])); controller.close(); }}); const body=await requestBodyForHttp1(stream,undefined,4); expect([...new Uint8Array(await body.arrayBuffer())]).toEqual([0,255,17,128]); }); it("cancels the source when its explicit upload cap is exceeded",async()=>{ const cancel=vi.fn(); const stream=new ReadableStream({start(controller){controller.enqueue(new Uint8Array(5));},cancel}); await expect(requestBodyForHttp1(stream,undefined,4)).rejects.toThrow("uploads"); expect(cancel).toHaveBeenCalledOnce(); }); it("respects an already-aborted upload",async()=>{ const control=new AbortController(); control.abort(); await expect(requestBodyForHttp1(new ReadableStream(),control.signal)).rejects.toThrow("Aborted"); }); }); function nextMessage(socket:WebSocket):Promise { return new Promise((resolve,reject)=>{ const timer=setTimeout(()=>reject(new Error("No message received")),3000); socket.addEventListener("message",event=>{clearTimeout(timer);resolve(event.data);},{once:true}); }); } describe("WebSocket relay in the Workers runtime",()=>{ it("sends open metadata first and preserves text and binary frames",async()=>{ let upstreamSignal: AbortSignal | undefined; const pair=new WebSocketPair(); pair[1].binaryType="arraybuffer"; pair[1].accept(); pair[1].addEventListener("message",event=>pair[1].send(event.data)); vi.stubGlobal("fetch",vi.fn(async(input,init)=>{ upstreamSignal=init.signal; expect(String(input)).toBe("https://gateway.discord.gg/?v=10"); expect(new Headers(init.headers).get("upgrade")).toBe("websocket"); return new Response(null,{status:101,webSocket:pair[0]}); })); const ctx=context(); const r=await worker.fetch(new Request("http://localhost/bare/v3/",{headers:{Upgrade:"websocket"}}),testEnv,ctx); expect(r.status).toBe(101); const client=r.webSocket!; client.binaryType="arraybuffer"; client.accept(); const opening=nextMessage(client); client.send(JSON.stringify({type:"connect",remote:"wss://gateway.discord.gg/?v=10",protocols:[],headers:{Origin:"https://discord.com"}})); expect(JSON.parse(await opening as string)).toEqual({type:"open",protocol:"",setCookies:[]}); // Regression: the original fetch deadline also aborted established sockets. await new Promise(resolve=>setTimeout(resolve,10_100)); expect(upstreamSignal?.aborted).toBe(false); const text=nextMessage(client); client.send("hello"); expect(await text).toBe("hello"); const binary=nextMessage(client); client.send(new Uint8Array([0,255,17]).buffer); const received = await binary; expect([...new Uint8Array(received as ArrayBuffer)]).toEqual([0,255,17]); client.close(); await waitOnExecutionContext(ctx); },20_000); it("closes invalid handshakes without contacting an upstream",async()=>{ const mock=vi.fn(); vi.stubGlobal("fetch",mock); const ctx=context(); const r=await worker.fetch(new Request("http://localhost/bare/v3/",{headers:{Upgrade:"websocket"}}),testEnv,ctx); const client=r.webSocket!; client.accept(); const closed=new Promise(resolve=>client.addEventListener("close",event=>resolve(event.code),{once:true})); client.send(JSON.stringify({type:"connect",remote:"wss://127.0.0.1/",protocols:[],headers:{}})); expect(await closed).toBe(1008); expect(mock).not.toHaveBeenCalled(); await waitOnExecutionContext(ctx); }); }); describe("Embedded relay and destination checks",()=>{ it("only allows framing the dedicated embedded entry",async()=>{ const fetch=vi.fn(async()=>new Response("hub",{headers:{"x-frame-options":"SAMEORIGIN"}})); const embeddedEnv={...testEnv,ASSETS:{fetch} as unknown as Fetcher}; const r=await worker.fetch(new Request("https://relay.example/embed?target=https%3A%2F%2Fexample.com"),embeddedEnv,context()); expect(r.headers.get("x-frame-options")).toBeNull();expect(String(fetch.mock.calls[0][0].url)).toBe("https://relay.example/"); const root=await worker.fetch(new Request("https://relay.example/"),embeddedEnv,context());expect(root.headers.get("x-frame-options")).toBe("SAMEORIGIN"); }); it("keeps the embedded session partitioned and HttpOnly",async()=>{ const r=await unlock(new Request("https://relay.example/api/session?embedded=1",{method:"POST",body:new URLSearchParams({password:secret})}),secret); expect(r.headers.get("set-cookie")).toContain("SameSite=None; Partitioned");expect(r.headers.get("set-cookie")).toContain("Secure; HttpOnly"); }); it("validates destinations before launching the engine",async()=>{ const good=await worker.fetch(new Request("http://localhost/api/target?url=https%3A%2F%2Fdiscord.com"),testEnv,context());expect(good.status).toBe(200); const bad=await worker.fetch(new Request("http://localhost/api/target?url=https%3A%2F%2F127.0.0.1"),testEnv,context());expect(bad.status).toBe(403); }); it("requires authentication for destination checks online",async()=>{const r=await worker.fetch(new Request("https://relay.example/api/target?url=https%3A%2F%2Fdiscord.com"),testEnv,context());expect(r.status).toBe(401);}); }); describe("Optional Render relay configuration",()=>{ const configured={RENDER_RELAY_URL:"https://sample-relay.onrender.com",RELAY_SHARED_SECRET:"test-only-shared-secret-of-at-least-32-characters"}; it("stays disabled unless both settings are present",()=>{ expect(renderRelayEndpoint({})).toBeNull(); expect(renderRelayEndpoint({...configured,RELAY_SHARED_SECRET:"short"})).toBeNull(); expect(renderRelayEndpoint(configured)?.href).toBe("https://sample-relay.onrender.com/bare/v3/"); }); it("rejects unsafe Render origins",()=>{ for(const url of ["http://sample-relay.onrender.com","https://user:pass@sample-relay.onrender.com","https://sample-relay.onrender.com/malicious","https://sample-relay.onrender.com:8443"]){ expect(()=>renderRelayEndpoint({...configured,RENDER_RELAY_URL:url})).toThrow(); } }); it("routes only Discord HTTP destinations through Render",()=>{ expect(useRenderRelay(new URL("https://discord.com/api/v10/gateway"),configured)).toBe(true); expect(useRenderRelay(new URL("https://cdn.discord.com/assets/file"),configured)).toBe(true); expect(useRenderRelay(new URL("https://github.com"),configured)).toBe(false); expect(useRenderRelay(new URL("https://discord.com.evil.invalid"),configured)).toBe(false); }); it("keeps the Render shared token on server-side requests",async()=>{ const mock=vi.fn(async(input,init)=>{ expect(String(input)).toBe("https://sample-relay.onrender.com/bare/v3/"); expect(new Headers(init.headers).get("x-relay-token")).toBe(configured.RELAY_SHARED_SECRET); expect(new Headers(init.headers).get("x-bare-url")).toBe("https://discord.com/api/v10/gateway"); return new Response("test-result",{headers:{"x-bare-status":"200","x-bare-headers":"{}"}}); }); vi.stubGlobal("fetch",mock); const result=await worker.fetch(new Request("http://localhost/bare/v3/",{headers:{"x-bare-url":"https://discord.com/api/v10/gateway","x-bare-headers":"{}"}}),{...testEnv,...configured},context()); expect(result.status).toBe(200); expect(await result.text()).toBe("test-result"); expect(result.headers.get("x-relay-token")).toBeNull(); expect(result.headers.get("x-relay-backend")).toBe("render"); }); }); describe("Opt-in Jimmy relay with Render fallback",()=>{ const jimmy={ JIMMY_RELAY_ENABLED:"true", JIMMY_RELAY_URL:"https://node.tailee273e.ts.net/", JIMMY_RELAY_SECRET:"jimmy-test-only-secret-at-least-32-characters", }; const render={ RENDER_RELAY_URL:"https://render-relay.onrender.com/", RELAY_SHARED_SECRET:"render-test-only-secret-at-least-32-characters", }; const bareRequest=(method="GET")=>new Request("http://localhost/bare/v3/",{ method, headers:{"x-bare-url":"https://discord.com/api/v10/gateway","x-bare-headers":"{}"}, ...(method==="POST"?{body:"fixture"}:{}), }); it("requires an explicit toggle, valid secret, and safe HTTPS origin",()=>{ expect(jimmyRelayEndpoint({...jimmy,JIMMY_RELAY_ENABLED:"false"})).toBeNull(); expect(jimmyRelayEndpoint({...jimmy,JIMMY_RELAY_SECRET:"short"})).toBeNull(); expect(jimmyRelayEndpoint(jimmy)?.href).toBe("https://node.tailee273e.ts.net/bare/v3/"); for(const url of ["http://node.tailee273e.ts.net/","https://user:pass@node.tailee273e.ts.net/", "https://node.tailee273e.ts.net:8443/","https://node.tailee273e.ts.net/other", "https://node.tailee273e.ts.net/?token=abc"]){ expect(()=>jimmyRelayEndpoint({...jimmy,JIMMY_RELAY_URL:url})).toThrow(); } expect(useJimmyRelay(new URL("https://discord.com/api"),jimmy)).toBe(true); expect(useJimmyRelay(new URL("https://github.com/"),jimmy)).toBe(false); expect(useJimmyRelay(new URL("https://discord.com.evil.invalid"),jimmy)).toBe(false); }); it("sends the Jimmy token server-side and returns the Jimmy route only",async()=>{ const mock=vi.fn(async(input,init)=>{ expect(String(input)).toBe("https://node.tailee273e.ts.net/bare/v3/"); const h=new Headers(init.headers); expect(h.get("x-relay-token")).toBe(jimmy.JIMMY_RELAY_SECRET); expect(h.get("x-bare-url")).toBe("https://discord.com/api/v10/gateway"); return new Response("from-jimmy",{headers:{"x-bare-status":"200","x-bare-headers":"{}"}}); }); vi.stubGlobal("fetch",mock); const r=await worker.fetch(bareRequest(),{...testEnv,...render,...jimmy},context()); expect(await r.text()).toBe("from-jimmy"); expect(r.headers.get("x-relay-backend")).toBe("jimmy"); expect(r.headers.get("x-relay-token")).toBeNull(); expect(mock).toHaveBeenCalledOnce(); }); it("falls back to Render for GET when Jimmy is unreachable",async()=>{ const mock=vi.fn(async(input,init)=>{ if(String(input).includes("tailee273e.ts.net"))throw new Error("Jimmy unavailable"); expect(String(input)).toBe("https://render-relay.onrender.com/bare/v3/"); expect(new Headers(init.headers).get("x-relay-token")).toBe(render.RELAY_SHARED_SECRET); return new Response("from-render",{headers:{"x-bare-status":"200","x-bare-headers":"{}"}}); }); vi.stubGlobal("fetch",mock); const r=await worker.fetch(bareRequest(),{...testEnv,...render,...jimmy},context()); expect(await r.text()).toBe("from-render"); expect(r.headers.get("x-relay-backend")).toBe("render"); expect(mock).toHaveBeenCalledTimes(2); }); it("does not retry POST requests that might have reached Jimmy",async()=>{ const mock=vi.fn(async()=>{throw new Error("lost upstream connection");}); vi.stubGlobal("fetch",mock); const r=await worker.fetch(bareRequest("POST"),{...testEnv,...render,...jimmy},context()); expect(r.status).toBe(502); expect(mock).toHaveBeenCalledOnce(); }); it("does not mistake a valid upstream 503 for relay unavailability",async()=>{ const mock=vi.fn(async()=>new Response("upstream-status",{ headers:{"x-bare-status":"503","x-bare-headers":"{}"}, })); vi.stubGlobal("fetch",mock); const r=await worker.fetch(bareRequest(),{...testEnv,...render,...jimmy},context()); expect(r.status).toBe(200); expect(r.headers.get("x-bare-status")).toBe("503"); expect(r.headers.get("x-relay-backend")).toBe("jimmy"); expect(mock).toHaveBeenCalledOnce(); }); it("preserves Render when Jimmy is disabled",async()=>{ const mock=vi.fn(async(input)=>{ expect(String(input)).toBe("https://render-relay.onrender.com/bare/v3/"); return new Response("render",{headers:{"x-bare-status":"200","x-bare-headers":"{}"}}); }); vi.stubGlobal("fetch",mock); const r=await worker.fetch(bareRequest(),{...testEnv,...render,...jimmy,JIMMY_RELAY_ENABLED:"false"},context()); expect(r.headers.get("x-relay-backend")).toBe("render"); expect(mock).toHaveBeenCalledOnce(); }); });