Security checklist
The checks a change that touches security must pass.
A change that affects input, accounts, stored data, workers, external services, configuration or CI must pass each check below that the change can affect. Record the test or operational evidence in the pull request. If a check does not apply, write one line in the pull request that says why.
Passing these checks does not prove that the product is ready for production. The checks are a review standard. They do not claim that every control is built.
Required checks
- Input validation: Reject invalid types, ranges, text, nesting and body sizes before the data is stored or sent to a worker. Model parameter schemas must be usable. Test rejected inputs and valid boundary values.
- Password hashing: Use the shared salted Argon2id path. Prove that stored values do not contain the password, and that two equal passwords get different salts.
- Secrets outside source: Load live secrets from protected configuration. Scan the working tree and the relevant history without printing any candidate value. Keep fake test values and any scanner allowance explicit. Locally, run
make verify-secrets, and runscripts/secret-scan.sh historyfor the full history. - Server authentication: In accounts mode, check the session, the role, the account state and the ownership on every REST and WebSocket path. Test direct calls that skip the frontend. Keep the CSRF checks on requests that change cookies.
- Login and reset limits: Charge each attempt before any costly or account-specific work. Keep the separate limits for each caller, and keep the reset answer the same for every request. Test overlapping requests, exhausted limits and recovery.
- External call deadlines: Set limits on connection, read, retry and total time where they apply. Prove that a stalled call releases its resources within a stated limit. Ending an await alone does not stop a blocking thread.
- Quota and payment replay: Prove that duplicate, delayed, conflicting and retried events cannot grant, reserve, charge or refund anything twice. Use durable unique keys for each source event and database transactions.
- Transactions: Commit related database writes together, and test the rollback after a failed write. For storage work outside the database, prove that cleanup runs in order and that a retry recovers.
- Query indexes: Check the real filter and sort plans with representative data. Add only the indexes that improve a measured path, and record their cost for writes and storage.
- Versioned migrations: Ship each model change with a versioned migration. Test upgrades from populated, supported schemas, and test the downgrade behavior that the migration declares. Keep the schema metadata in line with the models.
- Safe logs and request IDs: Link each HTTP request to a bounded server request ID. Keep credentials, token URLs and private worker text out of logs and exception output. Prove this with canary values and a check that shows the logging works.
- Store readiness: Prove that
/api/v1/readychecks PostgreSQL and the asset store, and returns 503 when either one is down./api/v1/healthonly shows that the process is running. - Backups and restore: Restore the database, the asset files and the required protected key configuration from one consistent recovery point, into an isolated environment. Prove that an account can sign in and open its saved images. A local restore test proves only the local path.
- Closed CORS: Keep HTTP requests on the same origin unless a named origin is needed. Check the exact allowed origins, the CSRF protection for cookie-changing requests, and the allowed browser origins for WebSockets. CORS does not replace authentication.
- Overload responses: Return 429 when a client goes over its budget. Keep the documented 503 with a
Retry-Afterheader for a busy service. Test that the refusal happens before any costly work, and that the service recovers after the limit clears. - Account isolation: Test with two separate accounts. Record IDs, cursors, source assets and nested results that belong to one account must not show or change another account's data. Test the admin and share permissions on their own.
Scope and accepted exceptions
AUTH_MODE=none, chosen at setup or set by the override, is the trusted-network, single-user mode. An install that has not chosen yet answers only the setup surface. In this mode, the server checks for accounts do not apply. Accounts mode uses the server checks in check 4. Admin access and bearer share links are explicit permissions. They are not failures of the ownership checks.
Worker releases must still work with the previous protocol version (N-1).
When you add a deadline, keep the waits that the project accepts on purpose: the 3 second account lock timeout, the 30 second cap on a later row lock, and the unbounded wait when an operator turns accounts off. A change to an accepted rule needs a new entry in the decisions log.
Recording evidence
Write the evidence in the pull request, in this order:
- The check name.
- The test file or the command you ran.
- The result, and any test that was skipped.
- The environment, when the result depends on it, such as the database version or the browser.
- For each check that does not apply, one line that explains why.