Sensitive information reference for Remix
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 with the on-device Rampart NER model, which detects names, addresses, and government or financial identifiers as well as structured types such as email addresses and card numbers.
Configuration
Section titled “Configuration”Sensitive information detection is configured by allowing or denying a subset of
sensitive information. The allow and deny lists are mutually exclusive. With
allow, Arcjet denies any detected sensitive information that the list does not
name. With deny, Arcjet allows any detected sensitive information that the
list does not name.
The arcjet client can be configured with one or more Sensitive information
rules. Construct them with sensitiveInfo(...) in JavaScript / TypeScript,
detect_sensitive_info(...) in Python, or
arcjet.SensitiveInfo(arcjet.SensitiveInfoOptions{...}) in Go. The lists are
allow / deny in JS and Python, and Allow / Deny in Go.
Set backend to the Rampart backend:
rampart() in JS and Python, or the Backend 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# detection table below 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";On-device detection with Rampart
Section titled “On-device detection with Rampart”Sensitive information detection runs on-device with the Rampart named-entity-recognition (NER) model, paired with deterministic recognizers for structured types. JavaScript and Python run a ~15 MB quantized ONNX model. Go embeds the same ~15 MB of weights in a nested module and infers in pure Go, with no cgo or ONNX runtime. The rule detects names, addresses, and government or financial identifiers, as well as email addresses, card numbers, IP addresses, and phone numbers. The model weights are bundled, so nothing is fetched at runtime and scanned text stays in your environment. For exactly what Rampart detects, see Entity detection.
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 is 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 arcjet, { sensitiveInfo } from "@arcjet/remix";import { rampart } from "@arcjet/sensitive-info-rampart";import type { ActionFunctionArgs } from "@remix-run/node";
const aj = arcjet({ key: process.env.ARCJET_KEY!, // Get your site 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 action(args: ActionFunctionArgs) { const decision = await aj.protect(args, { sensitiveInfoValue: await args.request.text(), });
if (decision.isDenied()) { if (decision.reason.isSensitiveInfo()) { return Response.json( { error: "Please don't send personal data." }, { status: 400 }, ); } else { return Response.json({ error: "Forbidden" }, { status: 403 }); } }
// We don't need to use the decision elsewhere, but you could return it to // the component return null;}import arcjet, { sensitiveInfo } from "@arcjet/remix";import { rampart } from "@arcjet/sensitive-info-rampart";
const aj = arcjet({ key: process.env.ARCJET_KEY, // Get your site 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 action(args) { const decision = await aj.protect(args, { sensitiveInfoValue: await args.request.text(), });
if (decision.isDenied()) { if (decision.reason.isSensitiveInfo()) { return Response.json( { error: "Please don't send personal data." }, { status: 400 }, ); } else { return Response.json({ error: "Forbidden" }, { status: 403 }); } }
// We don't need to use the decision elsewhere, but you could return it to // the component return null;}The full set of types Rampart supports is exported as rampartEntities
(JS) / rampart_entities (Python) / rampart.Entities() (Go). Use it to deny
every type Rampart can detect:
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 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. On the Node.js CPU runtime, median (p50)
inference is ~6.6 ms, dropping to ~3.9 ms p50 on WebGPU (set
device: "webgpu" in the JS SDK). The Python backend runs on ONNX
Runtime and defaults to CPU execution; select a
different accelerator with the providers option (described later on this
page). The Go backend is a nested module that embeds the same ~15 MB
weights and runs inference in pure Go (no cgo). Create it once with
rampart.New and reuse it. The default MaxInputChars is 4096.
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 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.
- 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.
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, // 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",), # Maximum number of characters scanned per request (default: 100,000). # Inference runs synchronously on the request path, so input longer than # this is truncated before detection to bound worst-case CPU cost. 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, // Character ceiling scanned per call (default: 4096). Longer input is // truncated. Raise this only 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{},})In JavaScript and Python, the model loads once on first use and is reused for
every subsequent request. In Go, rampart.New loads and dequantizes the
weights – create the backend once at startup and reuse it. It is 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 bundled model weights from disk at runtime. As a result:
- 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. Weights are compiled into the nested module, so there is no ONNX runtime, native addon, or extra file for a bundler to miss. Applications that import the module include about 15 MB of weights in the binary.
In Python, the backend depends on
ONNX Runtime (onnxruntime), which is installed for
you by the arcjet[sensitive-info-rampart] extra. Model inference is
synchronous and blocking, so its latency is added directly to each request the
rule scans – the same is true whether you use the arcjet() or 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. Also make sure any route handler that uses the backend runs on the Node.js
runtime (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”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 withreq_for decisions involving the Arcjet cloud API. For decisions taken locally, the prefix islreq_.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 ofArcjetRuleResultobjects containing the results of each rule that was executed.ip(ArcjetIpDetails) – An object containing Arcjet’s analysis of the client IP address. For more information, see the SDK reference.
To check whether a sensitive info rule returned a deny conclusion, 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).
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);}for result in decision.results: print("Rule Result", result)for _, result := range decision.Results { log.Printf("Rule Result %+v", result)}This example logs the full result as well as the sensitive info rule:
import arcjet, { shield, sensitiveInfo } from "@arcjet/remix";import { rampart } from "@arcjet/sensitive-info-rampart";import type { ActionFunctionArgs } from "@remix-run/node";
const aj = arcjet({ key: process.env.ARCJET_KEY!, // Get your site key from https://console.arcjet.com rules: [ sensitiveInfo({ deny: ["EMAIL"], mode: "LIVE", backend: rampart(), }), shield({ mode: "LIVE", }), ],});
export async function action(args: ActionFunctionArgs) { const decision = await aj.protect(args, { sensitiveInfoValue: await args.request.text(), });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isSensitiveInfo()) { console.log("Sensitive info rule", result); }
if (result.reason.isShield()) { console.log("Shield protection rule", result); } }
if (decision.isDenied()) { if (decision.reason.isSensitiveInfo()) { return Response.json( { error: "Please don't send personal data." }, { status: 400 }, ); } else { return Response.json({ error: "Forbidden" }, { status: 403 }); } }
// We don't need to use the decision elsewhere, but you could return it to // the component return null;}import arcjet, { shield, sensitiveInfo } from "@arcjet/remix";import { rampart } from "@arcjet/sensitive-info-rampart";
const aj = arcjet({ key: process.env.ARCJET_KEY, // Get your site key from https://console.arcjet.com rules: [ sensitiveInfo({ deny: ["EMAIL"], mode: "LIVE", backend: rampart(), }), shield({ mode: "LIVE", }), ],});
export async function action(args) { const decision = await aj.protect(args, { sensitiveInfoValue: await args.request.text(), });
for (const result of decision.results) { console.log("Rule Result", result);
if (result.reason.isSensitiveInfo()) { console.log("Sensitive info rule", result); }
if (result.reason.isShield()) { console.log("Shield protection rule", result); } }
if (decision.isDenied()) { if (decision.reason.isSensitiveInfo()) { return Response.json( { error: "Please don't send personal data." }, { status: 400 }, ); } else { return Response.json({ error: "Forbidden" }, { status: 403 }); } }
// We don't need to use the decision elsewhere, but you could return it to // the component return null;}Entity detection
Section titled “Entity detection”Rampart detects the following entity types. 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. You can
also detect more patterns with
custom entity detection.
| Entity type | Detected by |
|---|---|
EMAIL | Model and recognizer |
PHONE_NUMBER | Model |
IP_ADDRESS | Recognizer |
CREDIT_CARD_NUMBER | Recognizer |
URL | Model and 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 |
| Custom | Your own recognizers |
Model means the on-device Rampart NER model detects the type. Recognizer means a deterministic, validated pattern detects it – for example, card numbers must pass the Luhn checksum. Where the model and a recognizer overlap on the same text, the recognizer wins.
Phone numbers are left to the model because their digit shape overlaps with financial and government identifiers, so the default recognizers don’t include a phone number pattern.
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”Card numbers of 13 to 19 digits are detected when the final digit is a valid
Luhn check digit. The values
4242424242424242, 4000 0566 5566 5556, and 3782-8224-6310005 match but
4242424242424241 does not. Spaces and dashes are ignored because users often
group digits.
Email addresses
Section titled “Email addresses”The values alice@example.com and bob.smith@subdomain.example.com match but
alice.example.com does not.
IP addresses
Section titled “IP addresses”IPv4 and IPv6 addresses are detected. The values 127.0.0.1 and ::1 match.
Social Security numbers
Section titled “Social Security numbers”US Social Security numbers in the dashed form, such as 123-45-6789, are
detected.
URLs that start with http://, https://, or www. are detected. 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/remix";import { defaultRecognizers, rampart } from "@arcjet/sensitive-info-rampart";
// 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 site 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 action(args) { const decision = await aj.protect(args, { sensitiveInfoValue: await args.request.text(), });
if (decision.isDenied()) { if (decision.reason.isSensitiveInfo()) { return Response.json( { error: "Please don't send personal data." }, { status: 400 }, ); } else { return Response.json({ error: "Forbidden" }, { status: 403 }); } }
// We don't need to use the decision elsewhere, but you could return it to // the component return null;}import arcjet, { sensitiveInfo } from "@arcjet/remix";import { defaultRecognizers, type DetectedSpan, rampart,} from "@arcjet/sensitive-info-rampart";import type { ActionFunctionArgs } from "@remix-run/node";
// 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: process.env.ARCJET_KEY!, // Get your site 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 action(args: ActionFunctionArgs) { const decision = await aj.protect(args, { sensitiveInfoValue: await args.request.text(), });
if (decision.isDenied()) { if (decision.reason.isSensitiveInfo()) { return Response.json( { error: "Please don't send personal data." }, { status: 400 }, ); } else { return Response.json({ error: "Forbidden" }, { status: 403 }); } }
// We don't need to use the decision elsewhere, but you could return it to // the component return null;}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”Arcjet is designed to fail open so that a service issue or misconfiguration does not block all requests. The SDK also times out and fails open after 2000 ms by default. However, in most cases, the response time is less than 20 ms to 30 ms.
If there is an error condition when processing the rule, Arcjet returns 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 is ERROR.
import arcjet, { sensitiveInfo } from "@arcjet/remix";import { rampart } from "@arcjet/sensitive-info-rampart";import type { ActionFunctionArgs } from "@remix-run/node";
const aj = arcjet({ key: process.env.ARCJET_KEY!, // Get your site key from https://console.arcjet.com rules: [ sensitiveInfo({ deny: ["EMAIL"], mode: "LIVE", backend: rampart(), }), ],});
export async function action(args: ActionFunctionArgs) { const decision = await aj.protect(args, { sensitiveInfoValue: await args.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 // throw new Response("Service unavailable", { status: 503, statusText: "Service unavailable" }); } }
if (decision.isDenied()) { if (decision.reason.isSensitiveInfo()) { return Response.json( { error: "Please don't send personal data." }, { status: 400 }, ); } else { return Response.json({ error: "Forbidden" }, { status: 403 }); } }
// We don't need to use the decision elsewhere, but you could return it to // the component return null;}import arcjet, { sensitiveInfo } from "@arcjet/remix";import { rampart } from "@arcjet/sensitive-info-rampart";
const aj = arcjet({ key: process.env.ARCJET_KEY, // Get your site key from https://console.arcjet.com rules: [ sensitiveInfo({ deny: ["EMAIL"], mode: "LIVE", backend: rampart(), }), ],});
export async function action(args) { const decision = await aj.protect(args, { sensitiveInfoValue: await args.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 // throw new Response("Service unavailable", { status: 503, statusText: "Service unavailable" }); } }
if (decision.isDenied()) { if (decision.reason.isSensitiveInfo()) { return Response.json( { error: "Please don't send personal data." }, { status: 400 }, ); } else { return Response.json({ error: "Forbidden" }, { status: 403 }); } }
// We don't need to use the decision elsewhere, but you could return it to // the component return null;}Testing
Section titled “Testing”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.
For details, see the Testing section of the docs.