{"_id":"@anonrouter/confidential","_rev":"2-3f5e8c874ce4c30ddf699b56578d2402","name":"@anonrouter/confidential","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.1":{"name":"@anonrouter/confidential","version":"0.1.1","keywords":["anonrouter","tee","e2ee","attestation","confidential","tdx","ml-kem","verify"],"author":{"name":"AnonRouter"},"license":"Apache-2.0","_id":"@anonrouter/confidential@0.1.1","maintainers":[{"name":"sundaydev","email":"labrat@anonrouter.ai"}],"homepage":"https://github.com/anonrouter/anonrouter-sdk#readme","bugs":{"url":"https://github.com/anonrouter/anonrouter-sdk/issues"},"bin":{"anonrouter-verify":"dist/cli.js"},"dist":{"shasum":"3162affa12fb2e1529caa897b7f735756575b3f1","tarball":"https://registry.npmjs.org/@anonrouter/confidential/-/confidential-0.1.1.tgz","fileCount":199,"integrity":"sha512-9kCwVNRu5k11YpSnAsYgsnh4D0t5bzICHPlxDUdEp6ZnVspY13dJNZZ+zVmPYiBvMj2ilPjzGy+ezqSUd2hF2w==","signatures":[{"sig":"MEQCIH4nHPCxhkWJiKZE0zgLY1/Qln0iENZ7bAnDw8c6bP+gAiAkwFNU/hDHShR5cUruiRJGtNT9jjXw6vrB9m+PHmDtAg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@anonrouter%2fconfidential@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1263223},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","//files":"src ships alongside dist on purpose. This package's entire value is that you can read what it checks, and the emitted source maps would otherwise point at files nobody received. The measurement pins are in src/measurements.json and src/gateway/gateway-policies.json.","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./dcap":{"types":"./dist/gateway/dcap/index.d.ts","import":"./dist/gateway/dcap/index.js"},"./package.json":"./package.json","./chain-verifiers":{"types":"./dist/gateway/chain-verifiers.d.ts","import":"./dist/gateway/chain-verifiers.js"}},"gitHead":"4296e74a0ed668f5ac4b90d11e9d0d132d07b448","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json && node -e \"const f=require('fs');f.copyFileSync('src/measurements.json','dist/measurements.json');f.copyFileSync('src/confidential-route-policy.json','dist/confidential-route-policy.json');f.mkdirSync('dist/gateway',{recursive:true});f.copyFileSync('src/gateway/gateway-policies.json','dist/gateway/gateway-policies.json')\"","typecheck":"tsc --noEmit && tsc -p tsconfig.examples.json","example:media":"tsx examples/media.ts","prepublishOnly":"npm run build","example:selftest":"tsx examples/selftest-mock.ts","gen:dcap-vectors":"tsx scripts/gen-dcap-vectors.ts","example:chat-e2ee":"tsx examples/chat-e2ee.ts","example:verify-tee":"tsx examples/verify-tee.ts","example:route-matrix":"tsx examples/route-matrix.ts","example:verify-route":"tsx examples/verify-route.ts","gen:attestation-vectors":"tsx scripts/gen-attestation-vectors.ts","example:verify-then-call":"tsx examples/verify-then-call.ts","example:negative-controls":"tsx examples/negative-controls.ts","gen:gateway-binding-vectors":"tsx scripts/gen-gateway-binding-vectors.ts","gen:gateway-verdict-vectors":"tsx scripts/gen-gateway-verdict-vectors.ts"},"_npmUser":{"name":"sundaydev","email":"labrat@anonrouter.ai"},"repository":{"url":"git+https://github.com/anonrouter/anonrouter-sdk.git","type":"git","directory":"js/confidential"},"_npmVersion":"12.0.2","description":"Independently verify AnonRouter TEE/E2EE routes and run confidential inference from your own app.","directories":{},"sideEffects":false,"_nodeVersion":"22.23.2","dependencies":{"@noble/curves":"^1.9.7","@noble/hashes":"^1.8.0","@noble/ciphers":"^2.3.0","@noble/post-quantum":"^0.7.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.2","vitest":"^4.1.10","typescript":"^5.7.2","@anonrouter/client":"*"},"optionalDependencies":{"tinfoil":"^1.2.1"},"_npmOperationalInternal":{"tmp":"tmp/confidential_0.1.1_1789175221569_0.6413853955740194","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@anonrouter/confidential","version":"0.1.2","description":"Independently verify AnonRouter TEE/E2EE routes and run confidential inference from your own app.","license":"Apache-2.0","author":{"name":"AnonRouter"},"homepage":"https://github.com/anonrouter/anonrouter-sdk#readme","repository":{"type":"git","url":"git+https://github.com/anonrouter/anonrouter-sdk.git","directory":"js/confidential"},"bugs":{"url":"https://github.com/anonrouter/anonrouter-sdk/issues"},"type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./dcap":{"types":"./dist/gateway/dcap/index.d.ts","import":"./dist/gateway/dcap/index.js"},"./chain-verifiers":{"types":"./dist/gateway/chain-verifiers.d.ts","import":"./dist/gateway/chain-verifiers.js"},"./package.json":"./package.json"},"bin":{"anonrouter-verify":"dist/cli.js"},"//files":"src ships alongside dist on purpose. This package's entire value is that you can read what it checks, and the emitted source maps would otherwise point at files nobody received. The measurement pins are in src/measurements.json and src/gateway/gateway-policies.json.","sideEffects":false,"engines":{"node":">=22"},"publishConfig":{"access":"public"},"scripts":{"build":"tsc -p tsconfig.json && node -e \"const f=require('fs');f.copyFileSync('src/measurements.json','dist/measurements.json');f.copyFileSync('src/confidential-route-policy.json','dist/confidential-route-policy.json');f.mkdirSync('dist/gateway',{recursive:true});f.copyFileSync('src/gateway/gateway-policies.json','dist/gateway/gateway-policies.json')\"","test":"vitest run","typecheck":"tsc --noEmit && tsc -p tsconfig.examples.json","prepublishOnly":"npm run build","example:selftest":"tsx examples/selftest-mock.ts","example:verify-tee":"tsx examples/verify-tee.ts","example:verify-route":"tsx examples/verify-route.ts","example:verify-then-call":"tsx examples/verify-then-call.ts","example:chat-e2ee":"tsx examples/chat-e2ee.ts","example:media":"tsx examples/media.ts","gen:attestation-vectors":"tsx scripts/gen-attestation-vectors.ts","gen:gateway-binding-vectors":"tsx scripts/gen-gateway-binding-vectors.ts","gen:gateway-verdict-vectors":"tsx scripts/gen-gateway-verdict-vectors.ts","gen:dcap-vectors":"tsx scripts/gen-dcap-vectors.ts","example:route-matrix":"tsx examples/route-matrix.ts","example:negative-controls":"tsx examples/negative-controls.ts"},"keywords":["anonrouter","tee","e2ee","attestation","confidential","tdx","ml-kem","verify"],"dependencies":{"@noble/ciphers":"^2.3.0","@noble/curves":"^1.9.7","@noble/hashes":"^1.8.0","@noble/post-quantum":"^0.7.0"},"optionalDependencies":{"tinfoil":"^1.2.1"},"devDependencies":{"@anonrouter/client":"*","tsx":"^4.19.2","typescript":"^5.7.2","vitest":"^4.1.10"},"gitHead":"16980d04e6a1be1c4a3c403a7b812b178b119513","_id":"@anonrouter/confidential@0.1.2","_nodeVersion":"22.23.2","_npmVersion":"12.0.2","dist":{"integrity":"sha512-IfHL7z6TW09ZP2+VAKOCncD2oAP2tO55mq+ef0X14gerrtb3m8W/s+g6YNukRdCjZgTzetSAO3p8aipcC9N6Qg==","shasum":"3e17c6ee6c2cebc195e95888734fada519991ec7","tarball":"https://registry.npmjs.org/@anonrouter/confidential/-/confidential-0.1.2.tgz","fileCount":204,"unpackedSize":1316307,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@anonrouter%2fconfidential@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIFb/i6wYryE1alDNQWx5COmgLcqxLltSdW1aVnRqjzQaAiBHwUjuZfKruQ+AbeRikA1hmGys1UYT4Fnlv4sz0oFP7Q=="}]},"_npmUser":{"name":"sundaydev","email":"labrat@anonrouter.ai"},"directories":{},"maintainers":[{"name":"sundaydev","email":"labrat@anonrouter.ai"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/confidential_0.1.2_1789200317389_0.9500881807079014"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-12T01:07:01.393Z","modified":"2026-09-12T08:05:17.878Z","0.1.1":"2026-09-12T01:07:01.715Z","0.1.2":"2026-09-12T08:05:17.537Z"},"bugs":{"url":"https://github.com/anonrouter/anonrouter-sdk/issues"},"author":{"name":"AnonRouter"},"license":"Apache-2.0","homepage":"https://github.com/anonrouter/anonrouter-sdk#readme","keywords":["anonrouter","tee","e2ee","attestation","confidential","tdx","ml-kem","verify"],"repository":{"type":"git","url":"git+https://github.com/anonrouter/anonrouter-sdk.git","directory":"js/confidential"},"description":"Independently verify AnonRouter TEE/E2EE routes and run confidential inference from your own app.","maintainers":[{"name":"sundaydev","email":"labrat@anonrouter.ai"}],"readme":"# @anonrouter/confidential\n\nIndependently verify AnonRouter's TEE / E2EE routes and run end-to-end-encrypted\nconfidential inference from your own Node or browser app. The guiding principle is\n\"don't trust us, verify\": this package checks the provider's raw attestation\nevidence itself, against measurement pins you can read and review, so you do not\nhave to take AnonRouter's word for it.\n\nPart of the [AnonRouter SDK monorepo](https://github.com/anonrouter/anonrouter-sdk).\nFor the plaintext API surface, see `@anonrouter/client`.\n\n## Install\n\n```bash\nnpm install @anonrouter/confidential\n\n# Before registry propagation, build from a clone of the monorepo:\ngit clone https://github.com/anonrouter/anonrouter-sdk\ncd anonrouter-sdk/js && npm ci && npm run build\n```\n\nThe release tarball is also attached to the corresponding GitHub release. It is\nbuilt and installed into an empty environment before publication, so the exact\nbytes npm carries are exercised rather than only the working tree.\n\nNode 22 or newer. Four runtime dependencies, all `@noble` audited crypto\n(`ciphers`, `curves`, `hashes`, `post-quantum`) and nothing else.\n\nOptional dependency: install `tinfoil` to check a Tinfoil TEE route yourself with\n`verifyTinfoilEnclave()`, which runs the provider's official verifier and then\npins its own connection to the enclave. That function is Node-only, for the same\nreason the DCAP path is; see \"Browser support\" below.\n\n**Browser support.** The default `@anonrouter/confidential` package works in\nbrowsers and supports E2EE routes. Importing it never pulls in a Node builtin.\nFull Intel TDX hardware verification is currently available in Node.js through\n`@anonrouter/confidential/dcap`. Browsers cannot run the native verifier or\ninspect the server's TLS certificate, so the SDK does not claim full gateway\nhardware verification in a browser. The same limit applies to\n`verifyTinfoilEnclave()`: pinning the enclave's serving key means reading a peer\ncertificate, which no browser exposes, so in a browser it fails closed with\n`tinfoil_tls_observation_unsupported` rather than returning a verdict that\nskipped the pin. Use the Node.js SDK or `anonrouter-verify` CLI when you need\nthat proof.\n\n## Quickstart\n\n```ts\nimport { createClient, atLeast } from \"@anonrouter/confidential\";\nimport { createAnonRouterDcapVerifier } from \"@anonrouter/confidential/dcap\";\n\nconst client = createClient({\n  baseUrl: \"https://api.anonrouter.ai\",\n  controlBaseUrl: \"https://control.anonrouter.ai\",\n  apiKey: process.env.ANONROUTER_API_KEY!\n});\n\n// Verify BOTH hops, cross-bound to the route you asked for, and gate on it.\nconst verdict = await client.verifyRoute({\n  model: \"z-ai/glm-5.2\",\n  provider: \"venice\",\n  gateway: { chainVerifier: createAnonRouterDcapVerifier() }\n});\nif (!atLeast(verdict.overallState, \"cryptographically_checked\")) {\n  throw new Error(`route not established: ${verdict.reason}`);\n}\n\n// End-to-end-encrypted chat: keys and nonce are fresh per call, and only\n// ciphertext ever reaches AnonRouter's relay. requireGateway re-establishes hop 1\n// with a NEW nonce before a ticket is spent or a model is named, because a verdict\n// from a minute ago is a fact about a minute ago.\nconst reply = await client.chat({\n  model: \"z-ai/glm-5.2\",\n  provider: \"venice\",\n  messages: [{ role: \"user\", content: \"Draft a private message.\" }],\n  maxOutputTokens: 512,\n  requireGateway: { chainVerifier: createAnonRouterDcapVerifier() }\n});\nconsole.log(reply.content);\n```\n\n## Images and speech\n\n`images.generate` and `audio.speech.create` run AnonRouter's two-origin ticket\nexchange for you. The API key mints a **content-free** single-use ticket at the\ncontrol origin; the prompt or text then goes to the confidential origin with that\nticket as its only credential. Neither host sees both your identity and your\ncontent.\n\n```ts\nimport { createClient } from \"@anonrouter/confidential\";\n\n// The production origins are the defaults, so this is the whole configuration.\nconst client = createClient({ apiKey: process.env.ANONROUTER_API_KEY! });\n\nconst image = await client.images.generate({\n  model: \"alibaba/z-image-turbo\",\n  prompt: \"a lighthouse in a storm\",\n  size: \"1024x1024\"\n});\nawait writeFile(\"out.png\", image.data[0].bytes);\nconsole.log(image.selected_model, image.data[0].mime_type);\n\nconst speech = await client.audio.speech.create({\n  model: \"venice/kokoro-text-to-speech\",\n  input: \"The quick brown fox.\",\n  voice: \"af_sky\"\n});\nawait writeFile(\"out.mp3\", speech.audio);\n```\n\n**The official OpenAI SDK cannot perform this exchange.** It has one base URL and\none credential, so it would send your API key and your prompt to the same host in\none request. Point it at the confidential origin and the mint answers 404; point\nit at the control origin and media answers 503. Both failures are the design\nworking. AnonRouter's OpenAI-compatibility broker is a **different, lower-privacy\noption** — one service receives the key and the prompt together — and this SDK\nnever selects it implicitly, never falls back to it, and has no flag that enables\nit.\n\nUnsupported OpenAI parameters are **refused, not dropped**: `n` other than 1,\n`response_format` other than `b64_json` / `mp3`, `speed` other than 1, and unknown\nkeys like `quality`. Silently ignoring them would hand you something other than\nwhat you paid for. Failed POSTs are **never retried**, because a media generation\nis billed on the provider attempt.\n\n**Media is not end-to-end encrypted, unlike `chat()`.** The prompt reaches the\nconfidential origin as plaintext. What protects it is the origin split (the host\nholding the prompt never holds your credential) plus the TDX enclave that host\nruns in — which you can verify yourself with `verifyGateway()` before you send\nanything, against the same origin the content goes to. That is a real property\nand a weaker one than E2EE chat; if your threat model needs AnonRouter to be\nunable to read the content even in principle, media does not meet it today.\n\nFull contract, compatibility matrix, bound ticket facts, and the error taxonomy:\n[`docs/ticketed-media.md`](../../docs/ticketed-media.md).\n\n## Verify from a terminal\n\nInstalling this package installs `anonrouter-verify`. It prints one JSON document\nand exits nonzero unless the assurance you asked for was established:\n\n```bash\nnpx anonrouter-verify doctor --origin https://api.anonrouter.ai\nnpx anonrouter-verify gateway --origin https://api.anonrouter.ai --dcap \\\n  --require hardware_verified\necho $?   # 0 met, 1 not met, 2 the command itself was wrong\n```\n\n`gateway` is credential-free. `route` adds hop 2 and needs an API key, read only\nfrom an environment variable and never accepted on argv, where it would be visible\nin the process table. Neither command prints a key, a ticket, request content, or\nthe raw evidence body.\n\n## Reaching `hardware_verified`\n\nThis package bundles **no DCAP engine**, on purpose: shipping prebuilt binaries\nwould mean asserting that a binary we did not build reproducibly is the reviewed\none, and a hand-rolled JavaScript reimplementation would be an unreviewed version\nof the single component whose failure mode is printing `hardware_verified` for a\nforged quote.\n\nWhat ships instead is a strict adapter to the reviewed engine, plus the\nIntel-signed collateral it needs (the engine performs no network access, on\npurpose). Get `anonrouter-dcap-verifier` either as the checksummed `linux/amd64`\n[release asset](https://github.com/anonrouter/anonrouter-sdk/releases) or, better,\nby building the same source yourself with `scripts/build-dcap-verifier.sh\n--reproduce` and comparing digests. Put it on PATH or name it in\n`ANONROUTER_DCAP_VERIFIER_BIN`, and hop 1 can reach `hardware_verified`:\n\n```ts\nimport { createAnonRouterDcapVerifier, describeDcapInstallation } from \"@anonrouter/confidential/dcap\";\n\ndescribeDcapInstallation();   // is one installed? which one? what is its digest?\n\nconst verdict = await client.verifyRoute({\n  model, provider,\n  gateway: { chainVerifier: createAnonRouterDcapVerifier() }\n});\n```\n\nThe adapter fails closed on every path: a missing engine, a digest that does not\nmatch `expectedBinarySha256`, a timeout, a crash, non-JSON output, collateral it\ncould not acquire, or a verdict whose measured report disagrees with the quote the\nSDK parsed. Without an engine the ceiling is `cryptographically_checked` and a\npolicy demanding hardware verification fails closed. It never silently downgrades.\n\nHop 2 is unaffected and still caps at `provider-attested`: several provider routes\nrun GPU enclaves whose NVIDIA attestation chain is not available to verify, and\nchaining only the CPU quote would claim more than was checked.\n\n## The two hops\n\nA request travels through two parties, and verifying one tells you nothing about\nthe other:\n\n| | Question it answers | How to verify |\n| --- | --- | --- |\n| **Hop 1** AnonRouter's own routing plane | Is 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? | `verifyGateway()` |\n| **Hop 2** the downstream provider route | Did the model provider terminate my request inside a verified enclave running measurements I pinned? | `verifyAttestation()` |\n\nA verified hop 2 says nothing about who routed the request. A verified hop 1 says\nnothing about where inference ran. `verifyRoute()` establishes both, cross-binds\nthem to the route you asked for, and reports them separately:\n\n```ts\nconst verdict = await client.verifyRoute({\n  model: \"z-ai/glm-5.2\",\n  provider: \"venice\",\n  gateway: true            // omit to skip hop 1 entirely\n});\n\nverdict.overallState;                          // the weakest hop you ASKED about\nverdict.gateway.requested;                     // whether hop 1 was in scope at all\nverdict.gateway.state;                         // hardware_verified | ... | unavailable\nverdict.gateway.failedChecks;                  // the exact required checks that failed\nverdict.bindingMismatches;                     // the route you asked for vs what was served\nverdict.contentVisibleToAnonRouter;            // true on a tee route, false on e2ee\n```\n\nGate with `atLeast()` rather than comparing strings: it is the one place the\nordering lives, so a threshold keeps meaning the same thing if a state is later\ninserted into the scale.\n\n`overallState` only ever covers the hops you asked for, which is why\n`gateway.requested` sits beside it. A trusted verdict with\n`gateway.requested: false` establishes the provider enclave and makes no claim\nabout the router. And any entry in `bindingMismatches` forces the whole verdict\nuntrusted however strong the individual hops were, because two honestly-attested\nparties on the wrong route is still the wrong route.\n\n`verify()` returns the earlier report shape and is still supported; prefer\n`verifyRoute()`.\n\nTo refuse to send anything unless AnonRouter's own plane attests, gate `chat()`.\nThe check runs before the first authenticated call, so a failure means no ticket\nwas spent and no plaintext went near the wire:\n\n```ts\nawait client.chat({ /* ... */, requireGateway: true });\n```\n\n### Pinning hop 1\n\nThe policy a gateway is held to must never come from that gateway: a server that\ncould hand you the list of builds you accept could always name itself. So the pins\nship inside this package, and an origin with no pin fails closed rather than\nfalling back to whatever the server claims.\n\n```ts\nimport { loadGatewayPolicy } from \"@anonrouter/confidential\";\n\nawait client.verifyGateway({ policy: loadGatewayPolicy(myReviewedPolicy) });\n```\n\nTwo things to know about the pin this package ships today:\n\n- It is marked `published` from the independently retained production release\n  manifest. A different app id, compose hash or platform measurement fails\n  closed until a newly reviewed policy ships.\n- It sets `requireHardwareVerified`, and this package bundles no engine. So\n  `verifyGateway()` fails closed with reason `quote_signature_chain` until you\n  install the separate reproducible engine (see above). That is the honest answer,\n  not a bug: without chaining\n  the quote's signature to Intel's roots, nobody has checked that the quote came\n  from real silicon.\n\n## How AnonRouter protects your requests\n\nAnonRouter cannot read or log your prompts or responses. Content-bearing\nrequests go to `api.anonrouter.ai`, where TLS terminates inside an attested Intel\nTDX enclave. Plaintext exists only inside the measured relay while the request\nis routed and metered. The separate service at `control.anonrouter.ai` handles\nauthorization and billing metadata and does not receive request content.\n\nOn a TEE route, the measured relay processes plaintext inside the enclave. The\nSDK verifies AnonRouter's relay against its published measurements. For Tinfoil's\nupstream enclave, the official Tinfoil verifier validates the current release's\nGitHub/Sigstore authority, AMD SEV-SNP evidence and live code measurement, and it\ndoes not wait for AnonRouter to republish that release's fingerprint. The\nattested TLS key is then bound to a real pinned connection, because the document\nstates that key twice from one report field and so cannot establish it alone. No\nNVIDIA GPU evidence is verified on this route and nothing binds the model\nweights. In Node.js, the DCAP verifier can also verify the Intel hardware chain.\nIf the deployed code or configuration of AnonRouter changes, its measurements\nchange and gateway verification fails until the new release is reviewed and\npinned.\n\nOn an E2EE route, the SDK encrypts content to a key bound to the destination\nenclave's attestation. The AnonRouter relay receives only ciphertext and never\nprocesses prompt or response plaintext.\n\nThe SDK reports only the verification it actually completes. Gateway\nverification reaches `hardware_verified` only when the DCAP verifier validates\nthe quote against Intel's roots and every required policy check passes. Missing\nor failed checks are never reported as successful.\n\nAttestation identifies the exact code and configuration running inside the\nenclave. The published source and pinned compose hash let you inspect what that\nmeasured build contains.\n\nSee the [repository README](https://github.com/anonrouter/anonrouter-sdk#readme)\nfor the full trust-boundary discussion and the reviewed measurement pins.\n\n## License\n\nApache-2.0. See `LICENSE`.\n","readmeFilename":"README.md"}