Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/security-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This page describes what the code enforces today. Settings that live only in AWS
- `DELETE /api/account?confirm=true` and the guest purge both use `eraseTeam()` (`src/lib/erase-team.ts`). It removes every version and delete marker under the team's S3 prefixes, then deletes the database rows. If the S3 step fails, it stops before touching the database. The guest purge keeps `bedrock_usage` rows so that the shared AI cap and cost reporting stay correct.
- **AI.** Uploaded content is sent to Amazon Bedrock inside `<user_data>` tags, with an instruction to treat it as data. This reduces prompt-injection risk but does not prevent it, which is why rules need approval or a committee vote. `checkAiBudget` (`src/lib/bedrock-meter.ts`) runs in Postgres before every Bedrock call: suggest-transforms, the auto-clean committee, the chat builder and synthetic data. Every call is metered in `bedrock_usage`. Costs are estimates based on the prices in `src/lib/ai-config.ts`.
- **Rate limits.** Upstash sliding windows per user: 20 uploads/h, 50 AI calls/h and 30 chat messages/h. They fail open when Redis is not configured. The guest limits below are enforced in Postgres and do not depend on Upstash.
- **Headers.** `next.config.ts` sets a CSP (`src/lib/csp.ts`). For scripts it allows `'unsafe-inline'` and `'unsafe-eval'`, which Next.js and Clerk need without nonces. It adds the Cloudflare Turnstile origins when Turnstile is configured.
- **Headers.** `next.config.ts` sets a CSP (`src/lib/csp.ts`). For scripts it allows `'unsafe-inline'` and `'unsafe-eval'`, which Next.js and Clerk need without nonces. `worker-src 'self' blob:` lets Clerk start its blob: Web Workers. It adds the Cloudflare Turnstile origins when Turnstile is configured.
- **Secrets.** Nothing secret is committed, and `.env.example` contains only placeholders. AWS account ids and ARNs from older commits remain in git history.

## Guest access
Expand Down
9 changes: 7 additions & 2 deletions src/lib/csp.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { buildCsp } from "@/lib/csp";

// The exact policy shipped before the CSP moved out of next.config.ts.
// The exact production policy (no test flag, no Turnstile).
const PRODUCTION_CSP = [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://clerk.com https://*.clerk.accounts.dev https://js.sentry-cdn.com https://*.sentry.io",
Expand All @@ -10,14 +10,15 @@ const PRODUCTION_CSP = [
"img-src 'self' data: blob: https:",
"connect-src 'self' https://*.sentry.io https://*.ingest.sentry.io https://*.clerk.accounts.dev https://*.clerk.com wss://*.clerk.accounts.dev https://sqs.us-east-1.amazonaws.com https://*.s3.amazonaws.com https://*.s3.us-east-1.amazonaws.com",
"frame-src 'none'",
"worker-src 'self' blob:",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
"upgrade-insecure-requests",
].join("; ");

describe("buildCsp", () => {
it("is byte-identical to the previous production policy without the test flag", () => {
it("is byte-identical to the production policy without the test flag", () => {
expect(buildCsp({})).toBe(PRODUCTION_CSP);
});

Expand All @@ -31,6 +32,10 @@ describe("buildCsp", () => {
expect(csp).not.toContain("upgrade-insecure-requests");
});

it("allows Clerk's blob: workers, and only from self or blob:", () => {
expect(buildCsp({})).toContain("; worker-src 'self' blob:;");
});

it("allows Cloudflare Turnstile only when its site key is set", () => {
const csp = buildCsp({ NEXT_PUBLIC_TURNSTILE_SITE_KEY: "0x4AAAAAAA" });
expect(csp).toContain("frame-src https://challenges.cloudflare.com");
Expand Down
3 changes: 3 additions & 0 deletions src/lib/csp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export function buildCsp(env: EnvSource = process.env): string {
"img-src 'self' data: blob: https:",
`connect-src ${connectSrc.join(" ")}`,
turnstile ? `frame-src ${TURNSTILE_ORIGIN}` : "frame-src 'none'",
// Clerk starts Web Workers from blob: URLs; without this the browser
// falls back to script-src (no blob:) and logs a CSP violation.
"worker-src 'self' blob:",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
Expand Down
Loading