anonrouterdocs
SDKs

Verification and E2EE

Verify both hops of a confidential route with one verdict you can gate on, and run end-to-end-encrypted inference, with @anonrouter/confidential (JS/TS) and anonrouter-confidential (Python).

@anonrouter/confidential (JavaScript / TypeScript) and anonrouter-confidential (Python) are twin packages that let your own app do three things without trusting AnonRouter: verify a route's evidence, encrypt inference requests end to end, and generate images and speech across the two-origin split.

The verifier is pure over its inputs, fails closed, and both languages pass the same shared known-answer test vectors, so they agree bit-for-bit.

What TEE and E2EE each buy you

Your plaintext does not reach ordinary AnonRouter infrastructure. All content goes to api.anonrouter.ai, whose relay runs inside an attested Intel TDX CVM and terminates TLS in-enclavetransport_terminates_in_tee and tls_certificate_bound_to_quote are required checks in the shipped policy, so the attested TD provably holds the key for your connection.

What still differs between the modalities is the trust set. On a TEE route the relay handles your plaintext inside that enclave, so you are trusting our reviewed build — a build changed to exfiltrate it would change the measurements and hop 1 would stop verifying, making cheating detectable rather than impossible. On an E2EE route (NEAR, Venice, Chutes) the request is encrypted to the provider's attested key, so the relay holds ciphertext whatever code it runs and our build is not in your trust set at all. On some E2EE routes the enclave holding that key is a verified provider gateway rather than the model host; the catalog's per-route privacyNotes say which.

Every verdict reports which one you got as contentVisibleToAnonRouter, rather than leaving you to infer it from the provider name.

Install

The registry commands below do not work yet

Neither package is published on npm or PyPI. Both registry names return 404 today. Until they are published, take the tarball, wheel, or sdist from the v0.1.0 release, check it against the release SHA256SUMS, and install the file:

npm install ./anonrouter-confidential-0.1.0.tgz
pip install ./anonrouter_confidential-0.1.0-py3-none-any.whl
# Not published yet — see the callout above.
npm install @anonrouter/confidential

Node 22+ or Python 3.10+. You need an API key with the inference scope.

Create an API key

Configuration: two origins

AnonRouter serves two browser-visible origins with different jobs, and the SDK keeps them apart for you.

OriginRoleWhat it receives
https://control.anonrouter.aicontrolyour API key, content-free ticket and catalog requests
https://api.anonrouter.aiconfidentialattestation evidence and request content, authenticated by a single-use ticket alone

Both are the SDK's defaults, so most callers configure nothing but a key.

import { createClient } from "@anonrouter/confidential";

// Production origins are the defaults.
const client = createClient({ apiKey: process.env.ANONROUTER_API_KEY! });

// Or name them explicitly, for another deployment:
const explicit = createClient({
  inferenceBaseUrl: "https://api.anonrouter.ai",
  controlBaseUrl: "https://control.anonrouter.ai",
  apiKey: process.env.ANONROUTER_API_KEY!,
});

Why the origin matters to verification

A gateway quote binds the exact origin the client connected to. Verifying one origin while sending content to another would prove nothing about the route that carried your prompt, so the SDK deliberately offers no way to do it: evidence and content always use the same confidential origin. The control origin only ever carries the API key and content-free metadata.

The two hops

A request travels through two parties, and verifying one tells you nothing about the other.

Question it answersEndpoint
Hop 1 — AnonRouter's own routing planeIs the data plane I am connected to the exact reviewed build, running inside an Intel TDX confidential VM, bound to my nonce and my origin?GET /v1/gateway/attestation
Hop 2 — the downstream provider routeDid the model provider terminate my request inside a verified enclave running measurements I pinned?/v1/tee/attestation

A verified hop 2 says nothing about who routed the request. A verified hop 1 says nothing about where inference actually ran. Only asking both establishes the whole path.

verifyRoute() — the stable contract

verifyRoute establishes both hops, cross-binds them to the route you asked for, and reports ordered states rather than a boolean. This is the call to build on.

import { createClient, atLeast } from "@anonrouter/confidential";

const client = createClient({ apiKey: process.env.ANONROUTER_API_KEY! });

const verdict = await client.verifyRoute({
  model: "venice-uncensored",
  provider: "venice",
  gateway: true,                       // omit to skip hop 1 entirely
});

verdict.overallState;                  // the weakest hop you ASKED about
verdict.gateway.requested;             // whether hop 1 was in scope at all
verdict.gateway.failedChecks;          // the exact required checks that failed
verdict.bindingMismatches;             // route asked for vs route attested
verdict.contentVisibleToAnonRouter;    // true on a tee route

if (!atLeast(verdict.overallState, "cryptographically_checked")) {
  throw new Error(`route not established: ${verdict.reason}`);
}

Cross-binding is what this adds

Each hop's verifier only ever sees its own evidence, so neither can notice that the gateway attested itself perfectly while serving a different provider, model, or privacy class than you asked for. Any disagreement lands in bindingMismatches and forces untrusted, however strong the individual hops were. Two honestly-attested parties on the wrong route is still the wrong route.

The five states

Strictly ordered. Gate with atLeast() / at_least() rather than comparing strings, so a threshold keeps meaning the same thing if a state is later inserted into the scale.

StateWhat it establishes
hardware_verifiedEverything below, plus the quote's signature chained to the vendor's roots with an accepted TCB status. Reachable on hop 1 with a DCAP engine.
cryptographically_checkedThe evidence is internally consistent and bound to your fresh nonce, and the measurements match local pins. Nothing proved it came from real silicon.
policy_matchedThe evidence's claimed identity matched local pins, established through a vendor SDK rather than by our own parse.
untrustedWe looked and it failed.
unavailableWe could not look. The deployment does not expose the endpoint.

`untrusted` and `unavailable` are both failures, and they are different

"We looked and it failed" and "we could not look" call for different responses. Collapsing them into one boolean is how a missing endpoint gets mistaken for a passing check.

Reaching hardware_verified

Neither package bundles a DCAP engine. Shipping prebuilt binaries would mean asserting that a binary we did not build reproducibly is the reviewed one, and a hand-rolled reimplementation would be an unreviewed version of the single component whose failure mode is printing hardware_verified for a forged quote.

What ships instead is a strict adapter to the reviewed engine, plus the Intel-signed collateral it needs (the engine performs no network access, on purpose). Install anonrouter-dcap-verifier, put it on PATH or name it in ANONROUTER_DCAP_VERIFIER_BIN, and hop 1 can reach hardware_verified.

import { createAnonRouterDcapVerifier, describeDcapInstallation } from "@anonrouter/confidential/dcap";

describeDcapInstallation();   // is one installed? which one? what is its digest?

const verdict = await client.verifyRoute({
  model, provider,
  gateway: { chainVerifier: createAnonRouterDcapVerifier() },
});

The adapter fails closed on every path: a missing engine, a digest that does not match the expected one, a timeout, a crash, non-JSON output, collateral it could not acquire, or a verdict whose measured report disagrees with the quote the SDK parsed. Without an engine the ceiling is cryptographically_checked, and a policy demanding hardware verification fails closed. It never silently downgrades.

The pins do not come from the server being checked

A server that could hand you the list of builds you accept could always name itself. The measurement pins therefore ship inside the package, and an origin with no pin fails rather than falling back to whatever the server claims.

End-to-end-encrypted chat

For an E2EE provider the SDK encrypts your prompt in your process, relays only ciphertext, and decrypts the reply locally. It verifies the enclave first and binds the encryption key to that attestation, so it will not encrypt to an enclave it could not verify.

const reply = await client.chat({
  model: "venice-uncensored",
  provider: "venice",
  messages: [{ role: "user", content: "Draft a private message." }],
  maxOutputTokens: 512,
  // Re-establish hop 1 with a NEW nonce before a ticket is spent: a verdict from
  // a minute ago is a fact about a minute ago.
  requireGateway: true,
});

console.log(reply.content);
console.log(reply.usage);

requireGateway / require_gateway runs the check before the first authenticated call, so a failure means no ticket was spent and no plaintext went near the wire.

Conversations are multi-turn and text-only. Because the enclave is stateless you re-send the conversation each turn and the SDK re-encrypts it fresh, exactly like any stateless chat API. Tool calls, images, and attachments are not part of the encrypted surface and fail closed.

Providers and ceilings

ProviderModalityHop 2 ceiling
veniceE2EE (secp256k1 ECDH, AES-256-GCM)cryptographically_checked
chutesE2EE (ML-KEM-768, ChaCha20-Poly1305)cryptographically_checked
tinfoilTEE — our attested relay handles plaintext in-enclavepolicy_matched
near-aiE2EE (Ed25519 / X25519, XChaCha20-Poly1305)cryptographically_checked

Chutes is the only post-quantum route. Tinfoil is TEE-only and reaches policy_matched through its official verifier (the optional tinfoil dependency); without it, Tinfoil verification fails closed.

The near-ai verifier is registered but the live catalog publishes no callable NEAR route, so nothing selects that provider today. The callable confidential set is Tinfoil, Venice, and Chutes.

A Venice E2EE route may decrypt at a gateway, not at the model host

contentVisibleToAnonRouter answers the question the SDK can answer: whether our build sees plaintext. It does not describe the provider's internal topology. On some Venice routes the enclave your client encrypts to is a verified gateway that decrypts and forwards onward inside Venice. The route's privacyNotes in the catalog state this per route.

Hop 2 does not reach hardware_verified on any provider: several of these routes run GPU enclaves whose NVIDIA attestation chain is not available to verify, and chaining only the CPU quote would claim more than was checked.

Chutes E2EE bills a flat, full-ceiling reservation

Chutes' protocol encrypts the whole request body, so the gateway cannot meter it token by token. It therefore reserves and bills the model's full context window plus full output ceiling, flat, regardless of how short your prompt or maxOutputTokens is. On a large-context model that is significant. NEAR and Venice are metered from ciphertext length and cost the usual per-token amount. For a cheap encrypted round-trip prefer a small-context Chutes model or a NEAR / Venice route — and remember verification itself is always free.

The security contract

  • Independent re-verification. The SDK parses the raw TDX quote and provider evidence itself and checks it against pins shipped inside the package. It never turns a provider's verified: true, a model suffix, or the gateway's verdict into a proof.
  • Fresh nonce, key binding. Every verification uses a fresh nonce and confirms the enclave bound its key to that nonce, so evidence cannot be replayed.
  • Credential isolation. Your API key authenticates only content-free control requests. It is never sent to the origin that carries content.
  • No escape hatch. There is no environment variable that weakens a verdict. The suites set a dozen plausible spellings of one (ANONROUTER_INSECURE, ANONROUTER_SKIP_VERIFY, NODE_ENV=test, CI=true, and so on) and require the verdict for the same evidence to come out identical, not merely also-failing.

Errors

Failures raise a typed ConfidentialError with a stable code — such as attestation_untrusted, nonce_mismatch, or inference_ticket_failed — and a content-free message. Error messages never carry prompt or key material by construction. Media errors subclass the same type.

Advanced use

Both packages export the building blocks, for callers who want to verify or decrypt without the high-level client:

  • Pure verifiers per provider (NearTeeVerifier, VeniceTeeVerifier, ChutesTeeVerifier, TinfoilTeeVerifier) plus verifyRawEvidence.
  • TDX quote parsing: parseTdxQuote, matchMeasurementAllowlist.
  • Reviewed pins: pinnedMeasurementPolicyFor, pinnedGatewayPolicyFor.
  • Low-level provider crypto: providerCrypto["near-ai" | "venice" | "chutes"] (JS) or anonrouter_confidential.crypto.{near,venice,chutes} (Python), exercised by the shared known-answer vectors.

Release artifacts

v0.1.0 is the first public release: implementation, reviewed measurement pins, gateway policies, and the shared known-answer vectors ship as one auditable set under Apache-2.0, with the DCAP verifier's source under AGPL-3.0-only in native/dcap-verifier. SHA256SUMS covers both npm tarballs, the wheel, the sdist, the reproducible linux/amd64 verifier binary, and both source archives. linux/amd64 is the only target with a reproducible artifact and the only one claimed.

Two things about it are worth knowing before you depend on it:

  • The registry packages are not published. npm and PyPI both 404. Install from the release artifacts.
  • The shipped gateway pin predates the running content plane. v0.1.0 pins the release that was live when it was tagged, and the plane has been released since. Hop 1 therefore fails closed against production on compose_hash_pinned and release_pinned rather than passing — which is the intended behaviour for a stale pin, but it means --require hardware_verified needs a --policy you reviewed yourself until a refreshed pin ships.

On this page