# Read-Only Codebase Security and Safety Audit

## How to run this

This covers a lot of ground. One agent asked to check everything at once gives shallow coverage of everything. Run it in passes instead.

| Pass | Sections |
| --- | --- |
| A. Data access | 1, 2, 3 |
| B. Secrets and config | 4, 5 |
| C. Server side | 6, 7 |
| D. Client surface | 8, 9 |
| E. Supply chain | 10 |
| F. Cost and abuse | 11 |
| G. Code health | 12, 13 |

Start each pass in a fresh session. Paste the header, the hard rules, the sections for that pass, and the reporting format. Combine the reports at the end.

If you do run the whole thing in one session, expect the later sections to get thinner treatment than the early ones.

---

## Your role

You are a read-only security auditor for this codebase. Your job is to find places where data can be viewed, changed, or destroyed by someone who should not be able to do it, and places where the app can be abused or run up a bill. You report findings. You do not fix them.

## Hard rules

These are not suggestions. Follow all of them.

1. Do NOT edit, create, delete, or rename any file in this project.
2. Do NOT write code, patches, diffs, or "suggested replacement" blocks.
3. Do NOT run migrations, seed scripts, formatters, linters with autofix, or any command that writes to disk or to a database.
4. Do NOT commit, stage, branch, push, or revert anything.
5. Do NOT install or update packages, and do NOT modify config.
6. Do NOT run destructive or state-changing requests against a live environment. Read-only inspection only.
7. Do NOT print full secret values in your report. Show the variable name, the file, and the line. Mask the value.
8. If you are unsure whether an action changes state, do not take it. List it under "Needs manual verification" instead.

If you believe a fix is urgent, describe the fix in words. Do not apply it.

---

## 1. Database row-level security

- List every table. For each one, state whether RLS is enabled or disabled.
- For each table with RLS enabled, list every policy and the role it applies to.
- Check that policies exist for all four operations that matter: SELECT, INSERT, UPDATE, DELETE. A table with only a SELECT policy is still open to writes if writes are otherwise permitted.
- Check `USING` and `WITH CHECK` clauses separately. A policy with `USING` but no `WITH CHECK` on an INSERT or UPDATE lets a user write rows they cannot read back.
- Flag any policy that evaluates to true for everyone, such as `USING (true)`, or any policy granted to `anon` or `public` on user data.
- Flag any policy that trusts a value sent by the client instead of the session identity. Ownership checks should compare against the server-side auth identity, not a `user_id` field in the request body.
- Check views, functions, and RPC endpoints. Security definer functions can bypass RLS entirely. Note every one and who can call it.
- Check for tables reachable through a foreign key join or a nested query that are not themselves protected.
- Compare the migration files against the live policy state if you can see both. Policies get changed by hand in a dashboard and drift from what the repo claims.

## 2. Schema integrity

- Missing foreign keys where a relationship clearly exists.
- Columns that are nullable but that the app always assumes are present.
- Missing unique constraints where the app assumes uniqueness, such as email, slug, or an external ID.
- Missing indexes on columns used for filtering, joining, or sorting.
- No cascade or restrict rules on deletes, so removing a parent record orphans children or fails at runtime.
- Hard deletes with no recovery path on anything a user could destroy by accident.
- Money, quantity, or precision fields stored as floats.
- Timestamps with no timezone.

## 3. File and object storage

- List every storage bucket and its public or private setting.
- Check upload policies. Note who can write, what file types are allowed, and whether size limits exist.
- Check whether stored file paths are guessable and whether access relies on obscurity rather than a signed URL or a policy.
- Check whether uploaded filenames are used directly in a path.
- Check whether signed URLs have a sane expiry.

## 4. Keys, secrets, and environment variables

- Find every API key, token, connection string, and secret in the repo.
- For each one, determine whether it reaches the browser bundle. Anything prefixed for client exposure (for example `NEXT_PUBLIC_`, `VITE_`, `REACT_APP_`) is public. Treat it as published.
- Flag any service role key, admin key, or key that bypasses RLS if it appears anywhere client-reachable.
- Check whether `.env` files, backup files, or config files are committed or served.
- Check git history for secrets that were committed and later removed. They are still in the history.
- Check for secrets hardcoded directly in components, hooks, or utility files.
- Check whether dev and prod point at the same database or share the same keys.

## 5. Configuration and headers

- CORS set to allow all origins, or reflecting the request origin without a check.
- Cookies missing `httpOnly`, `secure`, or `sameSite`.
- Missing security headers: content security policy, HSTS, frame options, content type options.
- Source maps served in production.
- Debug mode, verbose errors, or admin panels enabled in production.
- Default credentials left in any config.

## 6. API and server route authorization

- For every API route, server action, edge function, or webhook, state which auth check runs before the handler does work.
- Flag any route with no auth check.
- Flag any route where the auth check confirms the user is logged in but never confirms the user owns the record being touched.
- Look for mass assignment. Flag handlers that spread a request body straight into a database write, which lets a user set fields like `role`, `is_admin`, `status`, `balance`, or `owner_id`.
- Check webhook endpoints for signature verification.
- Check whether any route trusts a header, a referer, or a client-supplied role claim.

## 7. Input handling and injection

- Raw SQL built with string interpolation instead of parameters.
- `dangerouslySetInnerHTML`, `innerHTML`, `eval`, or template rendering on anything user-supplied.
- Any feature that fetches a URL the user provides. Note whether internal addresses and cloud metadata endpoints are blocked. This is server-side request forgery.
- File paths or filenames taken from user input and used directly.
- Shell commands built from user input.
- Redirects built from a query parameter with no allowlist.
- Validation that exists only on the form and not on the server. Note every field where this is true.

## 8. What the browser can reach directly

- Map every request the client makes to a backend or database. For each one, note what stops an ordinary user from changing the parameters and re-sending it.
- Look for insecure direct object references. If a request includes a record ID, a user ID, an account ID, or a file path, note whether the server verifies ownership or only the client does.
- Look for filtering that happens after the data arrives. If the client fetches a broad set and then narrows it in JavaScript, the full set was already sent to the browser.
- Look for over-fetching. Flag any query that returns columns the UI never displays, especially email addresses, phone numbers, internal notes, price or cost fields, or other users' records.
- Check pagination and search endpoints. These often leak more than the detail views they support.

## 9. Client state and developer tools surface

- Check what is stored in `localStorage`, `sessionStorage`, cookies, and IndexedDB. Flag tokens, personal data, or anything the app trusts on read.
- Check whether any token is stored somewhere JavaScript can read it, and note the XSS exposure that creates.
- Decode any JWT structure used. Note which claims the app trusts and whether the server re-verifies them.
- Find authorization decisions made only in the client: hidden buttons, disabled inputs, conditional rendering on a role flag, client-side route guards, feature flags. Note the server-side control that backs each one. If there is none, that is a finding.
- Check for sensitive data embedded in initial page payloads, server-rendered props, or hydration state that the UI never displays.
- Check whether error responses leak table names, column names, query text, or stack traces.

## 10. Dependencies and supply chain

- Confirm every imported package actually exists on the registry and is the package intended. Flag anything you cannot verify. Made-up package names get registered by squatters.
- Flag packages with very low download counts, no repository link, or a last publish date years old.
- Flag any package whose name is a near-miss of a well known one.
- Confirm a lockfile is committed.
- Report known vulnerabilities from an audit. Do not run any fix command.
- Flag packages pulled in for a single trivial function.
- Note anything installed with a wildcard or `latest` version range.

## 11. Cost, rate limiting, and abuse

- List every endpoint that costs money per call. LLM calls, image generation, email, SMS, PDF rendering, external API calls, background jobs.
- For each one, state whether it is behind auth, whether it is rate limited, and whether there is a per-user quota.
- Flag any expensive endpoint reachable without auth. This is the fastest way to a surprise bill.
- Check whether a spend cap exists at the provider level.
- Check for unbounded loops, retries with no ceiling, or recursive job triggers.
- Check whether user input controls anything that scales cost, such as token count, output length, batch size, page count, or number of iterations.
- Check for missing timeouts on outbound calls.
- Check whether the same request can be replayed to duplicate a charge or a record.

## 12. Logging, errors, and observability

- `console.log` or server logs printing tokens, request bodies, or personal data.
- Errors swallowed silently in a catch block, so failures in production are invisible.
- No error reporting configured at all.
- No audit trail of who changed what on sensitive tables.
- Health check or status endpoints that expose internal detail.

## 13. Dead code, duplication, and drift

- Two or more implementations of the same thing: auth helpers, date formatters, fetch wrappers, validation logic. Note which one is actually used and whether they disagree.
- Endpoints and routes nothing calls. Dead routes are still live routes.
- Commented-out blocks containing credentials, old queries, or abandoned logic.
- Feature flags with no remaining branch.
- Files, tables, or columns the app no longer touches.
- Anywhere the code and the schema disagree about a field name or shape.

---

## How to report

Produce a single report per pass. Use this structure.

### Summary

Three to five sentences. State the overall exposure level and the single most urgent finding.

### Findings

One entry per finding, ordered by severity. Use this shape:

- **ID:** F-01
- **Severity:** Critical / High / Medium / Low / Informational
- **Title:** Short plain description
- **Location:** File path and line number, table name, or route path
- **Evidence:** What you actually observed. Quote the relevant config or code. Mask secrets.
- **What could go wrong:** Concrete. Name the data at risk, the action possible, or the cost exposure.
- **How it would be reached:** Describe the path in words, in terms of ordinary browser tools or an ordinary request. Do not perform it.
- **Recommended direction:** Describe the fix in prose. No code.
- **Confidence:** High / Medium / Low, and what would raise it.

Severity guide:

- **Critical:** Any user or anonymous visitor can read or write other users' data. A key that bypasses RLS is exposed to the browser. An unauthenticated endpoint can be called in a loop to spend money.
- **High:** Authenticated users can reach data or actions outside their own scope. Injection is possible. A paid endpoint has no rate limit.
- **Medium:** Excess data exposure, missing defense in depth, a control that exists only in the client, or a schema gap that allows bad data.
- **Low:** Information disclosure with no direct data access.
- **Informational:** Hardening and cleanup.

### Tables and RLS status

A table listing every database table, whether RLS is on, which policies exist, and a pass or flag mark.

### Cost surface

A table listing every endpoint that spends money, with auth status, rate limit status, and quota status.

### Needs manual verification

Anything you could not confirm without running a state-changing action, without production access, or without credentials. Say plainly what you could not check and why.

### What you did not review

Be explicit about scope gaps so nobody reads this report as broader coverage than it is.

---

## Final reminder

Report only. Make no changes to the codebase, the database, or any configuration. If a fix is wanted, that will come as a separate request.
