Skip to content

Sensitive information reference

Arcjet Sensitive Information Detection protects against clients sending you sensitive information such as PII that you do not wish to handle. All detection runs locally in your own environment through a pluggable detection backend: a built-in WebAssembly engine (the default) or the on-device Rampart NER model, which detects names, addresses, and government / financial identifiers in addition to the structured types.

Sensitive information detection is configured by allowing or denying a subset of sensitive information. The allow and deny lists are mutually-exclusive, such that using allow will result in a DENY decision for any detected sensitive information that is not specified in the allow list and using deny will result in an ALLOW decision for any detected sensitive information that is not specified in the deny list.

The arcjet client can be configured with one or more Sensitive information rules. In JavaScript / TypeScript these are constructed with sensitiveInfo(options: SensitiveInfoOptionsAllow | SensitiveInfoOptionsDeny). In Python they are constructed with the detect_sensitive_info(...) factory. allow and deny are mutually exclusive in both languages.

The optional backend selects which detection backend runs — omit it to use the built-in engine, or pass rampart() for on-device NER detection.

type SensitiveInfoOptionsAllow = {
mode?: "LIVE" | "DRY_RUN";
allow?: Array<ArcjetSensitiveInfoType>;
contextWindowSize?: number;
// You can also provide a custom detection function to detect other types
// of sensitive information (see below).
detect?: (tokens: string[]) -> Array<SensitiveInfoType | undefined>;
// Optionally swap the default WebAssembly detection engine for a custom
// backend, such as the on-device Rampart NER model (see below).
backend?: SensitiveInfoBackend;
};
type SensitiveInfoOptionsDeny = {
mode?: "LIVE" | "DRY_RUN";
deny?: Array<ArcjetSensitiveInfoType>;
contextWindowSize?: number;
// You can also provide a custom detection function to detect other types
// of sensitive information (see below).
detect?: (tokens: string[]) -> Array<SensitiveInfoType | undefined>;
// Optionally swap the default WebAssembly detection engine for a custom
// backend, such as the on-device Rampart NER model (see below).
backend?: SensitiveInfoBackend;
};
type ArcjetSensitiveInfoType =
| "EMAIL"
| "PHONE_NUMBER"
| "IP_ADDRESS"
| "CREDIT_CARD_NUMBER"
| "GIVEN_NAME"
| "SURNAME"
| "SSN"
| "URL"
| "TAX_ID"
| "BANK_ACCOUNT"
| "ROUTING_NUMBER"
| "GOVERNMENT_ID"
| "PASSPORT"
| "DRIVERS_LICENSE"
| "BUILDING_NUMBER"
| "STREET_NAME"
| "SECONDARY_ADDRESS"
| "CITY"
| "STATE"
| "ZIP_CODE";

Sensitive information detection runs through a detection backend — the engine that scans the request body and identifies entities. The rest of the rule (mode, allow/deny, and the result shape) is the same whichever backend you choose. See Entity detection for exactly what each backend detects.

By default — when you don’t set a backend — the rule runs a WebAssembly engine that detects four structured types: email addresses, phone numbers, IP addresses, and credit card numbers. It runs anywhere the SDK runs, including edge runtimes, needs no extra dependencies, and adds negligible latency, so it’s the right choice for most applications. Extend it with a custom detect function when you need to match your own patterns.

For broader coverage, the @arcjet/sensitive-info-rampart package provides a backend powered by the on-device Rampart named-entity-recognition (NER) model. It runs a ~15 MB quantized ONNX model locally so the rule can detect names, addresses, and government / financial identifiers in addition to the built-in structured types. Everything runs in your own environment — the model weights are bundled with the package, so nothing is fetched at runtime and no data leaves your infrastructure.

Install the package alongside your Arcjet SDK:

Terminal window
npm install @arcjet/sensitive-info-rampart

Then import rampart and pass it as the rule’s backend. Every entity the model detects is a built-in ArcjetSensitiveInfoType, so you can list the types directly in allow or deny:

Without a backend, the rule continues to use the built-in engine — this package is entirely opt-in.

The full set of types the backend supports is exported as rampartEntities, which is convenient when you want to deny everything it can detect:

import { rampart, rampartEntities } from "@arcjet/sensitive-info-rampart";
sensitiveInfo({ mode: "LIVE", deny: rampartEntities, backend: rampart() });

Rampart is a compact model — a ~14.7 MB artifact (~18.5M parameters, quantized to 4-bit weights) with a 512-token context window — so it is fast enough to run inline on each request:

  • ~6.6 ms median (p50) inference on the Node.js CPU runtime.
  • ~3.9 ms p50 on WebGPU (set device: "webgpu").

On the model author’s held-out benchmark it recalls ~98% of private terms across the seven Latin-script languages it supports (English, Spanish, French, German, Italian, Portuguese, and Dutch).

Accuracy varies by input, so it’s worth knowing the trade-offs:

  • Non-Latin scripts (for example Chinese, Japanese, Korean, Arabic, Hindi, or Cyrillic) have much lower recall — the model is trained on Latin-script text.
  • Government / financial identifiers without a checksum (passports, licences, case numbers) are recognised less reliably than the structured types the deterministic recognizers validate, such as emails and card numbers.
  • Inference runs synchronously in the request path, so this latency is added to each request the rule scans.

These figures come from the Rampart model card, which has the full per-language breakdown, calibration data, and limitations.

The rampart() factory accepts an optional configuration object:

import { defaultRecognizers, rampart } from "@arcjet/sensitive-info-rampart";
rampart({
// Execution device (default: "cpu"). Set to "webgpu" to use a GPU when the
// runtime supports it.
device: "webgpu",
// Minimum confidence score for a model token to count (default: 0.5).
threshold: 0.6,
// Deterministic recognizers to run alongside the model (default:
// `defaultRecognizers`). This is the extension point for custom detection
// with this backend — add a recognizer that returns matched spans. Pass `[]`
// to rely on the model alone.
recognizers: [
...defaultRecognizers,
(value) => {
// Return an array of `{ start, end, type }` spans.
return [];
},
],
});

The model loads once on first use and is reused for every subsequent request.

The Rampart backend loads a native ONNX runtime (@huggingface/transformers / onnxruntime-node) and reads its bundled model weights from disk at runtime. As a result:

  • It requires a server runtime with filesystem and native-addon access — Node.js, Bun, or Deno. It does not run on edge runtimes.
  • It must not be bundled by a server build, or the runtime will fail to locate the model weights.

For Next.js, mark the package and its native dependencies as server external packages so they are loaded from node_modules at runtime instead of bundled, and make sure any route handler that uses the backend runs on the Node.js runtime (the default) rather than the Edge runtime:

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
serverExternalPackages: [
"@arcjet/sensitive-info-rampart",
"@huggingface/transformers",
"onnxruntime-node",
],
};
module.exports = nextConfig;
app/api/arcjet/route.ts
export const runtime = "nodejs";

Arcjet provides a single protect function that is used to execute your protection rules.

This function returns a Promise that resolves to an ArcjetDecision object. This contains the following properties:

  • id (string) - The unique ID for the request. This can be used to look up the request in the Arcjet dashboard. It is prefixed with req_ for decisions involving the Arcjet cloud API. For decisions taken locally, the prefix is lreq_.
  • conclusion (ArcjetConclusion) - The final conclusion based on evaluating each of the configured rules. If you wish to accept Arcjet’s recommended action based on the configured rules then you can use this property.
  • reason (ArcjetReason) - An object containing more detailed information about the conclusion.
  • results (ArcjetRuleResult[]) - An array of ArcjetRuleResult objects containing the results of each rule that was executed.
  • ip (ArcjetIpDetails) - An object containing Arcjet’s analysis of the client IP address. See the SDK reference for more information.

You check if a deny conclusion has been returned by a sensitive info rule by using decision.isDenied() and decision.reason.isSensitiveInfo() (JS) / decision.is_denied() and decision.reason_v2.type == "SENSITIVE_INFO" (Python).

You can iterate through the results and check whether a sensitive info rule was applied:

for (const result of decision.results) {
console.log("Rule Result", result);
}

This example will log the full result as well as the sensitive info rule:

The entity types available to allow/deny depend on the active detection backend. Every type below is a built-in ArcjetSensitiveInfoType, so you can list any of them regardless of backend — a type the active backend doesn’t detect simply never matches. You can also detect your own types with custom entity detection.

Entity typeBuilt-in engine (default)Rampart backend
EMAIL✅ Model + recognizer
PHONE_NUMBER✅ Model + recognizer
IP_ADDRESS✅ Recognizer
CREDIT_CARD_NUMBER✅ Recognizer
URL✅ Model + recognizer
SSN✅ Recognizer
GIVEN_NAME✅ Model
SURNAME✅ Model
TAX_ID✅ Model
BANK_ACCOUNT✅ Model
ROUTING_NUMBER✅ Model
GOVERNMENT_ID✅ Model
PASSPORT✅ Model
DRIVERS_LICENSE✅ Model
BUILDING_NUMBER✅ Model
STREET_NAME✅ Model
SECONDARY_ADDRESS✅ Model
CITY✅ Model
STATE✅ Model
ZIP_CODE✅ Model
Customdetect callbackrecognizers option

For the Rampart backend, Model means the type is detected by the on-device NER model, and Recognizer means it is detected by a deterministic, validated pattern (mirroring Rampart’s deterministic redaction layer). Where the model and a recognizer overlap on the same text, the recognizer wins.

The structured types are matched the same way by the built-in engine and by the Rampart recognizers.

Card numbers can be detected. The values 4242424242424242, 4000 0566 5566 5556, and 3782-8224-6310005 match but 4242424242424241 does not. Whether something looks like a card number is based on its initial and final digits. The initial digits determine the expected length (min, max) of the total number. The final digit is a Luhn check digit. Spaces and dashes are ignored because users often group digits.

Email addresses can be detected. The values alice@example.com and bob.smith@subdomain.example.com match but alice.example.com does not. A dotless domain (user@localhost) and a domain literal (user@[127.0.0.1], if it contains a valid IP) also match.

IP addresses can be detected. The values 127.0.0.1 and ::1 match but 012.004.002.000 does not. IP v4 and v6 addresses are supported. Whether something looks like an IP address is based on whether it parses as IpAddr from std::net.

Phone numbers can be detected. The values +1 (555) 555-5555 and (020) 334 4522 match but 555-1234 does not. As phone numbers have a wide variety of sizes and formats, special short numbers such as 911 or 14 020 are not detected. Several characters such as dots (.), dashes (-), and spaces are allowed to separate digit groups. The country code can be prefixed with plus (+) or omitted, the next region group can be in parentheses.

When using the built-in engine you can provide a custom detect function, which enables you to detect entities that Arcjet doesn’t support out of the box using custom logic.

The function will take a list of tokens and must return a list of either undefined, if the corresponding token in the input list is not sensitive, or the name of the entity if it does match. The number of tokens that are provided to the function is controlled by the contextWindowSize option, which defaults to 1. If you need additional context to perform detections then you can increase this value.

Arcjet is designed to fail open so that a service issue or misconfiguration does not block all requests. The SDK will also time out and fail open after 1000ms in development (see ARCJET_ENV) and 500ms otherwise. However, in most cases, the response time will be less than 20-30ms.

If there is an error condition when processing the rule, Arcjet will return an ERROR result for that rule and you can check the message property on the rule’s error result for more information.

If all other rules that were run returned an ALLOW result, then the final Arcjet conclusion will be ERROR.

Arcjet runs the same in any environment, including locally and in CI. You can use the mode set to DRY_RUN to log the results of rule execution without blocking any requests.

We have an example test framework you can use to automatically test your rules. Arcjet can also be triggered based using a sample of your traffic.

See the Testing section of the docs for details.