Skip to content

Security

etemigarba edited this page Sep 6, 2026 · 1 revision

Security

Full reasoning in docs/09-security.md.

Change these three before deploying

Default Change to
ALLOW_ANON_TENANT=1 0
SESSION_SECRET=dev-only-insecure-secret A long random value
No rate limiting A limiter in front of /api/ask

Keys stay on the server

Anything prefixed NEXT_PUBLIC_ is compiled into the browser bundle in plain text. Build the app and grep the bundle for the value once; nobody forgets after seeing it.

No variable in this repository carries that prefix. Every provider call happens in a route handler or a script.

Retrieval is access control

A chunk is a row. A row a user must not read must not be retrievable.

Filter in the WHERE clause, before ranking. Retrieving everything and filtering afterwards has already put the data in your prompt, then in the model's output, then in your logs.

The tenant comes from a signed session cookie, never from the request body. A client-supplied tenant id is not access control.

Indirect prompt injection

Your own documents are untrusted input. A PDF containing "ignore previous instructions" is an attack delivered through step 1 and executed in step 7. Nobody typed it into the chat box.

Partial mitigations, all present here: explicit CONTEXT delimiters, a system prompt rule that context is data rather than instruction, and an answer audit that flags uncited answers and citations to sources that never existed.

None is complete. Trust a document exactly as much as you trust its source.

Demonstrate it in class. Index a document containing an instruction, watch an unguarded system obey it, add the delimiters, watch it stop.

Rendering

Never innerHTML. The text you are rendering came from a file, and the file came from somewhere. This repository builds text nodes and anchors.

Denial of wallet

Every request costs two upstream calls. An unauthenticated /api/ask is somebody else's inference budget. Rate limit, cap max_tokens, cap the context, cache embeddings, reject over-long questions.

Clone this wiki locally