Sideload handles OAuth tokens for Spotify and YouTube Music. If you find a vulnerability, email marctorrelles@gmail.com with steps to reproduce. Please do not open a public issue. You will get a reply within 7 days.
What we store: see the Privacy page (/privacy). Tokens are AES-GCM encrypted at rest inside a per-job Durable Object and deleted when the job finishes (Spotify) or within 24 hours (YouTube Music).
What the Worker guarantees and how to see it. Rerun after any change to auth, cookies, headers or rate limits: the curl lines run against production, the rest against the checkout. Last full run: 2026-09-03.
- Cookies carry the
__Host-prefix,HttpOnly; Secure; SameSite=Laxand a short lifetime.curl -si -X POST https://sideload.marctorrelles.com/auth/google/start -H 'Sec-Fetch-Site: same-origin' | grep -i set-cookie→__Host-sl_o=…; Max-Age=900; Path=/; HttpOnly; Secure; SameSite=Lax. - CSRF: state-changing routes refuse cross-site requests. The same request with
-H 'Sec-Fetch-Site: cross-site'→403 {"error":"cross_site"}. - OAuth state is checked before any token exchange.
curl -si 'https://sideload.marctorrelles.com/auth/spotify/callback?code=x&state=bad'→302to/connect?spotify_error=state_mismatchand noset-cookie. - No open redirect: the callback only ever redirects to
/connect.grep -n 'c.redirect(' worker/src/index.tsshows two fixed paths and no user input. - Job ids are 26 Crockford characters, 128 bits (
ID_REinworker/src/crypto.ts), logged as their first 6 characters only;Referrer-Policy: strict-origin-when-cross-originkeeps/t/:idout of third-party referrers and outbound links carryrel="noopener noreferrer". - Tokens at rest are sealed: the JobDO test reads
spotify_tokensstraight out of SQLite and asserts it is not plaintext. Secrets exist only aswrangler secret;git check-ignore .dev.varsprints the file. - Tokens in transit go only to
accounts.spotify.com,api.spotify.com,oauth2.googleapis.com,www.googleapis.com,music.youtube.comandwww.youtube.com.grep -rohE 'https://[a-z0-9.-]+' worker/src/*.ts | sort -ulists exactly those. - Headers:
curl -sI https://sideload.marctorrelles.com/→ HSTS with preload,x-content-type-options: nosniff,Referrer-Policy,Permissions-Policy,content-security-policy: frame-ancestors 'none'; the HTML carries Astro's<meta http-equiv="content-security-policy">withscript-src 'self' 'sha256-…'and nounsafe-inline. - Rate limits (
wrangler.jsonc): 10 auth starts and 3 job creations per minute, 60 reads per 10 s, 20 searches per minute, keyed by client IP. Cloudflare's counters are not shared across the machines in a location, so a probe must reuse one connection:curl -s -o /dev/null -w '%{http_code}\n' 'https://sideload.marctorrelles.com/api/session?n=[1-70]' | sort | uniq -c→ about 60200and the rest429. Separate connections spread across machines and each machine sees a fraction of the traffic: the limits are a brake on scripts, not a hard cap. - Input caps:
POST /api/jobsanswers413to more than 500 playlists or 2,000 albums or artists,400to a negativetrackCount, a bad id, a name over 200 characters or an empty selection; manual search queries are cut to 200 characters.worker/test/routes-validate.test.tscovers each (the route checks the session first, so an unauthenticated curl sees401). - Robots:
curl -sI https://sideload.marctorrelles.com/t/01arz3ndektsv4rrffq69g5fav→x-robots-tag: noindex;/robots.txtdisallows/t/,/connect,/select,/api/and/auth/(Cloudflare prepends its managed AI-crawler block). - Retention: a finished job drops
spotify_tokensat once,yt_tokens24 hours later and the whole record after 7 days; a failed job drops both at once. The end-to-end JobDO test fast-forwards both alarms and asserts each step. - Logs are structured JSON with an
evt, job ids cut to 6 characters and error strings capped;grep -n 'console\.' worker/src/*.tsshows nothing else. - Dependencies:
pnpm audit --prodis clean, versions are pinned by the lockfile, Dependabot alerts are on for the repository.