rampart-edge is a 1.45 MB ONNX model that decides whether a chat message contains personally identifiable information before the message leaves the user's device, and marks the PII spans in the same call. It is the model half of Rampart Edge, a client-side PII gate released by National Design Studio; the other half is a set of deterministic validators, and the gate blocks when either half says BLOCK.
Install @nationaldesignstudio/rampart-edge and onnxruntime-web, and serve onnx/model_q4.onnx and vocab.txt from this repo under one URL (here /models/rampart-edge/, keeping the onnx/ subfolder).
import { createGate } from "@nationaldesignstudio/rampart-edge";
const gate = await createGate({ modelBaseUrl: "/models/rampart-edge/" });
const landlord = await gate.check("My landlord Marcus Delgado won't return my security deposit.");
landlord.verdict; // "BLOCK"
landlord.spans; // [{ start: 12, end: 26, type: "NAME", probability: 0.99995, source: "model" }]
const park = await gate.check("Is Shenandoah National Park open this weekend?");
park.verdict; // "ALLOW"
const ssn = await gate.check("My SSN is 452-78-1934.");
ssn.basis; // "validator-and-model"
createGate checks both files against the byte counts and SHA-256s below before it uses them. modelBaseUrl is resolved against the page URL, so a path like /models/rampart-edge/ works in a browser only; in Bun or Node pass an absolute http:// URL to a directory that serves the two files. Without modelBaseUrl the files come from this Hugging Face repository.
| file | bytes | sha256 |
|---|---|---|
onnx/model_q4.onnx | 1,454,373 | 73fce00c229d8318d7e1f8decccc5ad2dbf7aa45a872364b54d632c38a9eb16c |
vocab.txt | 173,327 | ee0684683c7fb15744d0136e671cd7def76ae4d4ddafb72ced864e94c8d32c9a |
tokenizer.json, tokenizer_config.json and special_tokens_map.json describe the same tokenizer for other runtimes. Model + vocab: 1,289,572 bytes gzipped (gzip -9, each file separately).
Graph inputs input_ids, attention_mask (int64, [1, n]); outputs gate_logit (float32, [1]; BLOCK probability = sigmoid) and token_logits (float32, [1, n, 19]) over the BIO labels in config.json.
microsoft/xtremedistil-l6-h256-uncased, truncated from 6 layers to 4, with the first layer's weights shared by all four slots.vocab × 48 table plus 48 → 256 projection, initialized from the SVD of the pretrained table) and pruned to 23,910 word pieces.[CLS] → gate_logit; every token → token_logits (O plus B-/I- for nine span types).tokenizer_config.json).[CLS]/[SEP]), stride 96; a message's probability is the max over its windows.t = 0.417801: the lowest dev threshold with at most 1% false blocks. Not tuned on any benchmark.span_threshold: null); overlapping windows hand over at a token both label O.onnxruntime-web 1.30.0, Chromium.check per message, single-thread WASM, Chromium 156, Linux x86 CPU).| type | what it covers |
|---|---|
NAME | a person's full name (bare first names, bare surnames and listed public figures are not spans) |
ADDRESS | a complete residential or mailing address (a bare street, city, state or ZIP is not) |
EMAIL | a personal email address (.gov/.mil and role mailboxes are not) |
PHONE | a complete phone number |
GOV_ID | US government identifiers: SSN, ITIN, EIN, Medicare MBI, A-number, USCIS receipt, passport, driver's license |
FINANCIAL | card, IBAN, bank account and routing numbers |
DOB_KEYED | a date of birth together with the person it belongs to (a bare date is not) |
URL_HANDLE | a personal profile URL or handle |
OTHER_ID | case, claim, customer and medical record numbers tied to a person |
The runtime unions model spans with validator hits (validators keep their type), adds a DOB_KEYED span for a date next to a birth keyword and widens a model span that covers part of a date to the whole date. The probability on a model span is a ranking score, not a calibrated chance.
Dev set (32,202 rows), gate = validators OR model, threshold 0.417801:
| metric | value |
|---|---|
| catch at ≤ 1% false blocks | 98.24% |
| false blocks | 0.99% |
| AUROC | 0.99865 |
| hard ALLOW set (144 rows: company names near amounts, OCR noise, redaction placeholders), false blocks | 19 (13.2%) |
The 19 hard-ALLOW false blocks: 11 company names near contract amounts, 5 redaction placeholders, 3 OCR noise.
Frozen test test.v1 (30,863 rows, sha256 5671887f…), scored once for the shipped model after the threshold was fixed on dev. Nothing was chosen on test.
| system | catch | false blocks |
|---|---|---|
| Rampart Edge (t = 0.417801) | 98.19% | 1.13% |
| Rampart 1 default (all labels) | 87.48% | 11.77% |
| validators alone | 51.70% | 0.05% |
AUROC on test.v1: 0.99694.
Historical false-positive regression set (137 ALLOW / 29 BLOCK): 4 ALLOW rows blocked (two tracking numbers next to a government-id word, one public SSA login URL, one format-example passport number); 29 of 29 BLOCK twins caught.
Public redacted-source benchmark (seven FOIA / court release sets, held-out partition): 98.44% catch (441 of 448), 21 of 470 benign pages blocked (4.5%), shipped runtime.
Public PII systems on OpenPII (ai4privacy/pii-masking-openpii-1.5m validation @ a785eb5, 30,000-row held-out slice, 28,428 PII / 1,572 ALLOW; message-level, a row is PII if any gold span maps to a type Rampart Edge blocks; baselines at default settings, mapping fixed before scoring):
| system | catch | false blocks | size |
|---|---|---|---|
| Rampart Edge (validators OR model) | 89.8% | 9.2% | 1.45 MB |
| validators alone | 59.6% | 0.0% | — |
| Rampart 1, all labels on | 99.4% | 48.7% | 14.7 MB |
Microsoft Presidio + spaCy en_core_web_lg | 96.4% | 67.7% | 445 MB |
| GLiNER small v2.1 | 91.1% | 62.0% | 613 MB |
| BERT-small PII (gravitee-io) | 96.6% | 65.5% | 115 MB |
| OpenAI Privacy Filter | 98.0% | 46.1% | 2.83 GB |
Rampart Edge misses full names (49.6% caught when the name is the only PII), street addresses (48.2%) and non-US ids (36–65%); emails 100%, phones 94.3%. OpenPII is not a fully fair test: it labels by different rules than Rampart Edge, tagging non-US national ids and ID-shaped strings Rampart Edge allows and tagging some full names as first names only. OpenPII is synthetic, multilingual and mostly non-US; Rampart 1 was fine-tuned on about 1.4 million OpenPII rows; Rampart Edge was not trained on it. Without the 1,564 id-only rows (non-US national ids are out of scope) catch is 90.2%.
Span redaction, shipped runtime, class-agnostic matching:
| dataset | leak | over-redaction | relaxed P / R / F1 |
|---|---|---|---|
| Public redacted-source benchmark, held-out partition: 118 PDF documents, 1,857 gold spans | 10.2% | 8.0% | 0.755 / 0.815 / 0.784 |
Synthetic PDF dev set (span.pdf-dev.v2): 268 documents, 1,302 gold spans | 2.8% | 45.6% | 0.536 / 0.963 / 0.689 |
Leak is the share of gold PII values still visible after redaction; on the benchmark the leaks are mostly OTHER_ID (58%), URL_HANDLE (62%) and NAME (12%). Over-redaction is the share of public terms (agencies, places, hotlines, listed officials) redacted; on the PDF set it is agency hotlines (188 of 189) and named public officials (511 of 677). The synthetic PDF set was used for model selection; the benchmark was not.
@nationaldesignstudio/rampart-edge; alone, the model catches less structured PII.The gate thresholds the maximum window probability at t = 0.417801, chosen on the dev set as the lowest threshold that keeps false blocks at or under 1%. The probability is a ranking score, not a calibrated chance that a message contains PII: a message at p = 0.4 is not "40% likely" to carry PII. Raise the threshold to trade catch for fewer false blocks; GateOptions.threshold overrides the default. The validators are not thresholded; a hard validator hit blocks at any threshold.
The steps below run from a clone of the source repository (bun install); the Hugging Face files alone hold the weights, tokenizers and this card.
bun run test checks the pinned SHA-256s and reruns the reference texts in __tests__/fixtures/joint-reference.json against the Python probabilities and spans.@software{rampartedge_2026,
author = {{National Design Studio}},
title = {Rampart Edge: a browser-side PII gate},
year = {2026},
url = {https://huggingface.co/nationaldesignstudio/rampart-edge},
license = {CC0-1.0}
}
CC0 1.0. No attribution required.