Summary
Download tokens are signed with an HMAC key resolved in backend/src/lib/downloadTokens.ts. If neither DOWNLOAD_SIGNING_SECRET nor SUPABASE_SECRET_KEY is set, the signing key falls back to the literal string "dev-secret":
function getSecret(): string {
return (
process.env.DOWNLOAD_SIGNING_SECRET ??
process.env.SUPABASE_SECRET_KEY ??
"dev-secret"
);
}
Because this codebase is public, the fallback value is known to anyone. An attacker who determines that a deployment is running without these env vars can compute valid HMAC signatures for any storage path they can guess or enumerate, producing forged download tokens that the /download/:token route will accept.
Impact
- Any authenticated user (or unauthenticated user if the route were ever relaxed) can forge a download token for an arbitrary R2 storage path without possessing the real secret.
- The /download/:token route still requires a valid Supabase session, so this does not enable fully unauthenticated access — but it does break the token's purpose of scoping downloads to authorised files.
- The companion risk: DOWNLOAD_SIGNING_SECRET is not listed in .env.example, so a deployer following the README will not know to set it, and the fallback to SUPABASE_SECRET_KEY (if set) silently doubles that key's role without documentation.
Steps to reproduce
- Deploy the backend without setting DOWNLOAD_SIGNING_SECRET or SUPABASE_SECRET_KEY.
- Compute HMAC-SHA256("dev-secret", ) for a payload {"p":"","f":""}.
- Issue a GET /download/ with a valid Supabase session.
- Observe the file is served without the token having been issued by the server.
Proposed fix
- Remove the "dev-secret" fallback entirely. If neither env var is set, fail fast at startup rather than silently degrading:
function getSecret(): string {
const secret =
process.env.DOWNLOAD_SIGNING_SECRET ??
process.env.SUPABASE_SECRET_KEY;
if (!secret) throw new Error(
"DOWNLOAD_SIGNING_SECRET must be set"
);
return secret;
}
- Add DOWNLOAD_SIGNING_SECRET to backend/.env.example with a clear comment explaining it should be a randomly generated secret distinct from the Supabase key.
- Ideally, use a dedicated secret rather than reusing SUPABASE_SECRET_KEY, which already carries significant privilege.
Summary
Download tokens are signed with an HMAC key resolved in backend/src/lib/downloadTokens.ts. If neither DOWNLOAD_SIGNING_SECRET nor SUPABASE_SECRET_KEY is set, the signing key falls back to the literal string "dev-secret":
Because this codebase is public, the fallback value is known to anyone. An attacker who determines that a deployment is running without these env vars can compute valid HMAC signatures for any storage path they can guess or enumerate, producing forged download tokens that the /download/:token route will accept.
Impact
Steps to reproduce
Proposed fix