{"_id":"@altrsoftware/shield","_rev":"4-01aad92ca4ce0b9c1f82e1d966f35aa7","name":"@altrsoftware/shield","dist-tags":{"latest":"2.0.0"},"versions":{"0.0.1":{"name":"@altrsoftware/shield","version":"0.0.1","keywords":["altr","shield","tokenization","detokenization","pii","data-protection","data-security","masking","llm","ai-agents","streaming"],"author":{"name":"ALTR Solutions, Inc."},"license":"Apache-2.0","_id":"@altrsoftware/shield@0.0.1","maintainers":[{"name":"zigzagged9265","email":"kevin@altr.com"},{"name":"raan-94","email":"ryan@altr.com"}],"homepage":"https://github.com/altrsoftware/shield-sdk-node#readme","bugs":{"url":"https://github.com/altrsoftware/shield-sdk-node/issues"},"dist":{"shasum":"b9b08fe3700ea0715ef2d71a50b9fe8860f4e5b4","tarball":"https://registry.npmjs.org/@altrsoftware/shield/-/shield-0.0.1.tgz","fileCount":39,"integrity":"sha512-LbiCznhFJQ8Xcz/hUC9kSGuDNIC/Y2SJ1nbJwhGm5gRgiRmTKlvN2B1PXXX7HWyNqkwj8tOfj8Oq6g2rcZWIkw==","signatures":[{"sig":"MEUCIQDIsGHzhjksRmWRGwZaFAccA5fDjqZ2hJWtCpPJsEt5QwIgPr7XtTl/GsU1texWyPAUjql2y4qh3A4REqdlavGaZDA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":439323},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":"^20.19.0 || ^22.12.0 || >=23.0.0"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"ce4356d8cf0a9694a977b1c3df00ca99c37e1d5b","scripts":{"docs":"typedoc","lint":"eslint . && prettier --check .","test":"vitest run","bench":"vitest bench --run","build":"npm run clean && tsc -p tsconfig.json","clean":"node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"","format":"prettier --write .","lint:fix":"eslint . --fix && prettier --write .","typecheck":"tsc -p tsconfig.typecheck.json && tsc -p examples/tsconfig.json","test:coverage":"vitest run --coverage","prepublishOnly":"npm run typecheck && npm run build && npm test"},"_npmUser":{"name":"raan-94","email":"ryan@altr.com"},"repository":{"url":"git+https://github.com/altrsoftware/shield-sdk-node.git","type":"git"},"_npmVersion":"11.17.0","description":"ALTR Shield Node.js SDK — protect and restore sensitive data via the Shield data plane, with an XML token envelope and streaming detokenization.","directories":{},"sideEffects":false,"_nodeVersion":"26.5.0","dependencies":{"jose":"^6.2.4"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"10.8.0","vitest":"4.1.10","typedoc":"0.28.20","prettier":"3.9.6","fast-check":"4.9.0","typescript":"5.9.3","@types/node":"24.13.3","typescript-eslint":"8.65.0","@vitest/coverage-v8":"4.1.10","@arethetypeswrong/cli":"0.18.5","eslint-config-prettier":"10.1.8"},"_npmOperationalInternal":{"tmp":"tmp/shield_0.0.1_1785776030807_0.4356695545716014","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@altrsoftware/shield","version":"0.0.2","keywords":["altr","shield","tokenization","detokenization","pii","data-protection","data-security","masking","llm","ai-agents","streaming"],"author":{"name":"ALTR Solutions, Inc."},"license":"Apache-2.0","_id":"@altrsoftware/shield@0.0.2","maintainers":[{"name":"zigzagged9265","email":"kevin@altr.com"},{"name":"raan-94","email":"ryan@altr.com"}],"homepage":"https://github.com/altrsoftware/shield-sdk-node#readme","bugs":{"url":"https://github.com/altrsoftware/shield-sdk-node/issues"},"dist":{"shasum":"f235e780bf8e0bc11d489470a1f743744216beb1","tarball":"https://registry.npmjs.org/@altrsoftware/shield/-/shield-0.0.2.tgz","fileCount":39,"integrity":"sha512-DMG7QCTijg7VQJwU/sSMYMgA1lXjhLDQ8ZZxZgl2RPWT3advj/bL96WV6+OgtaVEqjZmQ8tuNpO/NB0A6xDgcg==","signatures":[{"sig":"MEYCIQC0JgAdMC2qQufrKQPgnxYj/dyUmL0nCnTKd63uAjszdQIhAI/6YtuRDPoisXFh09toost7ufjk+IDpeVERDvkjBIhe","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@altrsoftware%2fshield@0.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":439323},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":"^20.19.0 || ^22.12.0 || >=23.0.0"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"622d59f00e69dc0bcaf256193fd78addcc538836","scripts":{"docs":"typedoc","lint":"eslint . && prettier --check .","test":"vitest run","bench":"vitest bench --run","build":"npm run clean && tsc -p tsconfig.json","clean":"node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"","format":"prettier --write .","lint:fix":"eslint . --fix && prettier --write .","typecheck":"tsc -p tsconfig.typecheck.json && tsc -p examples/tsconfig.json","test:coverage":"vitest run --coverage","prepublishOnly":"npm run typecheck && npm run build && npm test"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","approver":{"name":"raan-94","email":"ryan@altr.com"},"trustedPublisher":{"id":"github","oidcConfigId":"oidc:43e2c289-8786-4737-8577-2c67a154ab4b"}},"repository":{"url":"git+https://github.com/altrsoftware/shield-sdk-node.git","type":"git"},"_npmVersion":"11.16.0","description":"ALTR Shield Node.js SDK — protect and restore sensitive data via the Shield data plane, with an XML token envelope and streaming detokenization.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"jose":"^6.2.4"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"10.8.0","vitest":"4.1.10","typedoc":"0.28.20","prettier":"3.9.6","fast-check":"4.9.0","typescript":"5.9.3","@types/node":"24.13.3","typescript-eslint":"8.65.0","@vitest/coverage-v8":"4.1.10","@arethetypeswrong/cli":"0.18.5","eslint-config-prettier":"10.1.8"},"_npmOperationalInternal":{"tmp":"tmp/shield_0.0.2_1785777100336_0.46802890803051","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@altrsoftware/shield","version":"1.0.0","keywords":["altr","shield","tokenization","detokenization","pii","data-protection","data-security","masking","llm","ai-agents","streaming"],"author":{"name":"ALTR Solutions, Inc."},"license":"Apache-2.0","_id":"@altrsoftware/shield@1.0.0","maintainers":[{"name":"zigzagged9265","email":"kevin@altr.com"},{"name":"raan-94","email":"ryan@altr.com"}],"homepage":"https://github.com/altrsoftware/shield-sdk-node#readme","bugs":{"url":"https://github.com/altrsoftware/shield-sdk-node/issues"},"dist":{"shasum":"55b83e16a0f04f1d495aff6535e469db53c45807","tarball":"https://registry.npmjs.org/@altrsoftware/shield/-/shield-1.0.0.tgz","fileCount":39,"integrity":"sha512-GZQzz4FO9eCzPzcpKwwXwm08LVFGtrGse/AhMvhUpxlC8c1/4eft6caq5WA5gU8n6VrE1uOiryYAkM9LuHnL3Q==","signatures":[{"sig":"MEQCIHfPJf5NKuvzTAt4XUnNVPdS+tXsiRMh5JLbcH057QRKAiAqi3tv4T/dZleYe7Ot3quH/i/mQArt2C4rimqSbpJ00Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@altrsoftware%2fshield@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":439323},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":"^20.19.0 || ^22.12.0 || >=23.0.0"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"15271c6cfb825491ab4d9ddef857ec26abe6f181","scripts":{"docs":"typedoc","lint":"eslint . && prettier --check .","test":"vitest run","bench":"vitest bench --run","build":"npm run clean && tsc -p tsconfig.json","clean":"node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"","format":"prettier --write .","lint:fix":"eslint . --fix && prettier --write .","typecheck":"tsc -p tsconfig.typecheck.json && tsc -p examples/tsconfig.json","test:coverage":"vitest run --coverage","prepublishOnly":"npm run typecheck && npm run build && npm test"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","approver":{"name":"raan-94","email":"ryan@altr.com"},"trustedPublisher":{"id":"github","oidcConfigId":"oidc:43e2c289-8786-4737-8577-2c67a154ab4b"}},"repository":{"url":"git+https://github.com/altrsoftware/shield-sdk-node.git","type":"git"},"_npmVersion":"11.16.0","description":"ALTR Shield Node.js SDK — protect and restore sensitive data via the Shield data plane, with an XML token envelope and streaming detokenization.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"jose":"^6.2.4"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"10.8.0","vitest":"4.1.10","typedoc":"0.28.20","prettier":"3.9.6","fast-check":"4.9.0","typescript":"5.9.3","@types/node":"24.13.3","typescript-eslint":"8.65.0","@vitest/coverage-v8":"4.1.10","@arethetypeswrong/cli":"0.18.5","eslint-config-prettier":"10.1.8"},"_npmOperationalInternal":{"tmp":"tmp/shield_1.0.0_1785780224393_0.22547311855387964","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"_id":"@altrsoftware/shield@2.0.0","bugs":{"url":"https://github.com/altrsoftware/shield-sdk-node/issues"},"dist":{"shasum":"a68a3d931d68bbb8d38e0857d4b5ef3416d52562","tarball":"https://registry.npmjs.org/@altrsoftware/shield/-/shield-2.0.0.tgz","integrity":"sha512-W6bnsjULucoIomEXBRhMZwWOJZ/JM7tRxd3yXKHaIyjo93cP4oWuHibUXOzOuIOm3O0Zm8dMgE611cWQy8LrqA==","fileCount":39,"unpackedSize":443091,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@altrsoftware%2fshield@2.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIBe1ggXsAKaAaOuI2IouzV7gstgi2Q1mhkRDka2BiiNQAiEAxqCMY9JMa9zG9UzFR5nwgEpDPdJTR1owb7OjRumMrPk="}]},"main":"./dist/index.js","name":"@altrsoftware/shield","type":"module","types":"./dist/index.d.ts","author":{"name":"ALTR Solutions, Inc."},"engines":{"node":"^20.19.0 || ^22.12.0 || >=23.0.0"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"01e1a14e0bc364685d4da0ea55894f8275e335cc","license":"Apache-2.0","scripts":{"docs":"typedoc","lint":"eslint . && prettier --check .","test":"vitest run","bench":"vitest bench --run","build":"npm run clean && tsc -p tsconfig.json","clean":"node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"","format":"prettier --write .","lint:fix":"eslint . --fix && prettier --write .","typecheck":"tsc -p tsconfig.typecheck.json && tsc -p examples/tsconfig.json","test:coverage":"vitest run --coverage","prepublishOnly":"npm run typecheck && npm run build && npm test"},"version":"2.0.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:43e2c289-8786-4737-8577-2c67a154ab4b"},"approver":{"name":"raan-94","email":"ryan@altr.com"}},"homepage":"https://github.com/altrsoftware/shield-sdk-node#readme","keywords":["altr","shield","tokenization","detokenization","pii","data-protection","data-security","masking","llm","ai-agents","streaming"],"repository":{"url":"git+https://github.com/altrsoftware/shield-sdk-node.git","type":"git"},"_npmVersion":"11.16.0","description":"ALTR Shield Node.js SDK — protect and restore sensitive data via the Shield data plane, with an XML token envelope and streaming detokenization.","directories":{},"maintainers":[{"name":"zigzagged9265","email":"kevin@altr.com"},{"name":"raan-94","email":"ryan@altr.com"}],"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"jose":"^6.2.4"},"publishConfig":{"access":"public"},"devDependencies":{"eslint":"10.8.0","vitest":"4.1.10","typedoc":"0.28.20","prettier":"3.9.6","fast-check":"4.9.0","typescript":"5.9.3","@types/node":"24.13.3","typescript-eslint":"8.65.0","@vitest/coverage-v8":"4.1.10","@arethetypeswrong/cli":"0.18.5","eslint-config-prettier":"10.1.8"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/shield_2.0.0_1786130470336_0.9033014840659264"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-03T16:53:50.679Z","modified":"2026-08-07T19:21:10.871Z","0.0.1":"2026-08-03T16:53:51.016Z","0.0.2":"2026-08-03T17:11:40.430Z","1.0.0":"2026-08-03T18:03:44.510Z","2.0.0":"2026-08-07T19:21:10.475Z"},"bugs":{"url":"https://github.com/altrsoftware/shield-sdk-node/issues"},"author":{"name":"ALTR Solutions, Inc."},"license":"Apache-2.0","homepage":"https://github.com/altrsoftware/shield-sdk-node#readme","keywords":["altr","shield","tokenization","detokenization","pii","data-protection","data-security","masking","llm","ai-agents","streaming"],"repository":{"url":"git+https://github.com/altrsoftware/shield-sdk-node.git","type":"git"},"description":"ALTR Shield Node.js SDK — protect and restore sensitive data via the Shield data plane, with an XML token envelope and streaming detokenization.","maintainers":[{"name":"zigzagged9265","email":"kevin@altr.com"},{"name":"raan-94","email":"ryan@altr.com"}],"readme":"# @altrsoftware/shield\n\n[![npm version](https://img.shields.io/npm/v/@altrsoftware/shield.svg)](https://www.npmjs.com/package/@altrsoftware/shield)\n[![license](https://img.shields.io/npm/l/@altrsoftware/shield.svg)](./LICENSE)\n\nNode.js SDK for the [ALTR Shield](https://docs.shield.live.altr.com/v1/docs) data plane. Protect sensitive data (classify + tokenize/mask per policy) before it leaves your trust boundary, and restore it — where policy allows — on the way back. Protected values are carried in-text as an XML token envelope (`<altr tok=\"…\"/>`), so prompts, memories, tool arguments, and streamed model output stay structurally intact while carrying only non-sensitive surrogates.\n\n- **Protect** — `POST /v1/protect` classifies your text; the SDK splices the findings back in as token tags and mask literals, byte-offset-safe for any Unicode.\n- **Restore / detokenize** — swap policy-allowed tokens back for their values; denied tokens stay as tags. Restoration is identity-aware: what detokenizes depends on the calling application and the tags on both the request and the stored token.\n- **Streaming** — a chunk-boundary-safe `TransformStream` / async-iterable detokenizer for LLM output.\n\n**ESM-only**, and runtime-agnostic — Node, Edge/Workers, Deno/Bun, and browsers (server-side; see [Runtime support](#runtime-support)).\n\n## Install\n\n```sh\nnpm install @altrsoftware/shield\n# or: pnpm add @altrsoftware/shield / yarn add @altrsoftware/shield\n```\n\n## Getting your credentials\n\nEverything the constructor needs comes from your ALTR organization (ask your ALTR admin, or see the ALTR docs for your deployment):\n\n| Option           | What it is                                                       | Where it comes from                                                                                |\n| ---------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |\n| `baseUrl`        | Shield data-plane URL, e.g. `https://<your-org>.shield.altr.com` | Provided when Shield is enabled for your org                                                       |\n| `orgId`          | Your ALTR org id (sent as the JWT `client_id` claim)             | ALTR organization settings                                                                         |\n| `appId`          | Shield **application** id (JWT `shield_app_id` claim)            | Created when you register a Shield Application                                                     |\n| `privateKeyPem`  | The application's registered RSA private key, **PKCS#8 PEM**     | You generate the keypair; the **public** key is registered on the application (two rotation slots) |\n| `collectionName` | Classifier collection that decides what counts as sensitive      | Created/managed in ALTR                                                                            |\n\nGenerate a keypair:\n\n```sh\nopenssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out app-private.pem\nopenssl pkey -in app-private.pem -pubout -out app-public.pem\n# register app-public.pem on your Shield Application\n```\n\nThe SDK mints short-lived JWTs from the private key on demand — there is no long-lived token to manage.\n\n## Quickstart\n\n```ts\nimport { readFileSync } from \"node:fs\";\nimport { ShieldClient } from \"@altrsoftware/shield\";\n\nconst shield = new ShieldClient({\n  baseUrl: \"https://<your-org>.shield.altr.com\",\n  orgId: \"<YOUR_ALTR_ORG_ID>\",\n  appId: \"<YOUR_SHIELD_APP_ID>\",\n  privateKeyPem: readFileSync(\"app-private.pem\", \"utf8\"),\n  collectionName: \"<YOUR_COLLECTION_NAME>\",\n});\n\nconst { protectedText, tokens, findings } = await shield.protect(\n  \"Hi, I'm Jane Doe. My card is 4111111111115462.\",\n  { tags: [\"conv:sess_123\"] },\n);\n// protectedText → Hi, I'm <altr tok=\"9edc…\"/>. My card is ************5462.\n\nconst { text, restored, denied } = await shield.restore(protectedText, {\n  tags: [\"conv:sess_123\"],\n});\n// text → policy-allowed tokens restored; denied tokens stay as tags\n```\n\nRequest-scope `tags` are stamped onto every token a protect call mints and are evaluated against restore policy — sending the same correlator tag (a conversation id, an actor id) on both protect and detokenize is what makes tag-scoped, identity-aware restore policies work.\n\nDeterministic tokenization is scoped by an optional `determinismContext` (set it on the client or per `protect()` call): under a deterministic policy strategy, the same value in the same context always resolves to the same token, and a different context yields an unlinkable one. The context is compared **byte-exact** — never trimmed or case-folded (`\"Fruit\"` ≠ `\"fruit\"`) — and capped at 256 UTF-8 bytes (`MAX_DETERMINISM_CONTEXT_BYTES`); omitted and `\"\"` are the same (default) scope.\n\n`detokenize(tokens, { tags })` is the batch primitive underneath `restore()`. Every token you pass gets an entry in the returned `values` map; a token that did not resolve (denied, unknown, or missing from the response) maps to **itself** — test with `values[token] === token`, never with truthiness.\n\n`findings` is discriminated on `action`: switch on it to get `finding.token` (`tokenize`) or `finding.masked_as` (`mask`) as plain strings. A finding this SDK version cannot represent fails the whole `protect()` call with `ShieldSpliceError` instead of reaching your code. See [docs/node-sdk.md](https://github.com/altrsoftware/shield-sdk-node/blob/main/docs/node-sdk.md) for the full API walkthrough.\n\n## Streaming LLM output\n\nModel output can split an `<altr tok=\"…\"/>` tag across any chunk boundary; the streaming detokenizer buffers only the smallest suffix that could still be a tag and swaps complete tags as they close:\n\n```ts\nimport {\n  createDetokenizeStream,\n  detokenizeIterable,\n} from \"@altrsoftware/shield\";\n\n// Web Streams — decode bytes first; a response body streams Uint8Array:\nif (!modelResponse.body) throw new Error(\"no response body\");\nmodelResponse.body\n  .pipeThrough(new TextDecoderStream())\n  .pipeThrough(createDetokenizeStream(shield, { tags: [\"conv:sess_123\"] }))\n  .pipeTo(destination); // destination = any WritableStream (an HTTP response, a file, ...)\n\n// Async iterable (agent loops, SSE handlers):\nfor await (const piece of detokenizeIterable(shield, chunks, { tags })) {\n  response.write(piece);\n}\n\n// A Node stream yields Buffers unless you ask for text:\nnodeReadable.setEncoding(\"utf8\");\nfor await (const piece of detokenizeIterable(shield, nodeReadable, { tags })) {\n  response.write(piece);\n}\n```\n\nBoth flavors take **decoded text** — a `Buffer`/`Uint8Array` chunk throws `ShieldError`, because a byte chunk can end mid-UTF-8-sequence. Decode first with `setEncoding(\"utf8\")`, a `TextDecoderStream`, or one `TextDecoder` reused with `{ stream: true }`.\n\nRepeated tokens cost one lookup per stream (denials cached too, up to a 10,000-unique-token per-stream cache). On a mid-stream detokenize failure the default is to error the stream; `errorMode: \"passthrough\"` emits the affected tags verbatim and keeps streaming, with an optional `onDetokenizeError` observer so failures aren't invisible.\n\nTags resolve per incoming chunk, which keeps output incremental. Streaming input is usually model output an attacker can influence directly, so bound it with a `signal` + timeout just like `restore()` — see [docs/security-notes.md](https://github.com/altrsoftware/shield-sdk-node/blob/main/docs/security-notes.md).\n\n## Retries\n\nTransient failures — network errors, `429`, `500`, `502`, `503`, and `504` — retry automatically with jittered exponential backoff (base 500 ms, capped at 5 s), honoring a server `Retry-After`. Both endpoints are safe to repeat: detokenize is a pure read, and a deterministic vault returns the identical token on a protect retry. The default budget is 2 retries — tune with `maxRetries` on the constructor (credential form only; a custom `transport` owns its own retry policy) or per call (`{ maxRetries: 0 }` disables). A gateway-authorizer 401/403 additionally replays exactly once with a freshly minted JWT. Detokenize batches that trip the `422` response-size cap split themselves automatically and continue.\n\n## Cancellation & timeouts\n\nEvery network-touching call accepts an `AbortSignal`, combined with the transport's own per-request timeout (default 30 s, configurable via `timeoutMs`):\n\n```ts\nconst controller = new AbortController();\nconst pending = shield.protect(text, { signal: controller.signal });\ncontroller.abort(); // pending rejects with the abort reason, not a ShieldNetworkError\n```\n\n`restore()` has no built-in cap on how many tokens it extracts — untrusted text carrying thousands of token-shaped tags fans out into many sequential round trips. When `text` is untrusted (stored chat history, an uploaded document), pass a `signal` wired to a timeout and bound the size of `text` yourself (details in [docs/security-notes.md](https://github.com/altrsoftware/shield-sdk-node/blob/main/docs/security-notes.md)):\n\n```ts\nconst controller = new AbortController();\nconst timer = setTimeout(\n  () => controller.abort(new Error(\"restore timeout\")),\n  5_000,\n);\ntry {\n  const { text } = await shield.restore(untrustedStoredText, {\n    signal: controller.signal,\n  });\n} finally {\n  clearTimeout(timer);\n}\n```\n\nAn abort also cuts short any in-progress retry backoff. To identify your application in server-side logs, set `appInfo: { name: \"my-service\", version: \"2.1.0\" }` on the client — it is appended to the SDK's `user-agent` header.\n\n## Runtime support\n\nThe SDK depends only on Web Platform APIs — `fetch`, `TextEncoder`/`TextDecoder`, and Web Crypto (via [jose](https://github.com/panva/jose)). It touches no Node-only globals (`Buffer`/`process`), so it runs anywhere those standards exist:\n\n| Runtime                                          | Supported                       |\n| ------------------------------------------------ | ------------------------------- |\n| Node.js (`^20.19.0 \\|\\| ^22.12.0 \\|\\| >=23.0.0`) | Yes                             |\n| Edge runtimes / Cloudflare & Vercel Workers      | Yes                             |\n| Deno / Bun                                       | Yes                             |\n| Browsers & bundlers (Vite, esbuild, webpack)     | Yes — but read the caveat below |\n\n**Run it server-side.** The client signs requests with your RSA **private key**, so it belongs anywhere that key is a server secret — a Node route handler, a Server Component/Action, an edge function, a worker. Do **not** import it into a browser bundle or a `\"use client\"` component: that ships the private key to the client.\n\n**Next.js:** works on both the **Node.js runtime** and the **Edge runtime** — route handlers, server components, server actions, and middleware are all fine. The only unsupported context is client components (see above).\n\nThe `engines.node` floor applies only to **CommonJS** `require()` callers (`require(esm)` needs Node ≥ 20.19 / 22.12 / 23); plain ESM `import` works on any modern runtime. CommonJS TypeScript consumers also need **TypeScript ≥ 5.8** with `module`/`moduleResolution: \"nodenext\"` — on older TypeScript, use a dynamic `import()` instead.\n\n## Error handling\n\nAll SDK errors extend `ShieldError`:\n\n| Error                         | Meaning                                                                                                                                                                                                                        |\n| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `ShieldError`                 | base class; also thrown directly for client-side misconfiguration — e.g. a `privateKeyPem` that cannot be imported as a PKCS#8 RSA key for RS256 or cannot sign with it, with the underlying `jose`/WebCrypto error on `cause` |\n| `ShieldApiError`              | non-2xx response; carries `status`, `errorCode`, raw `body`, and the gateway `requestId` (quote it to support)                                                                                                                 |\n| `ShieldAuthError`             | gateway authorizer denied the JWT (SDK already retried once with a fresh mint)                                                                                                                                                 |\n| `ShieldPayloadTooLargeError`  | 413 — body over the protect (500 KB = 500,000 bytes) / detokenize (512 KiB = 524,288 bytes) caps; also thrown client-side pre-flight                                                                                           |\n| `ShieldResponseTooLargeError` | 422 — the restored payload would exceed the response cap; split the batch                                                                                                                                                      |\n| `ShieldNetworkError`          | no HTTP response (DNS/connection/timeout); underlying error on `cause`                                                                                                                                                         |\n| `ShieldSpliceError`           | the server's findings violated invariants — the SDK fails loud rather than corrupt data                                                                                                                                        |\n| `ShieldTagValidationError`    | tag rejected client-side before any network call                                                                                                                                                                               |\n\n```ts\nimport { ShieldApiError, ShieldAuthError } from \"@altrsoftware/shield\";\n\ntry {\n  await shield.protect(text);\n} catch (err) {\n  if (err instanceof ShieldAuthError) {\n    // signing key not registered / rotated away — check key_registration slots\n  } else if (err instanceof ShieldApiError) {\n    console.error(err.toSafeString()); // \"ShieldApiError: HTTP 400 (error_code 700400) [req abc-123]\"\n  }\n  throw err;\n}\n```\n\n**Catch order:** `ShieldAuthError`, `ShieldPayloadTooLargeError`, and `ShieldResponseTooLargeError` all **extend** `ShieldApiError`, so test the specific subclasses _before_ the `ShieldApiError` base (as above) — a `ShieldApiError` branch placed first swallows all three.\n\n**Logging caution:** `ShieldApiError.message` and `.body` reproduce the server response verbatim, which for validation errors can echo fragments of the submitted (sensitive) text. Use `toSafeString()` in logs and telemetry.\n\nThe numeric `errorCode` (present on `apiError`-shaped bodies) uses the `700xxx` family — the last three digits mirror the HTTP status; full reference in [docs/error-codes.md](https://github.com/altrsoftware/shield-sdk-node/blob/main/docs/error-codes.md). A 401/403 whose body is **not** `apiError`-shaped is the gateway authorizer rejecting the JWT before it reached Shield — surfaced as `ShieldAuthError` after one fresh-bearer replay.\n\n## Learn more\n\nSee the repository's `docs/` for the [token-envelope grammar](https://github.com/altrsoftware/shield-sdk-node/blob/main/docs/envelope.md), the [full usage guide](https://github.com/altrsoftware/shield-sdk-node/blob/main/docs/node-sdk.md), and the [security notes](https://github.com/altrsoftware/shield-sdk-node/blob/main/docs/security-notes.md) (private-key handling, TLS enforcement, untrusted-input costs), and `examples/` for runnable programs.\n\n## Support\n\nNew features and fixes land on the latest major version only; the supported Node.js range is the `engines` field. File bugs and feature requests on the repository issue tracker; report suspected vulnerabilities privately per [SECURITY.md](https://github.com/altrsoftware/shield-sdk-node/blob/main/SECURITY.md), never as public issues. Contributions are welcome — see [CONTRIBUTING.md](https://github.com/altrsoftware/shield-sdk-node/blob/main/CONTRIBUTING.md).\n\n## License\n\n[Apache-2.0](./LICENSE) © ALTR Solutions, Inc.\n","readmeFilename":"README.md"}