Astro + Turso + Cloudflare Pages security playbook
Astro + Turso + Cloudflare is the rising edge stack for content-heavy apps. The fast cold-start story is the draw; the security story is straightforward if you keep Turso credentials server-only and audit island prop pass-throughs for accidentally-shared secrets.
What breaks on this stack
Server secret leaked to island via prop
Island components receive props at hydration time; passing TURSO_AUTH_TOKEN to a client island ships it to every visitor. Keep server-side fetch server-only; pass display-data only to islands.
Read the guide →Turso connection string in PUBLIC_ env
PUBLIC_-prefixed env vars ship to client. TURSO_DATABASE_URL must NOT have PUBLIC_ prefix.
Read the guide →Endpoints without auth
Astro Endpoints (/pages/api) accept any caller unless the handler checks session.
Read the guide →BOLA on dynamic [id] routes
Dynamic routes returning by-id without ownership check — same fix as Next.js BOLA.
Read the guide →Cloudflare Worker bindings over-broad
Bindings configured in wrangler.toml grant scope to the Worker; over-broad scope = downstream blast radius.
Read the guide →Pre-ship checklist
- Turso credentials server-only (no PUBLIC_ prefix)
- Astro island props reviewed for inadvertent secret pass-through
- Endpoints require session check
- Dynamic-route ownership checks present
- Cloudflare bindings scoped per route
- wrangler.toml committed but binding-secrets not (env-driven)
- Security headers via astro.config.mjs
- Rate limits on paid-API proxy routes
Starter config
// astro.config.mjs - security headers + edge runtime
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
export default defineConfig({
output: "server",
adapter: cloudflare({ mode: "directory" }),
vite: { ssr: { noExternal: ["@libsql/client"] } },
server: {
headers: {
"Strict-Transport-Security": "max-age=63072000; includeSubDomains; preload",
"X-Frame-Options": "DENY",
"X-Content-Type-Options": "nosniff",
"Referrer-Policy": "strict-origin-when-cross-origin",
},
},
});