Browser-Encrypted Private Mode on NanoGPT
NanoGPT now supports Private Mode for a first set of TEE-backed text models.
This is different from ordinary TEE routing. In a normal hosted chat request, NanoGPT receives your chat body as regular JSON, then routes it to a model. With Private Mode, the request and response body are encrypted on your device before NanoGPT receives them.
That means NanoGPT can authenticate, route, and bill the request, but cannot read the prompt or completion body for supported Private Mode requests.
What Private Mode does
Private Mode creates a stricter privacy boundary for supported models:
your browser or local proxy -> encrypted request body -> NanoGPT -> verified TEE target
verified TEE target -> encrypted response body -> NanoGPT -> your browser or local proxy
The plaintext exists where it has to exist:
- in your browser or local proxy
- inside the verified TEE target that runs the model
NanoGPT sees routing and account metadata, but not the encrypted request or response body.
What NanoGPT can and cannot see
For supported Private Mode requests, the encrypted body includes:
- messages
- system and developer instructions
- text conversation history sent to the model
- supported model settings and reasoning controls
- the model response body
NanoGPT can still see the parts needed to run the service:
- your NanoGPT account or API key identity
- the selected Private Mode model
- request timing, sizes, status, and usage metadata
- the selected TEE target metadata
The TEE target can see the plaintext prompt and completion because the model has to process them.
Using Private Mode in the web app
In the NanoGPT web app, choose an eligible TEE model and use the Private Mode control in the model picker.
If a model does not show the Private Mode control, that model is not currently supported through the browser-encrypted Private Mode path. This is so far only working with Tinfoil models - we're working with other providers to be able to do this for more models and providers.
The web app encrypts the chat-completions body in the browser with the Tinfoil client before NanoGPT receives it. NanoGPT forwards the encrypted body and returns the encrypted response. Your browser decrypts the response locally.
Private Mode in the web app is intentionally narrower than normal chat. It supports text chat, conversation history, model settings, streaming, and supported reasoning modes. Features that require NanoGPT to inspect or add plaintext before the request is encrypted are disabled for Private Mode turns.
Verification receipts
After a completed Private Mode response, NanoGPT shows a verification receipt.
The receipt includes:
- whether browser-side verification succeeded
- the TEE target and enclave host
- verifier step statuses
- expected and runtime measurement fingerprints
- attestation bundle hash when available
- verification document hash
- request and response encryption flags
- timestamp and model information
This receipt is meant to be copied, saved, and checked independently.
To verify a copied receipt locally:
npx @nanogpt/private-mode verify receipt.json
You can also pipe the receipt JSON on stdin:
cat receipt.json | npx @nanogpt/private-mode verify
The verifier fetches fresh attestation material for the receipt's TEE target, verifies it locally, and checks the measurements, release digest, and HPKE key against the copied receipt.
The right interpretation is specific: this individual Private Mode request used browser-side attestation verification and encrypted request/response bodies for a supported TEE target.
Using Private Mode through the API
For API clients, CLIs, agents, and OpenAI-compatible tools, use the local NanoGPT Private Mode proxy:
NANOGPT_API_KEY=sk-your-key npx @nanogpt/private-mode
It starts an OpenAI-compatible local server:
http://127.0.0.1:8787/v1
Then point your OpenAI-compatible client at that local base URL:
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "http://127.0.0.1:8787/v1",
apiKey: "unused",
});
const response = await client.chat.completions.create({
model: "private/kimi-k2-6",
messages: [{ role: "user", content: "Hello" }],
});
The local proxy verifies Tinfoil attestation, encrypts request bodies, sends ciphertext through NanoGPT, decrypts encrypted responses locally, and returns normal OpenAI-compatible responses to your app.
You can inspect the local proxy status with:
GET http://127.0.0.1:8787/v1/models
GET http://127.0.0.1:8787/v1/private-mode/status
GET http://127.0.0.1:8787/v1/private-mode/attestation
The local proxy preserves normal OpenAI chat semantics. Omit stream for a single JSON response, or set stream: true for server-sent events.
Why the local proxy exists
Direct API calls to NanoGPT's normal /v1/chat/completions endpoint send ordinary JSON to NanoGPT. That is the right path for normal NanoGPT API usage, but it is not the Private Mode privacy boundary.
Private Mode needs encryption before the request body leaves your machine. The local proxy gives OpenAI-compatible apps that boundary without requiring every app to implement attestation verification and encrypted transport itself.
Current scope
Private Mode is currently available for supported Tinfoil-backed text models.
That first scope is deliberate. We only want to show the Private Mode label for models where the request body can be encrypted before NanoGPT receives it, the client can verify the TEE target, and NanoGPT can still settle usage without decrypting the prompt or completion.
We will expand this when we can make the same claim for more models and providers.