{"_id":"@codecai/web-safety","_rev":"2-3f0e1eeb81933948af44c3cdc787a0a8","name":"@codecai/web-safety","dist-tags":{"latest":"0.5.0"},"versions":{"0.4.1":{"name":"@codecai/web-safety","version":"0.4.1","keywords":["codec","safety","prefilter","secrets","pii","classifier","guardrails","llm"],"license":"MIT","_id":"@codecai/web-safety@0.4.1","maintainers":[{"name":"wdunn001","email":"wdunn001@gmail.com"}],"homepage":"https://github.com/wdunn001/Codec/tree/main/packages/web-safety","bugs":{"url":"https://github.com/wdunn001/Codec/issues"},"dist":{"shasum":"bbe92bdb22c536f9d591869624afd7c45090d807","tarball":"https://registry.npmjs.org/@codecai/web-safety/-/web-safety-0.4.1.tgz","fileCount":42,"integrity":"sha512-il/DuF1UfynxeQlFkkyRBRFcqyFlim7mLnQsgs1kuwcnwL4Kmq+IZBqNNL5UlQsplFTF6wEzTR9DpwAFLVbnAw==","signatures":[{"sig":"MEQCIHdDaHDRjJlFdjC1RTFr4lgdOdrHLKZ6A8c9khpKjS+3AiAhiNllHEGvKiaaQfZbRu7RXgox2b+z14e3rm+m/iDYHw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":192552},"main":"./dist/cjs/index.cjs","type":"module","types":"./dist/types/index.d.ts","module":"./dist/esm/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/types/index.d.ts","import":"./dist/esm/index.js","default":"./dist/esm/index.js","require":"./dist/cjs/index.cjs"},"./gate":{"types":"./dist/types/gate.d.ts","import":"./dist/esm/gate.js","require":"./dist/cjs/gate.cjs"},"./registry":{"types":"./dist/types/registry.d.ts","import":"./dist/esm/registry.js","require":"./dist/cjs/registry.cjs"},"./prefilter":{"types":"./dist/types/prefilter.d.ts","import":"./dist/esm/prefilter.js","require":"./dist/cjs/prefilter.cjs"},"./classifiers/llama-guard-3-1b":{"types":"./dist/types/classifiers/llama-guard-3-1b.d.ts","import":"./dist/esm/classifiers/llama-guard-3-1b.js","require":"./dist/cjs/classifiers/llama-guard-3-1b.cjs"},"./classifiers/prompt-guard-86m":{"types":"./dist/types/classifiers/prompt-guard-86m.d.ts","import":"./dist/esm/classifiers/prompt-guard-86m.js","require":"./dist/cjs/classifiers/prompt-guard-86m.cjs"}},"gitHead":"53a71503f33fca91d353d7dfbeddfcf050c2d37a","scripts":{"test":"node --test --import tsx test/*.test.ts","build":"npm run build:esm && npm run build:cjs && npm run build:types","clean":"node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"","build:cjs":"tsc -p tsconfig.cjs.json && node -e \"require('fs').readdirSync('dist/cjs').filter(f=>f.endsWith('.js')).forEach(f=>require('fs').renameSync('dist/cjs/'+f,'dist/cjs/'+f.replace(/\\.js$/,'.cjs')))\"","build:esm":"tsc -p tsconfig.esm.json","build:types":"tsc -p tsconfig.types.json"},"_npmUser":{"name":"wdunn001","email":"wdunn001@gmail.com"},"repository":{"url":"git+https://github.com/wdunn001/Codec.git","type":"git","directory":"packages/web-safety"},"_npmVersion":"10.8.2","description":"Optional client-side safety layer for the Codec binary transport — prefilter (secrets / PII / high-entropy detection) and classifier registry. Pairs with @codecai/web; ships zero classifier weights of its own.","directories":{},"sideEffects":false,"_nodeVersion":"20.20.2","dependencies":{"@codecai/web":"^0.4.0"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.2","typescript":"^5.7.3","@mlc-ai/web-llm":"^0.2.0","@huggingface/transformers":"^3.0.0"},"peerDependencies":{"@mlc-ai/web-llm":"^0.2.0","@huggingface/transformers":"^3.0.0"},"peerDependenciesMeta":{"@mlc-ai/web-llm":{"optional":true},"@huggingface/transformers":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/web-safety_0.4.1_1778954320679_0.7769012499153682","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@codecai/web-safety","version":"0.5.0","description":"Optional client-side safety layer for the Codec binary transport — prefilter (secrets / PII / high-entropy detection) and classifier registry. Pairs with @codecai/web; ships zero classifier weights of its own.","license":"MIT","type":"module","main":"./dist/cjs/index.cjs","module":"./dist/esm/index.js","types":"./dist/types/index.d.ts","exports":{".":{"types":"./dist/types/index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.cjs","default":"./dist/esm/index.js"},"./prefilter":{"types":"./dist/types/prefilter.d.ts","import":"./dist/esm/prefilter.js","require":"./dist/cjs/prefilter.cjs"},"./gate":{"types":"./dist/types/gate.d.ts","import":"./dist/esm/gate.js","require":"./dist/cjs/gate.cjs"},"./registry":{"types":"./dist/types/registry.d.ts","import":"./dist/esm/registry.js","require":"./dist/cjs/registry.cjs"},"./classifiers/prompt-guard-86m":{"types":"./dist/types/classifiers/prompt-guard-86m.d.ts","import":"./dist/esm/classifiers/prompt-guard-86m.js","require":"./dist/cjs/classifiers/prompt-guard-86m.cjs"},"./classifiers/llama-guard-3-1b":{"types":"./dist/types/classifiers/llama-guard-3-1b.d.ts","import":"./dist/esm/classifiers/llama-guard-3-1b.js","require":"./dist/cjs/classifiers/llama-guard-3-1b.cjs"}},"sideEffects":false,"scripts":{"build":"npm run build:esm && npm run build:cjs && npm run build:types","build:esm":"tsc -p tsconfig.esm.json","build:cjs":"tsc -p tsconfig.cjs.json && node -e \"require('fs').readdirSync('dist/cjs').filter(f=>f.endsWith('.js')).forEach(f=>require('fs').renameSync('dist/cjs/'+f,'dist/cjs/'+f.replace(/\\.js$/,'.cjs')))\"","build:types":"tsc -p tsconfig.types.json","test":"node --test --import tsx test/*.test.ts","clean":"node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""},"dependencies":{"@codecai/web":"^0.5.0"},"peerDependencies":{"@huggingface/transformers":"^3.0.0","@mlc-ai/web-llm":"^0.2.0"},"peerDependenciesMeta":{"@huggingface/transformers":{"optional":true},"@mlc-ai/web-llm":{"optional":true}},"devDependencies":{"@huggingface/transformers":"^3.0.0","@mlc-ai/web-llm":"^0.2.0","tsx":"^4.19.2","typescript":"^5.7.3"},"engines":{"node":">=18"},"keywords":["codec","safety","prefilter","secrets","pii","classifier","guardrails","llm"],"repository":{"type":"git","url":"git+https://github.com/wdunn001/Codec.git","directory":"packages/web-safety"},"homepage":"https://github.com/wdunn001/Codec/tree/main/packages/web-safety","_id":"@codecai/web-safety@0.5.0","gitHead":"3acffb9c70bacc876e64ee42a63beab26cca5f26","bugs":{"url":"https://github.com/wdunn001/Codec/issues"},"_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-LbMMs6ad15scHBq+iotC+6e6zOEzFKMGckxidT+kQIDTpwqM0FRc07BffPqbYhWIPhdczZAlkj9tIO8sQtpqxg==","shasum":"8f295c24ff67b13b3c2006ea790f0b50cd6c5374","tarball":"https://registry.npmjs.org/@codecai/web-safety/-/web-safety-0.5.0.tgz","fileCount":42,"unpackedSize":192552,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDSEjCLHiKeHKVIuxnEo5iDtJJwF4ZZW4utvZi6onbn5gIhAO/acd4ah124fqM9Ewc92KiHm8wcC0j2FyvDZxburMFH"}]},"_npmUser":{"name":"wdunn001","email":"wdunn001@gmail.com"},"directories":{},"maintainers":[{"name":"wdunn001","email":"wdunn001@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/web-safety_0.5.0_1779081187050_0.8693026234316139"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-16T17:58:40.609Z","modified":"2026-05-18T05:13:07.342Z","0.4.1":"2026-05-16T17:58:40.841Z","0.5.0":"2026-05-18T05:13:07.205Z"},"bugs":{"url":"https://github.com/wdunn001/Codec/issues"},"license":"MIT","homepage":"https://github.com/wdunn001/Codec/tree/main/packages/web-safety","keywords":["codec","safety","prefilter","secrets","pii","classifier","guardrails","llm"],"repository":{"type":"git","url":"git+https://github.com/wdunn001/Codec.git","directory":"packages/web-safety"},"description":"Optional client-side safety layer for the Codec binary transport — prefilter (secrets / PII / high-entropy detection) and classifier registry. Pairs with @codecai/web; ships zero classifier weights of its own.","maintainers":[{"name":"wdunn001","email":"wdunn001@gmail.com"}],"readme":"# @codecai/web-safety\n\n> Optional client-side safety layer for the Codec binary transport.\n> Ships in v0.4 alongside [the safety-policy negotiation spec](../../spec/versions/v0.4.md#safety-policy-negotiation).\n> Sibling to [`@codecai/web`](../web); zero classifier weights of its\n> own.\n\n```sh\nnpm install @codecai/web-safety\n```\n\n## What it gives you\n\nTwo complementary layers, both framework-free (no React / Vue / Svelte\ndependency — host apps render their own UI using the gate's view-model):\n\n### Layer 1 — Prefilter (always-on, no network, no model load)\n\nCatches secrets, PII, jailbreak templates, destructive-command literals,\nhigh-entropy strings, and any host-supplied blocked patterns in a user's\ninput **before** it gets tokenized and sent over the wire. Doomed prompts\nnever use uplink, never hit server inference budget, never need\nserver-side moderation.\n\nFive categories:\n\n- **`secrets`** — vendor-anchored regex for AWS access keys, GitHub PATs,\n  OpenAI / Anthropic / Google API keys, Slack / Stripe tokens, SSH\n  private key headers, JWTs.\n- **`pii`** — email, US phone, SSN, Luhn-validated credit-card\n  candidates.\n- **`high_entropy`** — generic catch-all over base64-ish and hex-ish runs\n  (Shannon ≥ 4.0 bits, ≥ 24 chars). Catches API keys of unknown vendors.\n- **`dangerous_action`** — obvious bad asks: jailbreak templates\n  (`ignore previous instructions`, DAN-mode, \"pretend to be\n  unrestricted\"), malware-authoring asks\n  (`write working ransomware...`), exploit-authoring asks\n  (`generate a 0-day exploit for...`), destructive command literals\n  (`rm -rf /`, `dd if=/dev/zero of=/dev/sda`, `DROP TABLE prod`).\n  These are deliberately conservative regex — the semantic\n  classifiers in `classifiers/` (Prompt Guard 86M / Llama Guard 3\n  1B) catch the nuanced cases. The point of regex-level\n  enforcement here is to stop *cleanly-stated* doomed asks in the\n  prefilter, before they consume wire, server inference budget, or\n  classifier-tier compute.\n- **`blocked_action`** — host-supplied patterns. Empty by default; the\n  host application (`leet`, `codec-website`, etc.) passes\n  `blockedActionPatterns: [{ rule, pattern, confidence? }]` to\n  enforce deployment-specific gates (internal hostnames,\n  `--privileged`, \"no `rm -rf` against `/prod`\", regulator-mandated\n  refusals). Patterns live in the host's code, not in this package.\n\nPlus dedup so vendor keys aren't double-reported as both a regex hit\nand a generic entropy hit.\n\n> **The prefilter rules are public by design.** They ship in this\n> npm package's source — visible via `npm view @codecai/web-safety`\n> or by reading `src/prefilter.ts`. That's the *opposite* boundary\n> from the [server-side policy disclosure\n> contract](https://github.com/wdunn001/Codec/blob/main/spec/versions/v0.4.md#safety-policy-negotiation):\n> operator-internal banned-token-ID lists, classifier thresholds,\n> and multi-token patterns live in `codec-supervisor/policies_dir/`\n> and *never* cross the wire. The published policy descriptor at\n> `.well-known/codec/policies/<id>.json` lists only categories +\n> action types + classifier family + summary counts. Server-side\n> private; client-side public. Complementary, not duplicating —\n> see the top-of-file comment in `src/prefilter.ts` for the full\n> layer-mapping.\n\n```ts\nimport { SafetyGate } from \"@codecai/web-safety\";\n\nconst gate = new SafetyGate({\n  audit: (e) => {\n    // categories + counts only, never values\n    if (e.kind === \"blocked\") console.info(`prefilter: ${e.categories}`);\n  },\n});\n\nconst decision = gate.check(\"paste with AKIA1234567890ABCDEF in it\");\nif (decision.kind === \"blocked\") {\n  // Host renders a redact / send-anyway / cancel dialog using\n  // decision.matches; user picks; gate.apply() returns send or cancel.\n  const action = await showHostModal(decision);\n  const result = gate.apply(decision, action);\n  if (result.kind === \"cancel\") return;\n  prompt = result.text;  // possibly redacted with [REDACTED:<rule>]\n}\n// ... tokenize and send via @codecai/web as usual\n```\n\n### Layer 3 — Browser-side classifier registry (opt-in)\n\nModular `SafetyClassifier` interface mirroring the\n[`codec-supervisor` server registry](https://github.com/wdunn001/codec-supervisor)\nexactly — same shapes, same canonical-categories list, so policy\ndescriptors talk about both sides without distinguishing host.\n\nTwo shipped implementations:\n\n- **Prompt Guard 86M via Transformers.js** (tier 1, default) — ~80 MB\n  ONNX, CPU/WASM, no WebGPU dependency. Best for always-on\n  inbound-prompt classification.\n- **Llama Guard 3 1B via codec-web-llm** (tier 2, opt-in) — ~1 GB\n  WebGPU quant. Catches what Prompt Guard misses; same 14-category\n  Llama Guard taxonomy as the server-side classifier so policy\n  decisions are symmetric across mesh peers.\n\n```ts\nimport { registerPromptGuard86m } from \"@codecai/web-safety/classifiers/prompt-guard-86m\";\nimport { registerLlamaGuard31B } from \"@codecai/web-safety/classifiers/llama-guard-3-1b\";\nimport { resolveClassifier } from \"@codecai/web-safety\";\n\nregisterPromptGuard86m();\nregisterLlamaGuard31B();  // opt-in\n\nconst { classifier, downgraded } = await resolveClassifier(\"Llama-Guard-3-1B\");\n// downgraded === true → registry fell back to Prompt Guard because\n// the device couldn't load Llama Guard (no WebGPU, insufficient memory).\n// Surface a \"downgraded enforcement\" badge in your UI.\n\nconst result = await classifier.score({\n  form: \"text\",\n  payload: userMessage,\n});\nif (result.scores.jailbreak >= 0.5) {\n  // host policy decides: stop, redact, regenerate, flag\n}\n```\n\n## Architecture notes\n\n- **Framework-free.** No React/Vue/Svelte dependency. Hosts render\n  modals in their own component system using `SafetyGate`'s\n  `PrefilterDecision` view-model.\n- **Stable cross-stack contract.** A policy's `classifier.family`\n  string resolves to the same model on browser + server when both\n  ship the matching registry entry — so admin UIs can bind one\n  policy and have it enforced consistently across hosts.\n- **Audit hook receives only categories + counts.** Never log\n  matched values to telemetry; the audit callback intentionally\n  doesn't expose them.\n- **Per-pattern actions** match the\n  [`safety-policy.schema.json`](../../spec/safety-policy.schema.json)\n  contract: `stop` / `redact` / `regenerate` / `flag`. The browser\n  prefilter handles the first three actions itself; `flag` annotates\n  and continues.\n\n## Peer dependencies (optional)\n\n- [`@huggingface/transformers`](https://www.npmjs.com/package/@huggingface/transformers)\n  — only needed if you `registerPromptGuard86m()`. Without it, you can\n  still use the prefilter + the gate + the registry interface.\n- [`@mlc-ai/web-llm`](https://github.com/mlc-ai/web-llm) — only\n  needed if you `registerLlamaGuard31B()`. Same property.\n\nBoth are declared as peer deps in `package.json` with\n`peerDependenciesMeta.optional: true` so consumers that don't use them\nnever install them.\n\n## Tests\n\n```sh\nnpm test\n```\n\nCurrently 62 tests covering prefilter (vendor regexes, PII Luhn-gating,\nentropy-only confidence, dedup, redaction), gate state machine\n(check/apply transitions, audit events), registry (register/unregister/\nfallback semantics, capability detection), Prompt Guard 86M (label\nmapping for all variants), Llama Guard 3 1B (prompt builder + parser +\nclassifier round-trip with stubbed generator). All run without\nnetwork or model weights — generator injection is the default test\npattern.\n\n## See also\n\n- [`spec/versions/v0.4.md`](../../spec/versions/v0.4.md) — the safety-\n  policy negotiation spec on the wire.\n- [`spec/safety-policy.schema.json`](../../spec/safety-policy.schema.json)\n  — the publishable descriptor format.\n- [`@codecai/web`](../web) — base tokenizer/detokenizer this package\n  pairs with.\n- [`codec-supervisor`](https://github.com/wdunn001/codec-supervisor) —\n  the server-side companion shipping the policy admin REST + the\n  matching `SafetyClassifier` Python registry.\n","readmeFilename":"README.md"}