Sensitive information reference for SvelteKit
Arcjet Sensitive Information Detection stops clients from sending you data that you don’t want to handle, such as personally identifiable information (PII). The SDK rule runs detection in your own environment with the on-device Rampart named-entity recognition (NER) model. It detects names, addresses, and government or financial identifiers, as well as structured types such as email addresses and card numbers. The text it scans never leaves your environment.
Configuration
Section titled “Configuration”You configure sensitive information detection with a list of entity types to allow or to deny. The two lists are mutually exclusive:
- With
allow, Arcjet denies any detected type that the list doesn’t name. - With
deny, Arcjet denies only the types that the list names.
Add one or more sensitive information rules to the arcjet client. Create them
with sensitiveInfo(...) in JavaScript / TypeScript,
detect_sensitive_info(...) in Python, or
arcjet.SensitiveInfo(arcjet.SensitiveInfoOptions{...}) in Go. The lists are
allow and deny in JS and Python, and Allow and Deny in Go.
Set backend to the Rampart backend:
rampart() in JS and Python, or the Backend that you create with
rampart.New in Go.
type SensitiveInfoOptionsAllow = { mode?: "LIVE" | "DRY_RUN"; allow?: Array<ArcjetSensitiveInfoType>; // The detection backend. Pass `rampart()` from // `@arcjet/sensitive-info-rampart` (see below). backend?: SensitiveInfoBackend;};type SensitiveInfoOptionsDeny = { mode?: "LIVE" | "DRY_RUN"; deny?: Array<ArcjetSensitiveInfoType>; // The detection backend. Pass `rampart()` from // `@arcjet/sensitive-info-rampart` (see below). backend?: SensitiveInfoBackend;};# Signature for arcjet.detect_sensitive_infodef detect_sensitive_info( *, # Required. Mode.LIVE blocks requests; Mode.DRY_RUN logs only. mode: Mode, # Entity types to allow (report only). Mutually exclusive with `deny`. allow: Sequence[str | SensitiveInfoEntityType] = (), # Entity types to deny. Mutually exclusive with `allow`. deny: Sequence[str | SensitiveInfoEntityType] = (), # The detection backend. Pass `rampart()` from # `arcjet_sensitive_info_rampart` (see below). backend: SensitiveInfoBackend | None = None, # Rate-limit characteristics (see https://docs.arcjet.com/fingerprints) characteristics: Sequence[str] = (),) -> SensitiveInfoDetection: ...
# Entity types are SensitiveInfoEntityType enum members or their string# names, such as SensitiveInfoEntityType.EMAIL or "GIVEN_NAME". See the entity# types table later on this page for the full list.type SensitiveInfoOptions struct { Mode Mode Allow []EntityType // mutually exclusive with Deny Deny []EntityType Backend SensitiveInfoBackend // create with rampart.New}
// Entity types: SensitiveInfoEmail, SensitiveInfoPhoneNumber,// SensitiveInfoIPAddress, SensitiveInfoCreditCardNumber, SensitiveInfoURL,// SensitiveInfoSSN, SensitiveInfoGivenName, SensitiveInfoSurname,// SensitiveInfoTaxID, SensitiveInfoBankAccount, SensitiveInfoRoutingNumber,// SensitiveInfoGovernmentID, SensitiveInfoPassport,// SensitiveInfoDriversLicense, SensitiveInfoBuildingNumber,// SensitiveInfoStreetName, SensitiveInfoSecondaryAddress, SensitiveInfoCity,// SensitiveInfoState, SensitiveInfoZipCode.// Or deny the whole set with rampart.Entities().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";Per route versus hooks
Bot protection rules can be configured in two ways:
- Per route: The rule is defined in the route handler itself. This allows you to configure the rule alongside the code it is protecting which is useful if you want to use the decision to add context to your own code. However, it means rules are not located in a single place.
- Hooks: The rule is defined as a hook. This lets you configure rules in a single place or apply them globally to all routes, but it means the rules are not located alongside the code they are protecting.
Per route
This configures bot protection on a single route.
import { env } from "$env/dynamic/private";import arcjet, { sensitiveInfo } from "@arcjet/sveltekit";import { rampart } from "@arcjet/sensitive-info-rampart";import { error, type RequestEvent } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY!, // Get your Arcjet key from https://console.arcjet.com rules: [ sensitiveInfo({ deny: ["EMAIL"], mode: "LIVE", backend: rampart(), }), ],});
export async function load(event: RequestEvent) { const decision = await aj.protect(event, { sensitiveInfoValue: await event.request.text(), });
if (decision.isDenied()) { return error(403, "You are suspicious!"); }
return {};}import { env } from "$env/dynamic/private";import arcjet, { sensitiveInfo } from "@arcjet/sveltekit";import { rampart } from "@arcjet/sensitive-info-rampart";import { error } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY, // Get your Arcjet key from https://console.arcjet.com rules: [ sensitiveInfo({ deny: ["EMAIL"], mode: "LIVE", backend: rampart(), }), ],});
export async function load(event) { const decision = await aj.protect(event, { sensitiveInfoValue: await event.request.text(), });
if (decision.isDenied()) { return error(403, "You are suspicious!"); }
return {};}Hooks
This runs on every request to your SvelteKit app – see the SvelteKit Hooks docs for details.
import { env } from "$env/dynamic/private";import arcjet, { sensitiveInfo } from "@arcjet/sveltekit";import { rampart } from "@arcjet/sensitive-info-rampart";import { error } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY, // Get your Arcjet key from https://console.arcjet.com rules: [ // This allows all sensitive entities other than email addresses and those containing a dash character. sensitiveInfo({ mode: "LIVE", // Will block requests, use "DRY_RUN" to log only // allow: ["EMAIL"], Will block all sensitive information types other than email. deny: ["EMAIL"], // Will block email addresses, backend: rampart(), }), ],});
export async function handle({ event, resolve }) { const decision = await aj.protect(event, { sensitiveInfoValue: await event.request.text(), });
if (decision.isDenied()) { return error(400, "Bad request - sensitive information detected"); }
return resolve(event);}import { env } from "$env/dynamic/private";import arcjet, { sensitiveInfo } from "@arcjet/sveltekit";import { rampart } from "@arcjet/sensitive-info-rampart";import { error, type RequestEvent } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY!, // Get your Arcjet key from https://console.arcjet.com rules: [ // This allows all sensitive entities other than email addresses and those containing a dash character. sensitiveInfo({ mode: "LIVE", // Will block requests, use "DRY_RUN" to log only // allow: ["EMAIL"], Will block all sensitive information types other than email. deny: ["EMAIL"], // Will block email addresses, backend: rampart(), }), ],});
export async function handle({ event, resolve,}: { event: RequestEvent; resolve: (event: RequestEvent) => Response | Promise<Response>;}): Promise<Response> { const decision = await aj.protect(event, { sensitiveInfoValue: await event.request.text(), });
if (decision.isDenied()) { return error(400, "Bad request - sensitive information detected"); }
return resolve(event);}Avoid double protection with hooks
If you use Arcjet in hooks and individual routes, you need to be careful that Arcjet is not running multiple times per request. This can be avoided by excluding the individual routes before running Arcjet in the hook.
For example, if you already have a sensitive info rule defined in the API route
at /api/arcjet, you can exclude it from the hook like this:
import { env } from "$env/dynamic/private";import arcjet, { detectBot } from "@arcjet/sveltekit";import { error, type RequestEvent } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY!, rules: [ detectBot({ mode: "LIVE", allow: [], // "allow none" will block all detected bots }), ],});
export async function handle({ event, resolve,}: { event: RequestEvent; resolve: (event: RequestEvent) => Response | Promise<Response>;}): Promise<Response> { // Ignore routes that extend the Arcjet rules // - they will call `.protect` themselves const filteredRoutes = ["/api/arcjet"]; if (filteredRoutes.includes(event.url.pathname)) { // return - route will handle protecttion return resolve(event); }
const decision = await aj.protect(event);
if (decision.isDenied()) { return error(403, "Forbidden"); }
return resolve(event);}On-device detection with Rampart
Section titled “On-device detection with Rampart”Sensitive information detection runs on-device. It pairs the Rampart NER model, which understands context, with deterministic recognizers for structured types. The rule detects names, addresses, and government or financial identifiers, as well as email addresses, card numbers, IP addresses, and phone numbers. For the full list, see Entity types.
The model weights ship with the package, so nothing is fetched at runtime and the text you scan stays in your environment. JavaScript and Python run a ~15 MB quantized ONNX model. Go embeds the same ~15 MB of weights in a nested module and runs inference in pure Go, with no cgo or ONNX runtime.
Install the Rampart package alongside your Arcjet SDK. In Python, it ships as
the sensitive-info-rampart extra of the arcjet package. In Go, it’s a nested
module that bundles the model weights:
npm install @arcjet/sensitive-info-rampartpip install "arcjet[sensitive-info-rampart]"go get github.com/arcjet/arcjet-go/sensitiveinfo/rampartThen import rampart and pass it as the rule’s backend. Create the Go
backend once at startup with rampart.New and reuse it. List the entity types
to detect in allow or deny:
import { env } from "$env/dynamic/private";import arcjet, { sensitiveInfo } from "@arcjet/sveltekit";import { rampart } from "@arcjet/sensitive-info-rampart";import { error, json, type RequestEvent } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY!, // Get your Arcjet key from https://console.arcjet.com rules: [ sensitiveInfo({ mode: "LIVE", // Will block requests, use "DRY_RUN" to log only // Rampart detects names and addresses as well as email addresses // and government identifiers. See the reference for the full list. deny: ["EMAIL", "GIVEN_NAME", "SURNAME", "STREET_NAME", "SSN"], // Run detection on-device with the Rampart NER model. backend: rampart(), }), ],});
export async function POST(event: RequestEvent) { const decision = await aj.protect(event, { sensitiveInfoValue: await event.request.text(), });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isSensitiveInfo()) { console.log("Sensitive info rule", result); } }
if (decision.isDenied()) { return error(403, "Forbidden"); }
// Fail closed when Arcjet couldn't complete the check. if (decision.isErrored()) { return error(503, "Service unavailable"); }
return json({ message: "Hello world" });}import { env } from "$env/dynamic/private";import arcjet, { sensitiveInfo } from "@arcjet/sveltekit";import { rampart } from "@arcjet/sensitive-info-rampart";import { error, json } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY, // Get your Arcjet key from https://console.arcjet.com rules: [ sensitiveInfo({ mode: "LIVE", // Will block requests, use "DRY_RUN" to log only // Rampart detects names and addresses as well as email addresses // and government identifiers. See the reference for the full list. deny: ["EMAIL", "GIVEN_NAME", "SURNAME", "STREET_NAME", "SSN"], // Run detection on-device with the Rampart NER model. backend: rampart(), }), ],});
export async function POST(event) { const decision = await aj.protect(event, { sensitiveInfoValue: await event.request.text(), });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isSensitiveInfo()) { console.log("Sensitive info rule", result); } }
if (decision.isDenied()) { return error(403, "Forbidden"); }
// Fail closed when Arcjet couldn't complete the check. if (decision.isErrored()) { return error(503, "Service unavailable"); }
return json({ message: "Hello world" });}To deny every type that Rampart detects, use the full set: rampartEntities in
JS, rampart_entities in Python, or rampart.Entities() in Go:
import { rampart, rampartEntities } from "@arcjet/sensitive-info-rampart";
sensitiveInfo({ mode: "LIVE", deny: rampartEntities, backend: rampart() });from arcjet import Mode, detect_sensitive_infofrom arcjet_sensitive_info_rampart import rampart, rampart_entities
detect_sensitive_info(mode=Mode.LIVE, deny=rampart_entities, backend=rampart())backend, err := rampart.New(rampart.Options{})arcjet.SensitiveInfo(arcjet.SensitiveInfoOptions{ Mode: arcjet.ModeLive, Deny: rampart.Entities(), Backend: backend,})Model accuracy and performance
Section titled “Model accuracy and performance”Rampart is compact enough to run inline on each request: a ~14.7 MB
artifact with ~18.5M parameters, quantized to 4-bit weights, and a 512-token
context window. Longer text is scanned in overlapping windows. On the Node.js
CPU runtime, median (p50) inference is ~6.6 ms, and ~3.9 ms on
WebGPU (set device: "webgpu" in the JS SDK). The Python backend runs on
ONNX Runtime and uses the CPU by default. To use a
different accelerator, set the providers option. In Go, create the backend
once with rampart.New and reuse it.
On the model author’s held-out benchmark, Rampart recalls ~98% of private terms across the seven Latin-script languages it supports: English, Spanish, French, German, Italian, Portuguese, and Dutch.
Accuracy depends on the input. Keep the following trade-offs in mind:
- 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 or financial identifiers without a checksum (passports, licenses, case numbers) are recognized less reliably than the structured types the deterministic recognizers validate, such as emails and card numbers.
- Latency – inference runs synchronously in the request path, so it adds to the response time of each request that the rule scans.
These figures come from the Rampart model card, which has the full per-language breakdown, calibration data, and limitations.
Rampart options
Section titled “Rampart options”The rampart() factory (JS and Python) or rampart.New (Go) accepts these
options:
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, // Largest input to scan, in characters (default: 100_000). The rule denies // longer input without scanning it. Lower it to bound latency, or raise it // to accept longer text. maxInputChars: 100_000, // Deterministic recognizers to run alongside the model (default: // `defaultRecognizers`). This is the extension point for custom detection – // 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 rampart() factory takes an optional RampartOptions:
from arcjet_sensitive_info_rampart import ( RampartOptions, default_recognizers, rampart,)
rampart( RampartOptions( # Minimum confidence score for a model token to count (default: 0.5). threshold=0.6, # ONNX Runtime execution providers (default: ("CPUExecutionProvider",)). # Set this to use a GPU or other accelerator supported by your ONNX # Runtime install. providers=("CPUExecutionProvider",), # Largest input to scan, in characters (default: 100,000). The rule # denies longer input without scanning it. Lower it to bound latency, # or raise it to accept longer text. max_input_chars=100_000, # Deterministic recognizers to run alongside the model (default: # `default_recognizers`). This is the extension point for custom # detection – add a recognizer that returns matched spans. Pass () to # rely on the model alone. recognizers=( *default_recognizers, lambda value: [], # Return a list of DetectedSpan(start, end, type). ), # Directory containing the model files (default: the bundled models). model_path=None, ))rampart.New takes rampart.Options. Inference is pure Go (no cgo). There is
no device or ONNX provider option:
backend, err := rampart.New(rampart.Options{ // Minimum confidence score for a model token to count (default: 0.5). Threshold: 0.6, // Largest input to scan, in characters (default: 4096). The rule denies // longer input without scanning it. Raise it to accept longer text if you // can afford the extra latency. MaxInputChars: 4096, // Deterministic recognizers run alongside the model (default: // rampart.DefaultRecognizers). Pass an empty non-nil slice to rely on the // model alone. Phone numbers are left to the model because their digit // shape overlaps with financial and government identifiers. // Recognizers: []rampart.Recognizer{},})Long input
Section titled “Long input”Rampart scans text on the request path, so longer text takes longer to scan. To keep that cost predictable, each SDK sets a maximum length, which you can change:
| SDK | Default limit | Option |
|---|---|---|
| JavaScript | 100,000 characters | maxInputChars |
| Python | 100,000 characters | max_input_chars |
| Go | 4,096 characters | MaxInputChars |
Go’s default is lower because the Go backend runs the model in pure Go, with no native runtime to install, and each scan takes several times longer than with the ONNX Runtime that JavaScript and Python use.
A live rule denies text longer than the limit without scanning any of it, so
nothing past the limit goes through unchecked. A rule in dry run mode doesn’t
block: with protect() the SDK logs a warning, and with Guards Arcjet records
the denial. The limit applies to protect(), to the local
sensitive information rule in Arcjet Guards, and to a LOCAL input in an
agent guard policy. JavaScript counts UTF-16 code units, and Python and Go
count Unicode code points.
With protect(), the denial has an error reason instead of a sensitive
information reason, because Arcjet didn’t detect anything. The examples on this
page block it with their catch-all denial. To return a clearer response, check
for the error reason: decision.reason.isError() in JavaScript,
decision.reason_v2.type == "ERROR" in Python, or decision.Reason.IsError()
in Go:
if (decision.isDenied() && decision.reason.isError()) { // The text is longer than the backend's scan limit. return Response.json({ error: "Message too long" }, { status: 413 });}With Guards, the rule result is a denial with no detected entity types.
Choose a limit that fits your latency budget. Each window of about 480 characters is one inference pass, so a higher limit raises the worst-case scan time for a request. The limit must be a positive whole number, and there’s no setting that turns it off. Also cap the request body size in your server or framework, so your application doesn’t read an oversized body into memory.
On-device detection suits typical request fields. For longer text, you have the following options:
- Raise the limit when the extra latency fits your budget.
- Use a server input. Send the text as a
SERVERinput to the server-side sensitive information detector in an agent guard policy, which screens values up to about 100 KB on Arcjet’s servers. Arcjet allows up to 3 seconds to screen a value over 2 KB, and a live rule fails closed if the scan doesn’t finish. Set the client timeout above 3 seconds (the default is 2), so the client doesn’t stop waiting first, and deny the request whenhasFailedOpen()is true. - Split the text into parts, for text longer than about 100 KB, and
screen each part in its own
guard()call. Each call screens up to twoSERVERinputs longer than 2 KB.
To detect protected health information (PHI), always use the server-side detector. On-device detection doesn’t detect the PHI types. For more information, see Detect protected health information (PHI).
In JavaScript and Python, the model loads on first use and is reused for every
later request. In Go, rampart.New loads and dequantizes the weights, so
create the backend once at startup and reuse it. It’s safe for concurrent use.
To detect your own entity types, add a recognizer. See Custom entity detection.
Bundlers and runtimes
Section titled “Bundlers and runtimes”In JavaScript and Python, the Rampart backend loads a native ONNX runtime and reads its model weights from disk at runtime. This affects where it runs and how you build your application:
- It requires a server runtime with filesystem and native-addon access. In JS that means Node.js, Bun, or Deno – it does not run on edge runtimes. In Python it runs anywhere the SDK does (Python 3.10+).
- A server build must not bundle it, or the runtime fails to locate the model weights.
In Go, inference is pure Go. The weights are compiled into the nested module, so there’s no ONNX runtime, native addon, or extra file for a bundler to miss. An application that imports the module adds about 15 MB of weights to its binary.
In Python, the backend depends on
ONNX Runtime (onnxruntime), which the
arcjet[sensitive-info-rampart] extra installs. Inference is synchronous, so
it adds to the response time of each request that the rule scans, with either
the arcjet() or the arcjet_sync() client.
For Next.js, mark the package and its native dependencies as
server external packages,
so that Next.js loads them from node_modules at runtime instead of bundling
them. Run each route handler that uses the backend on the Node.js runtime, which
is the default, rather than the Edge runtime:
/** @type {import('next').NextConfig} */const nextConfig = { serverExternalPackages: [ "@arcjet/sensitive-info-rampart", "@huggingface/transformers", "onnxruntime-node", ],};
module.exports = nextConfig;export const runtime = "nodejs";Decision
Section titled “Decision”To evaluate your rules, call the protect function.
The function returns a Promise that resolves to an ArcjetDecision object
with the following properties:
id(string) – The unique ID for the request. Use it to look up the request in the Arcjet Console. Decisions that involve the Arcjet cloud API have the prefixreq_, and decisions made locally have the prefixlreq_.conclusion(ArcjetConclusion) – The overall conclusion from evaluating every configured rule. To follow Arcjet’s recommended action, use this property.reason(ArcjetReason) – More detail about the conclusion.results(ArcjetRuleResult[]) – The result of each rule that ran.ip(ArcjetIpDetails) – Arcjet’s analysis of the client IP address. For more information, see the SDK reference.
To check whether a sensitive information rule denied the request, use
decision.isDenied() and decision.reason.isSensitiveInfo() (JS) /
decision.is_denied() and decision.reason_v2.type == "SENSITIVE_INFO"
(Python) / decision.IsDenied() and decision.Reason.IsSensitiveInfo() (Go).
To see what each rule found, iterate through the results:
for (const result of decision.results) { console.log("Rule Result", result);}for result in decision.results: print("Rule Result", result)for _, result := range decision.Results { log.Printf("Rule Result %+v", result)}The following example logs each result and the sensitive information rule:
import { env } from "$env/dynamic/private";import arcjet, { sensitiveInfo } from "@arcjet/sveltekit";import { rampart } from "@arcjet/sensitive-info-rampart";import { error, json, type RequestEvent } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY!, rules: [ sensitiveInfo({ deny: ["EMAIL"], mode: "LIVE", backend: rampart(), }), ],});
export async function GET(event: RequestEvent) { const decision = await aj.protect(event, { sensitiveInfoValue: await event.request.text(), });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isSensitiveInfo()) { console.log("Sensitive info rule", result); } }
if (decision.isDenied()) { return error(403, "Forbidden"); }
return json({ message: "Hello world" });}import arcjet, { sensitiveInfo } from "@arcjet/sveltekit";import { rampart } from "@arcjet/sensitive-info-rampart";import { error, json } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY, rules: [ sensitiveInfo({ deny: ["EMAIL"], mode: "LIVE", backend: rampart(), }), ],});
export async function GET(event) { const decision = await aj.protect(event, { sensitiveInfoValue: await event.request.text(), });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isSensitiveInfo()) { console.log("Sensitive info rule", result); } }
if (decision.isDenied()) { return error(403, "Forbidden"); }
return json({ message: "Hello world" });}Entity types
Section titled “Entity types”For every entity type that Arcjet detects, grouped by category with its
taxonomy version and where Arcjet detects it, see
Sensitive information entity types.
The SDK detects the version 1 types with Rampart. List any of them in allow or
deny. Each one is an ArcjetSensitiveInfoType in JS, a
SensitiveInfoEntityType in Python, and an arcjet.EntityType in Go.
To list every type at once, use rampartEntities (JS), rampart_entities
(Python), or rampart.Entities() (Go).
Structured type matching
Section titled “Structured type matching”The default recognizers match these structured types with deterministic, validated patterns.
Card numbers
Section titled “Card numbers”Arcjet detects card numbers of 13 to 19 digits when the final digit is a valid
Luhn check digit. The values
4242424242424242, 4000 0566 5566 5556, and 3782-8224-6310005 match, but
4242424242424241 doesn’t. Spaces and dashes are ignored, because people often
group the digits.
Email addresses
Section titled “Email addresses”The values alice@example.com and bob.smith@subdomain.example.com match, but
alice.example.com doesn’t.
IP addresses
Section titled “IP addresses”Arcjet detects IPv4 and IPv6 addresses. The values 127.0.0.1 and ::1 match.
Social Security numbers
Section titled “Social Security numbers”Arcjet detects US Social Security numbers in the dashed form, such as
123-45-6789.
Arcjet detects URLs that start with http://, https://, or www.. The model
also detects URLs from context.
Phone numbers
Section titled “Phone numbers”The Rampart model detects phone numbers from context. The default recognizers don’t include a phone number pattern, because a run of digits can also be a bank account, routing number, or government identifier, and a pattern can’t tell which one it is.
Custom entity detection
Section titled “Custom entity detection”To detect patterns that the default recognizers don’t cover, add your own
recognizer to the Rampart recognizers option. A recognizer receives the full
text and returns the spans it matched, each with a start offset, an end offset,
and an entity type. Recognizer results take precedence over the model where
they overlap.
Keep the default recognizers in the list unless you want to replace them. The
following example adds a recognizer that reports IBANs as BANK_ACCOUNT:
import arcjet, { sensitiveInfo } from "@arcjet/sveltekit";import { defaultRecognizers, rampart } from "@arcjet/sensitive-info-rampart";import { error, json } from "@sveltejs/kit";
// A recognizer receives the full text and returns the spans it matched. This// one reports IBANs as bank account numbers.function detectIban(value) { const spans = []; for (const match of value.matchAll(/\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b/g)) { spans.push({ start: match.index, end: match.index + match[0].length, type: "BANK_ACCOUNT", }); } return spans;}
const aj = arcjet({ key: process.env.ARCJET_KEY, // Get your Arcjet key from https://console.arcjet.com rules: [ sensitiveInfo({ deny: ["EMAIL", "BANK_ACCOUNT"], mode: "LIVE", backend: rampart({ // Keep the default recognizers and add your own. recognizers: [...defaultRecognizers, detectIban], }), }), ],});
export async function GET(event) { const decision = await aj.protect(event, { sensitiveInfoValue: await event.request.text(), });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isSensitiveInfo()) { console.log("Sensitive info rule", result); } }
if (decision.isDenied()) { return error(403, "Forbidden"); }
return json({ message: "Hello world" });}import { env } from "$env/dynamic/private";import arcjet, { sensitiveInfo } from "@arcjet/sveltekit";import { defaultRecognizers, type DetectedSpan, rampart,} from "@arcjet/sensitive-info-rampart";import { error, json, type RequestEvent } from "@sveltejs/kit";
// A recognizer receives the full text and returns the spans it matched. This// one reports IBANs as bank account numbers.function detectIban(value: string): DetectedSpan[] { const spans: DetectedSpan[] = []; for (const match of value.matchAll(/\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b/g)) { spans.push({ start: match.index, end: match.index + match[0].length, type: "BANK_ACCOUNT", }); } return spans;}
const aj = arcjet({ key: env.ARCJET_KEY!, // Get your Arcjet key from https://console.arcjet.com rules: [ sensitiveInfo({ deny: ["EMAIL", "BANK_ACCOUNT"], mode: "LIVE", backend: rampart({ // Keep the default recognizers and add your own. recognizers: [...defaultRecognizers, detectIban], }), }), ],});
export async function GET(event: RequestEvent) { const decision = await aj.protect(event, { sensitiveInfoValue: await event.request.text(), });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isSensitiveInfo()) { console.log("Sensitive info rule", result); } }
if (decision.isDenied()) { return error(403, "Forbidden"); }
return json({ message: "Hello world" });}Offsets are UTF-16 code unit indexes in JS, code point indexes in Python, and byte offsets in Go – the indexes each language’s regular expression matches return.
Error handling
Section titled “Error handling”By default, Arcjet fails open, so a service issue or misconfiguration doesn’t block your users. The SDK times out and fails open after 2,000 ms. Most responses take 20 ms to 30 ms.
If a rule hits an error, Arcjet returns an ERROR result for that rule. The
message property on the result explains what went wrong. If every other rule
that ran returned ALLOW, the overall conclusion is ERROR.
Sensitive information detection protects data, so deny the request when the decision is errored, unless your application can accept data that Arcjet didn’t screen. The quick start examples fail closed this way. The following example logs the error and shows where to fail closed:
import { env } from "$env/dynamic/private";import arcjet, { sensitiveInfo } from "@arcjet/sveltekit";import { rampart } from "@arcjet/sensitive-info-rampart";import { error, json, type RequestEvent } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY!, rules: [ sensitiveInfo({ deny: ["EMAIL"], mode: "LIVE", backend: rampart(), }), ],});
export async function GET(event: RequestEvent) { const decision = await aj.protect(event, { sensitiveInfoValue: await event.request.text(), });
for (const { reason } of decision.results) { if (reason.isError()) { // Fail open by logging the error and continuing console.warn("Arcjet error", reason.message); // You could also fail closed here for very sensitive routes //return error(503, { message: "Service unavailable" }); } }
if (decision.isDenied()) { return error(403, { message: "You are suspicious!" }); }
return json({ message: "Hello world" });}import { env } from "$env/dynamic/private";import arcjet, { sensitiveInfo } from "@arcjet/sveltekit";import { rampart } from "@arcjet/sensitive-info-rampart";import { error, json } from "@sveltejs/kit";
const aj = arcjet({ key: env.ARCJET_KEY, rules: [ sensitiveInfo({ deny: ["EMAIL"], mode: "LIVE", backend: rampart(), }), ],});
export async function GET(event) { const decision = await aj.protect(event, { sensitiveInfoValue: await event.request.text(), });
for (const { reason } of decision.results) { if (reason.isError()) { // Fail open by logging the error and continuing console.warn("Arcjet error", reason.message); // You could also fail closed here for very sensitive routes //return error(503, { message: "Service unavailable" }); } }
if (decision.isDenied()) { return error(403, { message: "You are suspicious!" }); }
return json({ message: "Hello world" });}Testing
Section titled “Testing”Arcjet runs the same way in every environment, including locally and in CI. To
log the results of your rules without blocking any requests, set mode to
DRY_RUN.
You can test your rules automatically with a test framework, or with a sample of your traffic. For more information, see Testing.