What is Arcjet?
Arcjet is the AI agent runtime security platform. It discovers the agents running in your organization, enforces policy across every action, prompt, and tool call, and keeps the evidence to prove what happened: detecting prompt injection, authorizing agent tool calls, redacting PII, and blocking bots and abuse. For agents and applications that you build, Arcjet is an SDK for JavaScript and TypeScript, Python, and Go that you call from your own code, with no proxy and no DNS change. For Claude Code, GitHub Copilot, Cursor, OpenAI Codex, and Muse Code, Arcjet enforces policy through the hooks each agent already fires, with no SDK at all. This page covers every Arcjet control, where each check runs, and its documented limits, then compares the cost of building the same controls in-house. Building wins when your policy needs your own domain model, when you already run a platform team, at extreme scale, or when the data can't leave your environment. For table-stakes controls without those conditions, buying usually costs less.
Arcjet publishes this page. Feature details come from the Arcjet documentation, and external figures come from the linked primary sources, reviewed on September 25, 2026.
Arcjet's three verbs – discover, enforce, and prove – answer different questions:
- Discover: which agents are running, who runs them, and what do they do? Arcjet ingests activity with no code change, through OpenTelemetry or the Claude Compliance API.
- Enforce: is this specific action allowed to run? A decision returns before the side effect executes.
- Prove: what actually happened? Every decision keeps the policy that ran and the values it read, and on the Enterprise plan you can export decisions to Datadog, Splunk, SentinelOne, Panther, or Amazon S3.
For the enforcement half, you import the Arcjet SDK, declare rules, and call a function at the point where a decision matters, which is also the point where your application knows the most. Your application knows who the user is, what the agent is about to do, which record is involved, and what it costs if that goes wrong.
The Arcjet call comes in two shapes, and the difference runs through everything else on this page:
protect()takes an HTTP request. Use it in route handlers and API endpoints, in any supported framework.guard()takes inputs directly, with noRequestobject. Use it inside tool handlers, MCP servers, queue consumers, and agent pipelines, where the consequential action has no HTTP front door.
A third function, capture(), records that an allowed action happened and doesn't change a
decision. The capture() function is the evidence primitive: a record of what an agent did, next to
the decisions that permitted it.
There is one enforcement surface with no SDK at all. For coding agents – Claude Code, GitHub Copilot, Cursor, OpenAI Codex, and Muse Code – Arcjet is called from the hook the agent already fires, so a security team can govern what those agents do on developer laptops without touching anyone's code. Policy is published centrally rather than declared in a repository, which matters because the developer running the agent isn't the person who sets the rule. The hook entry is installed through each vendor's managed settings or organization lock, so a developer can't remove it without admin access. GitHub Copilot is covered in the CLI and the cloud agent; VS Code agent mode hooks don't support the HTTP hook type. One policy applies across all five agents, and a policy decides before the action at one of three moments: Tool call, Prompt, or Model switch. Starter policies cover destructive commands, history rewrites, protected paths, credential access, piped installers, MCP server and egress allowlists, destination threat, model allowlists, sensitive information, and prompt injection. Evaluation runs at the edge in more than 300 data centers, and the hook adds one request of latency, typically a few tens of milliseconds.
Claude Code and Copilot fail open on a slow or failing HTTP hook, by their own design, so that surface's enforcement strength equals Arcjet's availability and latency. Cursor and Codex run through a command wrapper that fails closed. Hooks also can't withhold a tool result from the model once the tool has run, and personal accounts don't load an organization's hooks, so block those at the network or device. For more information about each vendor's behavior, see how to secure AI coding agents and the coding agent policies documentation.
Where Arcjet is going
Arcjet's direction explains why the API is shaped the way it is. Arcjet is moving from point checks to workflow-level runtime policy: connecting protect() and guard()
decisions, together with audit events you supply, into one security trace, then enforcing before the
next risky production action executes. The thesis is an application-native security enforcement
layer for AI workflows, connecting enterprise identity to centrally managed policy, evaluating
context before consequential actions, and producing evidence.
The two speeds of AI agent runtime security sets out the reasoning, and it's explicit about the split. It borrows Google's terms from Beyond Zero. The floor is consistent baseline policy and enforcement, applied the same way across teams. The ceiling is where a decision draws on identity, resource sensitivity, recent behavior, and the sequence of actions that led to a request: "Google is describing the ceiling. Arcjet is making the floor deployable while building up from the foundations to reach the ceiling."
Everything documented on this page is the floor, and all of it is available. Read the trace-level policy as direction rather than as a feature you can configure. It matters for a build-versus-buy decision because the enforcement points are the durable part of the investment, and because the argument for putting them in your application doesn't change as the policy above them gets richer: "the reasoning only matters if there is somewhere to enforce its decision."
Not every rule is available on both surfaces, and the split isn't arbitrary. Rate limiting, prompt
injection detection, and sensitive information detection work on both. Rules that need the HTTP
request itself – bot detection, Shield, email validation, filters, and IP analysis – are
protect() only. Content moderation and custom rules are guard() only. The
agent get started guide publishes the full matrix.
The following sections cover every feature, then ask which of these controls you'd be better off building yourself.
Where does each check run?
Each Arcjet check runs in one of two places: locally in the SDK, through a WebAssembly module in your process, or in the Arcjet Cloud API. Where a check runs determines your latency, your privacy posture, and which compliance questions you have to answer.
Arcjet has two components: an SDK in your application, which includes the WebAssembly module for
local analysis, and the Cloud API. The architecture documentation
is specific about when the network call happens.
A Cloud API call is required when you configure Shield, rate limiting, bot protection where local
header analysis is inconclusive, email validation of a syntactically valid address, or a filter
that reads ip.src.* fields backed by the IP reputation database.
Two properties matter for capacity planning. Arcjet makes a single API call per request regardless of how many rules you configure, and the SDK caches deny decisions locally for a TTL, so a blocked client doesn't cost a round trip on every subsequent request.
The following table lists the published latency figures:
| Path | Documented overhead |
|---|---|
| Local analysis or a cached decision | "less than 1ms" |
| Cloud API call | "typically no more than 20-30ms, often significantly less" |
| Prompt injection detection | "approximately 100 ms" |
| Local sensitive information inference | "~6.6 ms" median, "~3.9 ms" on WebGPU |
The Cloud API runs in "more than 300 global locations" and routes to the closest region, per the regions documentation. The default request timeout is 2000 ms in both development and production.
We've written up how that budget is actually spent in how we achieve our 25 ms p95 response time SLA, including why decisions are cached per rule type and why the transport uses HTTP/2 multiplexing. If you want the full component inventory behind the service, Arcjet's tech stack names about thirty production systems, which is itself a useful input to the build-versus-buy question.
Arcjet fails open by default. The architecture page states the reasoning plainly: a service
issue or a misconfiguration shouldn't block all of your traffic. You can configure it to fail
closed instead, and for guard() the framework wrappers already default the other way. Decide
this per action rather than globally, because a product search page and a payment endpoint don't
warrant the same answer.
Arcjet also runs post-request analysis on the platform, whether you configure it or not. Once a client crosses a dynamic threshold of suspicious activity, the client gets a deny decision for a period, with no configuration needed.
For a candid account of what stays in your process and what doesn't, see keeping security inspection local and the privacy documentation, which lists exactly which request data is processed remotely.
Shield WAF
Shield is Arcjet's web application firewall. It watches request metadata over time and blocks clients whose behavior crosses a threshold, rather than trying to adjudicate each request in isolation. It carries rules from the OWASP Core Rule Set covering SQL injection, cross-site scripting, local file inclusion, remote file inclusion, and PHP and Java code injection.
Shield takes one option:
import arcjet, { shield } from "@arcjet/next";
const aj = arcjet({ key: process.env.ARCJET_KEY!, rules: [shield({ mode: "LIVE" })],});Check decision.reason.isShield() when a request is denied. Shield is available as a remote rule,
so a security team can enable it from the dashboard or the MCP server without a deploy.
Shield has one documented limit that matters here. Shield analysis uses request headers and query parameters, not the request body, and it happens on the Arcjet platform after the request is reported. Our limitations page records it as one of two entries. If you want body inspection, that's sensitive information detection, which works differently and stays local.
Rate limiting
Arcjet rate limiting ships three algorithms, documented with their trade-offs in the algorithms guide: fixed window, sliding window, and token bucket. For the mechanics of each and when to choose which, see our rate limiting guide.
import arcjet, { fixedWindow, slidingWindow, tokenBucket } from "@arcjet/next";
const aj = arcjet({ key: process.env.ARCJET_KEY!, characteristics: ["userId"], rules: [ fixedWindow({ mode: "LIVE", window: "1h", max: 100 }), slidingWindow({ mode: "LIVE", interval: 60, max: 20 }), tokenBucket({ mode: "LIVE", refillRate: 10, interval: 60, capacity: 100 }), ],});Token bucket consumes a variable amount per call, which is what makes it the right primitive for
cost-weighted work: await aj.protect(req, { userId, requested: 50 }).
Two details decide whether your limits behave the way you intended. First, characteristics
combine into one fingerprint. ["ip.src", "userId"] gives you one bucket per unique IP and user
pair, not separate IP and user counters. If you want independent ceilings, write independent
rules. Second, don't key on anything the caller controls, or the limit isn't a limit. The
fingerprints documentation also warns against using
personal information as a characteristic, even though values are hashed.
State lives in the Cloud API, so limits hold across every instance of your application without you running Redis. Fixed and sliding window are available as remote rules. Token bucket isn't, because the SDK has to declare how many tokens each request spends.
The @arcjet/decorate package adds draft-standard RateLimit headers to your responses from
a decision.
Bot protection
Bot protection classifies automated clients by name and
by category, and you write policy against either. We document detection of more
than 600 bots across categories such as CATEGORY:AI, CATEGORY:SEARCH_ENGINE, and
CATEGORY:MONITOR.
detectBot({ mode: "LIVE", allow: ["CATEGORY:SEARCH_ENGINE", "CATEGORY:MONITOR"],});The allow and deny semantics are easy to get backwards. With allow, anything detected and not on the list is denied. With deny, anything detected and
not on the list is allowed. A bot that's detected but unidentifiable becomes UNKNOWN_BOT, and
if you use an allow list without including it, those requests are blocked. That's the default,
and it's usually what you want.
For allow rules, Arcjet verifies authenticity using IP data and reverse DNS, so a client claiming
to be Googlebot has to actually be Googlebot. Deny rules skip verification, since there's nothing
to gain by verifying a client you're blocking anyway. The @arcjet/inspect package exposes
isSpoofedBot, isVerifiedBot, and isMissingUserAgent.
The detection data is open source. arcjet/well-known-bots is MIT licensed and carries 793 entries, each with an ID, categories, a match pattern, and a verification field. You can read exactly how a given bot is identified, which is what makes it checkable if you're evaluating. The dataset is a hard fork of crawler-user-agents, and the upstream copyright is preserved in the license.
The dataset also records a limit: the verification field is present on all 793 entries but populated on only 85 of them. The rest can't be confirmed by reverse DNS or published IP range, because their operators don't publish one. That's a limit of the problem, not of the dataset.
Publishing the dataset lets you check its usefulness independently of Arcjet. Fastly's log analytics tool reads the same JSON file as its bot feed, and a 2026 UCLouvain paper on detecting robots from server logs cites it as one of the canonical crowdsourced bot-identification lists.
For how the matching itself is implemented, making Arcjet's Wasm bot detector smaller and
faster documents
replacing a single large RegexSet with an Aho-Corasick automaton plus a small residual regex set,
taking the component from about 944 KB gzipped to about 689 KB and detection from roughly
0.9 ms to 0.4 ms. The post also explains why a fresh instance is created per request rather than
reused, which is a memory-isolation decision rather than a performance one.
Advanced bot signals add a browser-side layer, documented at
advanced signals. A WebAssembly module
in the page collects environment signals, exchanges them for a continue token stored in the
aj_signals cookie, and your server-side detectBot rule reads that cookie on the next request.
A missing cookie is itself a signal, and you can enforce its presence with a filter. Billing keys
off the detectBot call, not the script load. The page lists the Content-Security-Policy entries
you'll need.
Arcjet's own documentation states the limit of any bot detector: "no bot detection system can be 100% accurate." For the AI-agent-specific version of this problem, see AI agent bot management.
Email validation
Email validation runs in two stages, and the split matters for privacy. Syntax validation happens locally in the SDK. Only a syntactically valid address goes to the Cloud API, which checks MX records, whether the domain is disposable or free, and whether a Gravatar exists.
validateEmail({ mode: "LIVE", deny: ["DISPOSABLE", "INVALID", "NO_MX_RECORDS"],});Then pass the address at call time: await aj.protect(req, { email }).
Two options change the strictness. requireTopLevelDomain defaults to true, so foo@bar is
rejected; set it to false and it isn't. allowDomainLiteral defaults to false, so
foo@[123.456.789.0] is rejected.
The local syntax check is implemented on the open source email_address crate, which Arcjet
forks publicly, and our reference page lists the ten
RFCs it accounts for. RFC coverage is a question that most teams underestimate, and the
build section returns to it.
Email validation isn't available as a remote rule, because the address comes from the request body.
Sensitive information detection
Sensitive information detection inspects request bodies for personally identifiable information (PII) in your process, and it's the Arcjet feature where the local-versus-cloud split is most visible. Our docs commit to where the SDK-local path runs: "All of this logic runs locally and in-process. The raw request body is never sent to Arcjet." Arcjet receives the decision, not the content. Prefer that path wherever an SDK is in the call. Guard policies also offer a server-side sensitive-information detector for no-SDK paths such as coding-agent hooks.
sensitiveInfo({ mode: "LIVE", deny: ["CREDIT_CARD_NUMBER", "EMAIL", "PHONE_NUMBER"],});The built-in engine covers four structured types: email addresses, phone numbers, IP addresses,
and credit card numbers. The matching rules are documented rather than left to inference. Card
numbers must pass the Luhn check, so 4242424242424242 matches and 4242424242424241 doesn't.
IP addresses must parse as an IpAddr. Short numbers like 911 aren't treated as phone numbers.
For names, addresses, and government or financial identifiers you add an optional on-device model:
import { rampart, rampartEntities } from "@arcjet/sensitive-info-rampart";
sensitiveInfo({ mode: "LIVE", deny: rampartEntities, backend: rampart() });Our reference page gives the model's specifications, which is the level of detail you need to make a deployment decision: roughly 14.7 MB and 18.5M parameters quantized to 4-bit weights, a 512-token context window, and about 6.6 ms median inference. The model recalls around 98% of private terms across the seven Latin-script languages it supports.
The reference page also publishes the model's limits. Non-Latin scripts have much lower recall. Identifiers without a checksum are recognized less reliably than the structured types. Inference is synchronous, so its latency lands on every request the rule scans. The model needs a server runtime with filesystem and native-addon access, so it won't run on edge runtimes, and it must be excluded from server bundling. The model is CC BY 4.0 and the package is Apache-2.0.
You can also extend detection with a detect callback for custom patterns, or add recognizers when
using the model backend.
Running PII detection locally with the Rampart NER model covers the parts of named entity recognition (NER) that aren't the model: the tokenizer returns no original character offsets, so the implementation maintains a per-character map back to the source string, and long inputs are scanned as overlapping windows against the model's 512-token limit with longest-span-wins resolution. The model itself is a 6-layer BERT token classifier with 35 labels, and it ships in the public repository under CC BY 4.0 with attribution to National Design Studio.
Separately, @arcjet/redact redacts locally and gives
you a reversible handle:
const [redacted, unredact] = await redact(text, { entities: ["email"] });The redact function replaces matches with numbered placeholders such as <Redacted email #0>, and unredact() restores them, which is
the pattern you want when you need a model to see structure but not values. For the wider treatment,
see how to detect and redact PII in LLM inputs and outputs.
Prompt injection and content moderation
Prompt injection detection evaluates a message with a specialist model before it reaches your provider. Unlike sensitive information detection, this one is a cloud call. Our docs say so directly: "The prompt text is sent to the Arcjet Cloud API for evaluation," and it adds approximately 100 ms. Be precise about that distinction when you answer a privacy review.
const aj = arcjet({ key: process.env.ARCJET_KEY!, rules: [detectPromptInjection({ mode: "LIVE" })],});
const decision = await aj.protect(req, { detectPromptInjectionMessage: message,});Arcjet's docs give two pieces of operational advice for prompt injection detection. Keep the denial
response generic, because a detailed rejection teaches an attacker what to change. Also run in
DRY_RUN first to measure your
false-positive rate against real traffic before you enforce.
Injection doesn't only arrive in the user's message. Tool results re-enter the context too, which
is why the same rule is available on guard(). For that argument in full, see
the lethal trifecta and
prompt injection protection for LangChain, LlamaIndex, and Vercel AI SDK apps.
Content moderation detects harmful content in
untrusted text. Content moderation is a Guard rule with no protect() equivalent, exposed as moderateContent() in
JavaScript, ModerateContent() in Python, and GuardModerateContent in Go. The result is a binary
verdict rather than per-category scores.
Filters and signup protection
Filters give you a Wireshark-style expression language over request fields, evaluated by a Rust engine compiled to WebAssembly. The engine is a public fork of Cloudflare's wirefilter.
filter({ mode: "LIVE", deny: ['ip.src.country == "CN" and http.request.uri.path matches "^/admin"'],});Fields cover hosts, methods, paths, headers, cookies, query arguments, and a large set of
ip.src.* attributes including vpn, tor, proxy, hosting, asnum, and country. The
ip.src.* fields need the IP reputation database, so they cost a Cloud API call; everything else
evaluates locally. You get up to 10 expressions of up to 1024 bytes each.
Matching is literal and case-sensitive, which rules out a whole class of use. As our reference page puts it, "hand-written filters are not an effective way to block injection-style attacks like SQL injection (SQLi) or cross-site scripting (XSS)." That's what Shield is for.
Signup form protection bundles bot detection, email validation, and a sliding window into one rule, with a documented recommended configuration:
protectSignup({ email: { mode: "LIVE", deny: ["DISPOSABLE", "INVALID", "NO_MX_RECORDS"] }, bots: { mode: "LIVE", allow: [] }, rateLimit: { mode: "LIVE", interval: "10m", max: 5 },});In Python, protect_signup takes the same options and returns the three rules as a tuple that you
unpack into rules.
Agent guards
Agent guards are the part of Arcjet that isn't about HTTP. They put a decision immediately before a side effect, in code, where you know the authenticated user, the tool, and the arguments.
import { launchArcjet, detectPromptInjection, tokenBucket,} from "@arcjet/guard";
const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });
const budget = tokenBucket({ bucket: "agent-tokens", refillRate: 2_000, intervalSeconds: 3_600, maxTokens: 5_000,});
const decision = await arcjet.guard({ label: "invoice.refund", actor: session.userId, correlationId: runId, rules: [ budget({ key: session.userId, requested: 1 }), detectPromptInjection()(message), ],});
if (decision.conclusion === "DENY" || decision.hasFailedOpen()) { throw new Error("Refund blocked");}The Guard rate-limit options are named differently from the request SDK equivalents:
intervalSeconds and maxTokens rather than interval and capacity.
Four details of the Guard API affect how you use it.
First, label is the policy selector. The label names the action, and a security team writes remote policy
against that name. Labels are validated as slugs: lowercase letters, digits, dashes, dots, and
underscores, starting and ending with a lowercase letter or digit, up to 256 bytes.
Second, actor must be server-side. If an agent can set its own actor, it can select its own policy.
Third, failure behaves differently by path. A direct guard() call fails open, returning ALLOW with
an error result rather than treating an incomplete check as a denial. Check hasFailedOpen(). The
framework wrappers default the other way and won't run the tool, unless you opt in with
onGuardError: "allow".
Fourth, capture() isn't a decision. The capture() function records that something happened, and its delivery is
best-effort by design: a bounded in-memory queue, batches sent on size or a short delay, no retry
on a failed batch, and the newest event dropped when the queue is full. Treat it as telemetry, not
as an audit log of record.
Remote policies let a security team change what a
labelled action permits without touching agent code. You pass typed inputs, and the exposure is
explicit in the type you choose: policyInput.server.string(value) sends the value, while
policyInput.local.string(value) keeps it local and sends a domain-separated SHA-256 digest plus a
rule attestation. We document that digest as correlation data rather than anonymization, since
low-entropy values can be guessed and hashed. A policy declares typed inputs, detectors, and rules.
Expression rules are authored in the visual builder or Rego; detector rules can run prompt
injection detection, sensitive-information detection, or destination threat analysis. Sensitive
information has two kinds: SDK-local (the raw value never leaves the process) and server-side
(needed when there is no SDK, such as a coding-agent hook).
Rate limits stay in application code or on HTTP remote rules, because Guard policies don't author
them. Arcjet selects coding-agent policies by moment (tool call, prompt, or model switch), not by
label.
Destination threat analysis applies the threat intelligence that scores the caller of an HTTP
request to the hosts an agent is about to contact, such as a fetched URL or an absolute URL inside a shell command. The detector reads the
hosts from a server-side input, returns a risk level from none to critical, and fails closed
when a call names more than eight public destinations, so a malicious host can't hide past a
cutoff. On coding agents, the coding-agent.destination-threat starter policy applies it to every
tool call, including a curl in Bash. For more information, see
how to detect malicious URLs in AI agent tool calls and
rogue MCP server detection.
defineCustomRule lets you write your own rule with a local evaluate function, which is Guard
only.
First-party framework integrations cover Claude Agent SDK, Claude Managed Agents, Cloudflare Think,
CrewAI, Genkit, Google ADK, LangChain, LangGraph, Mastra, OpenAI Agents, Strands Agents, TanStack
AI, Vercel AI SDK, and Vercel Eve, plus Microsoft Agent Framework in Go. Which of those ship
JavaScript, Python, or both is in the
framework integrations matrix. Import paths
are versioned against the upstream framework, such as @arcjet/guard/vercel-ai/v7 and
@arcjet/guard/mastra/v1. Wrappers take action, not label; direct guard() calls use label
for the same slug. We've written up the per-framework enforcement points in
agent framework security,
how to secure a Mastra agent,
Claude Agent SDK security, and
Eve agent security is three jobs.
Introducing Arcjet Guards sets out the placement argument: a tool handler receives untrusted input as a function argument rather than a request body, so there is no HTTP boundary for a proxy or WAF to hook.
A related pattern worth copying whether or not you use Arcjet is in how we defend MCP tool outputs from prompt injection. Tool output is model input, so any MCP tool that interpolates attacker-controlled text into a guidance field becomes a delivery mechanism. The fix is field-level trust separation, schema descriptions that label trust explicitly, and adversarial regression tests asserting hostile strings never reach the trusted fields.
For testing, @arcjet/guard/testing provides registerTestClient(). The test client records calls
and returns a fail-open ALLOW, because no rule ran. It doesn't stub per-rule verdicts,
so helpers that fail closed will deny against it.
SDKs, languages, and the management plane
Arcjet ships SDKs for JavaScript and TypeScript, Python, and Go, plus three management surfaces: a CLI, an MCP server, and a plugin for Claude Code and Cursor. JavaScript and TypeScript are the most mature SDK surface. The JS SDK is Apache-2.0 and reached 1.0 in January 2026, with framework packages for Next.js, Node.js, Bun, Deno, Fastify, NestJS, Nuxt, Remix, React Router, SvelteKit, and Astro. Express and Hono apps use the Node.js or Bun package.
Python 1.2.0 covers FastAPI, Flask, and Django, with separate
async and sync clients and a documented Guard surface for agent frameworks.
Go 1.0.0 covers net/http plus direct Guard and capture
(Go 1.25 or later).
Policy doesn't have to live only in code. Remote rules are stored on the platform and evaluated alongside your SDK rules, covering rate limits, bot protection, filters, and Shield, applied site-wide, taking effect without a deploy. There's no precedence between the two sets: if either denies in LIVE mode, the request is denied. Rules needing request body content stay in the SDK. Guard policies are a separate system from those HTTP remote rules. For the trade-offs, see application-native versus remote security policies.
Arcjet's three management surfaces are designed for agents as well as people. The
CLI treats its commands as a stable API contract, defaults to JSON
when stdout isn't a TTY, and requires --confirm for mutations, including remote HTTP rules. The
MCP server at https://api.arcjet.com/mcp exposes that
surface over OAuth and is also where Guard policies are authored, validated, and published. The
Arcjet plugin bundles MCP access, coding rules, skills, and
a security analyst agent for Claude Code and Cursor, and the
skills repository is public.
The Arcjet Console records every decision and groups an agent's decisions into sessions, so you can review what ran and why. On the Enterprise plan, SIEM export sends those decisions to Datadog, Splunk, SentinelOne, Panther, or Amazon S3, which is where a security operations team alerts on denied agent actions and keeps evidence past the Console's retention window. For visibility into agents that don't call Arcjet yet, the Console also ingests OpenTelemetry and the Claude Compliance API with no code change.
Alongside the main SDK sit independent utilities: @arcjet/redact, @arcjet/inspect,
@arcjet/ip, @arcjet/decorate, and Nosecone for
security headers.
Arcjet is partly open source. The SDKs and Nosecone are Apache-2.0, the bot dataset and the filter engine fork are MIT, and the docs are CC BY 4.0. The compiled analysis WebAssembly modules are committed to the public repository, so you can inspect the artifact, but their Rust sources aren't public. The Cloud API, the MCP server, and the CLI are closed, and we set out where that line sits and why in how Arcjet approaches open source. One piece of the toolchain is public and reusable on its own: gravity generates wazero host bindings for WebAssembly Components, though its README describes it as an early release.
On platform assurance, Arcjet completed a SOC 2 Type 2 examination for the Arcjet Platform as of February 2026, covering Security, Availability, and Confidentiality with an unqualified opinion. The report is available through the Trust Center, and the security page records a 10-working-day patch target for vulnerability reports.
Is it worth building this yourself?
Every control on this page is buildable, and teams tend to underestimate the maintained version by an order of magnitude. Rate limiting is a counter, bot detection is pattern matching, and PII detection is regular expressions, but each of those descriptions is true of the first working version only.
The build-versus-buy decision comes down to three narrower questions:
- Is this control a source of competitive advantage for us, or is it table stakes that every application needs?
- What does the maintained version cost, not the first working version?
- What happens the first time it's wrong, in either direction?
That third question is the one that separates security infrastructure from most internal tooling. A rate limiter that's too loose costs money. A rate limiter that's too tight blocks paying customers. Both failures are silent until someone notices.
The following sections cover what building each control involves, using published evidence rather than assertion. The pattern is consistent: a naive implementation takes a weekend, a correct one takes a team, and the vendors who publish their engineering are the ones documenting how hard it was.
This page cites Arcjet's own engineering writing where it's relevant. Read it with the same skepticism you'd apply to any vendor: the Arcjet blog is one team's writing, mostly by one author, and it isn't independent corroboration. It is, at least, specific enough to check.
What each control costs to build
Each control costs more to maintain than its first version suggests. The following sections cost six controls from published evidence: distributed rate limiting, bot detection, PII detection, email validation, prompt injection detection, and a WAF rule set.
Distributed rate limiting
A correct distributed rate limiter needs atomic counters, a store that doesn't lose writes, and
bounded latency, and published engineering accounts show that each one is hard. Cloudflare, for
example, doesn't count exactly. Cloudflare's published
account of building rate limiting at scale
describes an approximation, and they measured its error at "0.003% of requests have been wrongly
allowed or rate limited" with "An average difference of 6% between real rate and the approximate
rate". They chose it because the storage primitives available to them were GET, SET, and INCR,
and a leaky bucket "requires multiple distinct operations that we cannot do atomically". That post
is from 2017, so read it as history rather than current behavior, but the constraint it describes is
the one you'll meet.
Then read Redis's own INCR documentation, which
walks through the obvious counter implementation and then says, in bold: "In the above code there is
a race condition." Redis's fix is a Lua script evaluated with EVAL. Most in-house rate limiters skip
that step.
The store adds its own failure mode. Redis Cluster's documentation states that it "does not guarantee strong consistency" and that "it is possible that Redis Cluster will lose writes that were acknowledged by the system to the client". Sentinel's documentation is equally direct: "there is always a window for losing acknowledged writes". Your quota counter inherits that.
The store also adds latency. Redis's own latency guide puts network round-trip on a 1 Gbit/s network at about 200 microseconds, and intrinsic latency at 115 microseconds on bare metal but 9.7 ms on a measured virtual machine, with up to 40 ms observed "in systems otherwise apparently running normally". The same page names mass key expiry as a latency source, which is exactly the access pattern a fixed-window limiter creates when every counter in a window expires at once.
GitHub built one properly and published the bugs. Two were pure distributed-systems faults: a reset timestamp computed by reading a TTL in Redis and adding it to the clock in Ruby, and a read that hit a replica while the write hit a primary, which rejected requests while the response headers advertised 5,000 remaining. GitHub's June 2026 availability report says that about 97% of API rate limiting is now handled at their gateway, so rate-limiting decisions "no longer contend with request-serving workers inside the monolith".
If you'd rather buy a gateway than build, Kong documents the trilemma rather than hiding it. Local counters are "Less accurate", and the counter "diverges when scaling the number of nodes". The cluster policy forces "a read and a write on the data store" per request. The Redis policy needs Redis. And when Redis is unreachable, Kong falls back to local counters, so "users will be able to perform more requests than the limit" – your limit silently multiplies by your node count. Envoy's reference rate limit service carries a similar caveat in memcache mode: "it's technically possible for a client to exceed quota briefly".
Finally, the standard itself declines to promise what most client implementations assume. The
IETF RateLimit headers draft
says "Clients MUST NOT consider the available quota parameter as a service level agreement", and
warns servers that "many throttled clients may come back at the very moment specified" – the spec
telling you that your 429 responses create the next thundering herd. Which is why you also need jittered
backoff, as AWS documented.
None of this work is exotic, and all of it is work you maintain for as long as you run the limiter.
Bot detection
Bot detection decays in a way the other controls don't, because an adversary updates faster than your rules do. Every cheap signal has a documented one-line defeat, and every expensive signal depends on something you don't control.
Cheap signals are easy to defeat. Setting the user agent is a supported API parameter in both
Playwright
and Puppeteer, not a hack. The
navigator.webdriver flag that the WebDriver specification defines falls to a single Chrome
command-line switch, and the
stealth plugin evasion that does it is a few dozen
lines. That plugin ships 17 evasions and still draws roughly 949,000 downloads a week despite its
last release being in 2023. curl_cffi, which impersonates browser TLS and HTTP/2 fingerprints,
runs above 41 million downloads a month.
Expensive signals decay on someone else's release schedule. TLS fingerprinting was the strongest cheap discriminator until Chrome began permuting its ClientHello extension order, announced in November 2022. Google's stated motive was anti-ossification rather than anti-detection, but the effect was immediate: Fastly watched the canonical Chrome JA3 fingerprint fall off their network within days, noting that with roughly 15 factorial possible orderings, each connection now has a practically unique JA3. Salesforce's original JA3 repository is archived, and its README says the project "is no longer being actively maintained by Salesforce".
The successor has a licensing catch that matters if you're building a product. JA4 itself is BSD-3-Clause, but the wider JA4+ suite is under the FoxIO License 1.1 and is patent pending, granting rights "only for non-commercial purposes" and stating that "Providing the software on a hosted or managed service basis to others is not a non-commercial purpose". Internal use is fine. Shipping it in something you sell needs a separate license.
The allowlist is the part that teams most underestimate. Verifying AI crawlers against operator-published IP ranges means polling nine JSON files from four operators, together carrying around 1,900 CIDR prefixes. None of those operators commits to an update cadence, and when measured, their staleness ranged from one day to over eighteen months. Only Google publishes IPv6 ranges at all, so on an IPv6-reachable site you can't verify the others by IP. Anthropic's published list carries no per-bot attribution, so it can't tell you whether a verified Anthropic address is ClaudeBot, Claude-User, or Claude-SearchBot – which is exactly the distinction a training opt-out depends on. Google-Extended has no user agent and no IP range of its own, so it's only expressible in robots.txt. A self-built detector can't implement that control at all.
There's a quieter failure mode here too. When Google moved its IP range files to new paths, the deprecated URL kept returning HTTP 200 with stale, incomplete data rather than a redirect or an error. Anyone who hardcoded the old path is still verifying successfully against a list that stopped being updated months ago, with nothing in their logs to say so. That's the kind of bug you find during an incident.
The name list also churns. The community ai.robots.txt dataset that many implementations
depend on went from 36 entries to 165 in two years, across commits in 23 of 25 months, with entries
removed as well as added. Composition churns faster than the list: Bytespider was 37.3% of AI
crawler traffic in July 2024 and 5.8% a year later, per
Cloudflare's measurements. Any
per-agent ruleset hand-tuned in 2024 was mostly obsolete in 2025. Peer-reviewed work found the
predictable consequence: 4.5% of sites disallowed user agents that Anthropic never operated, while
missing the one it did.
The deeper problem is labeling your own traffic. A USENIX Security paper on machine learning in security puts it directly: "reliable labels are typically not available, resulting in a chicken-and-egg problem". Cloudflare's answer is instructive about the scale required. They employ analysts writing heuristics tuned to a false positive rate of "0.0001% (One out of 1 million)" for the express purpose of generating labels to train models on, and they have shipped nine model generations since 2019, with their own documentation conceding that older versions' accuracy "may degrade".
IP reputation doesn't rescue it either. Peer-reviewed measurement of residential proxy networks found that 90% of exit addresses relay for about 870 seconds, that only 2.20% ever appear on a blocklist, and that the average blocklisting delay is 22 days. By the time an address is listed, it has moved on.
CAPTCHA isn't a reliable fallback. A reCAPTCHA v2 solve costs about $0.0008 at published solver prices, so the protected action has to be worth less than that for the economics to work. Research from ETH Zurich reported solving 100% of reCAPTCHA v2 image challenges with no statistically significant difference from a human in challenge count. The W3C's note on CAPTCHA inaccessibility is unambiguous about the cost to real users: the interactive task "inherently excludes many people with disabilities, resulting in a denial of service to these users". We go into that trade-off in CAPTCHAs versus Arcjet.
Treat published bot-traffic percentages with caution. No published "percentage of internet traffic that is bots" figure measures the internet. Each one measures a single vendor's customer base under that vendor's own classifier, and they disagree by nearly a factor of two, from 29% to 53%. Treat them as directional.
PII detection
Presidio is the reference open source implementation for PII detection, and its own documentation argues against treating PII detection as a solved problem. Its FAQ states that "there is no guarantee that Presidio will find all sensitive information. Consequently, additional systems and protections should be employed", and that it "is not an official product of any company and comes with no warranty or SLA". Its evaluation documentation says the vanilla configuration's "results aren't very accurate".
The project publishes the size of that gap in its own repository. On the 1,500-sample synthetic dataset in its evaluation notebooks, stock Presidio scores an F2 of 0.661 at 0.646 recall, and the tuned configuration reaches 0.910 F2 at 0.907 recall. Both are the maintainers' own numbers on their own synthetic data, and the notebooks say as much: "the synthetic dataset used here isn't representative of a real dataset". Read it as the shape of the gap, not as a benchmark.
What closes that gap is the interesting part, because the notebook is an itemised build bill. It swaps the NER model for a 434M-parameter transformer with 1.74 GB of weights, hand-writes a label map from that model's tags onto Presidio's entity names, adds three custom recognizers, deletes 14 that don't apply, widens the context window, and retunes the score threshold. All of that is specific to one dataset, and none of it transfers to yours.
Independent measurement backs that up. The Text Anonymization Benchmark, published in Computational Linguistics, measured stock Presidio's entity recall on direct identifiers – full person names and similar – at 0.460 on the test set. Closing that gap required fine-tuning a Longformer on in-domain data. A peer-reviewed clinical evaluation of Presidio with customization reported strict recall of 0.8064, and concluded that "additional checks are required to ensure person names are successfully anonymised".
Presidio's default coverage is narrower than its entity list suggests. Presidio's published entity list documents 80 types, of which 34 have a checksum or validation step and eight are model-driven. The rest are regular expressions plus optional context words, including US Social Security numbers, passports, driver's licences, and bank numbers. More to the point, only 19 of those types and 17 recognizers load by default for English. Everything else is opt-in, country-specific, or needs a separate model.
Presidio is candid about what those regexes are worth. Its own
Social Security recognizer
labels the bare nine-digit pattern "very weak" and scores it 0.05, and its card recognizer scores
the regex 0.3 and "weak", leaving the Luhn check and eleven context words to do the real work.
Checksums help less than people assume. Exactly 10% of random digit strings of any length pass the Luhn check, because the check digit is the unique digit that makes the weighted sum divisible by ten. Luhn tells you a number survived a transcription error. It doesn't tell you the number is a payment card. Order IDs and database keys in the correct shape will pass.
Names and addresses need a model, and models are domain-specific. On the same named-entity task, the highest published result on CoNLL-2003 newswire is 94.6 F1. On WNUT-2017 emerging entities in user-generated text, the shared task's winning system managed 41.86, and later work reaches only about 50. Your users write like the second corpus.
Downloading a purpose-built model doesn't reliably shortcut this either. Piiranha, one of the better
known PII taggers, is published under
cc-by-nc-nd-4.0 –
non-commercial, no derivatives – so it can't ship inside a commercial product at all. Check the
licence before the accuracy table. And in that table, surname recall is 0.78, the weakest row in a
model built specifically for this job.
Deployment adds its own constraints. Presidio's default spaCy model, en_core_web_lg, is 382.1 MB, against
AWS Lambda's 250 MB
unzipped package limit including layers, so you're on the container image path. spaCy's
published throughput is 10,014 words per second on CPU for
that pipeline and 684 for the accurate transformer one. And spaCy documents that with the spawn
start method "the model data is copied in memory for each new process", so per-worker memory
multiplies.
Compare that with the numbers in the sensitive information section: a 14.7 MB quantized model at about 6.6 ms median inference, running in-process. Both sets of figures are published, so you can do the comparison yourself.
Email validation
Email validation looks like a regular expression problem until you read the grammar. RFC 5322 defines the grammar in 133 ABNF rules, 53 of which are obsolete forms, and section 4 requires that those obsolete forms "MUST be accepted and parsed by a conformant receiver". RFC 6531 then extends the grammar to UTF-8, which makes any ASCII-only character class wrong by specification.
The famous 6,000-character validation regex has a real author and a real caveat. Paul Warren generated it from the grammar, and his own page records the limit: comments in addresses can nest arbitrarily, and "A single regular expression cannot cope with this". His module preprocesses addresses to strip comments before matching.
Syntax is the easier half. Deliverability needs MX lookups, disposable-domain intelligence that decays weekly, and a decision about role addresses.
Prompt injection detection
Prompt injection detection is the one control where neither building nor buying solves the problem. Arcjet ships prompt injection detection, and you can run it as a layer, but don't treat it as a gate on its own.
Standards bodies are direct about the limit. OWASP's 2026 Top 10 for LLM applications states that LLMs "make no architectural distinction between instructions and data", that "no reliable prevention mechanism exists today", and draws the conclusion in one sentence: "Defense is therefore architectural rather than interceptive." The UK's National Cyber Security Centre is blunter still, warning that prompt injection "cannot be fully mitigated with a product or appliance" and advising readers to "beware any that claim they can 'stop' prompt injection". NIST's adversarial machine learning taxonomy tells designers to assume "prompt injection attacks are possible if a model is exposed to untrusted input sources".
Independent measurements are lower than vendor figures. Vendor model cards routinely publish accuracy above 99%. Independent evaluation at a realistic false-positive budget puts the same detectors in single digits: one peer-reviewed benchmark measured a widely used open detector at 1.97% true positive rate at a 1% false positive rate, and 0.00% at 0.1%. Meta's own second-generation model card scores its own first generation at 21.2% recall at a 1% false positive rate, against the 99.9% headline on that model's own card. The gap isn't dishonesty. It's the operating point: an in-distribution test split flatters a classifier that a deployment budget does not.
Under adaptive attack, the relevant threat model for a security control, detectors fail. A team including researchers from Google DeepMind and ETH Zurich bypassed 12 recent defenses with attack success above 90% for most, noting that "the majority of defenses originally reported near-zero attack success rates". A separate study measured detection rates falling from 61% to 1% once the attacker adapted. Stacking doesn't help either: the same authors report that "simply adding more filters or stacking additional detectors does not resolve the underlying robustness problem". Trivial transformations still work – putting spaces between letters once took a shipped Meta classifier from 100% accuracy to 0.2%.
The false-positive cost decides the architecture, because detection that catches everything catches your product too. A USENIX Security evaluation found the only detector achieving a zero false negative rate did so with a false positive rate as high as 0.93. And in AgentDojo, adding a prompt injection classifier cut targeted attack success from 57.69% to 7.95% but destroyed 27.5 points of benign utility, while an architectural tool filter reached a better 6.84% attack success rate and raised utility. That result is the whole argument in one table: constraining what the agent may do beats trying to recognise what the attacker said.
Arcjet treats detection and authorization as two controls for that reason. Prompt injection
detection is a layer,
and we tell you to run it in DRY_RUN first and keep denials generic. The
control that actually holds is authorization at the point of action, which is what
agent guards are for. We make the same argument at more length in
the lethal trifecta, where a claimed 95% catch rate is a failing grade for
an exfiltration path, and in
human approval is not a security policy.
The fair counterweight, from the researchers who broke every detector they tested: "detectors are relatively easy to deploy and can still provide practical value by blocking some unsophisticated or opportunistic attacks, making them a useful – but limited – component of a broader defense strategy." Build or buy, you own the architecture either way. Buying saves you the model, the evaluation set, and the false-positive budget on a threat that mutates weekly.
A WAF rule set
The OWASP Core Rule Set (CRS) is free, well maintained, and a clear illustration of maintenance cost. Its own documentation on paranoia levels is direct: at PL4 "the rules are so aggressive that they detect almost every possible attack, yet they also flag a lot of legitimate traffic as malicious", and "Running at the highest paranoia level, PL 4, may seem appealing from a security standpoint, but it could take many weeks to tune away the false positives encountered". A note on the same page warns that writing exclusions "can be a substantial amount of work".
The CRS project ships continuously, and its changelog says what the work is. From v4.0.0 in February 2024 to v4.29.0 in August 2026 there were 33 stable v4 releases, close to one a month. Across the point releases after v4.0.0, 180 changelog entries are fixes and more than 40 correct false positives. The false positives and tuning guide explains why that work never finishes: "A fresh CRS deployment has no awareness of the web services that may be running behind it, or the quirks of how those services work." It also warns that editing rule files directly forks the rule set, making every update a manual reapplication. The issue tracker carries 454 false-positive reports and 162 evasion reports.
More rules can reduce detection. ModSec-AdvLearn, published in IEEE Transactions on Information Forensics and Security, measured vanilla ModSecurity with CRS at a fixed 1% false positive rate and found the true positive rate went down as the paranoia level went up: 66.82% at PL2 against 61.00% at PL4 on one dataset. The authors' conclusion is worth quoting: "increasing the number of rules does not improve detection capabilities; instead, it worsens them by increasing false positives." More rules is not more security.
The maintainers have documented what this costs, in unusual detail. In spring 2022, Yahoo and Intigriti ran a three-week bug bounty against CRS. Project co-lead Christian Folini's retrospective is subtitled "it's not for the faint of heart". The team had "estimated our capacity to be at around two security findings per week", then received 175 reports: "175 reports fixed and 511 individual payloads detected." Among them they "identified almost a dozen rule/partial ruleset bypasses", which Folini calls "really bad". Closing out three weeks of testing ran until February 2023, forced the replacement of the project's regex generator, and, in his words, "delayed the CRS v4 release by a year".
One line from that post summarizes the maintenance load of detection rules: "So an individual bypass is more or less daily business for us."
Some defects outlive entire release lines. In 2019, answering five ReDoS CVEs filed against its own regexes, the project was candid that the problem was known and unsolved: "We just have not solved it yet - or have not been able to solve it yet", noting that many rules were "10 or 15 years old" and predicting "This can take a while". Version 4.28.0, in July 2026, shipped five separate catastrophic-backtracking fixes, for the same class of defect seven years later.
Rule-set vulnerabilities aren't always fixable inside the rule set. A July 2026 advisory covers a bypass through XML attribute values that "affects approximately 159 rules across 9 rule files" at every paranoia level, and it needed a matching ModSecurity engine release to land. Changing a single regex safely is its own project: that release documents a differential test over roughly 2.4 million random inputs, a false-positive corpus run at all four paranoia levels, and an 889-test regression suite.
Deploying a rule set means inheriting its support window too. CRS patches "the two latest point releases on the current development line" plus one long-term support line. At close to monthly releases, a deployment that stops tracking updates leaves the supported set within a couple of months.
Cloudflare's 2019 outage is the canonical WAF incident. Cloudflare's post-mortem on the 2 July 2019 outage attributes 27 minutes of global downtime to "a single WAF rule that contained a poorly written regular expression that ended up creating excessive backtracking". The rule wasn't even blocking: it was deployed in simulate mode, "But even in the simulate mode the rules actually need to execute". Two numbers from that write-up describe the ongoing job better than any estimate: "In the last 60 days, 476 change requests have been handled for the WAF Managed Rules (averaging one every 3 hours)", and the remediation included "Manually inspecting all 3,868 rules".
Finally, CRS tells you what it can't do: "Application-specific vulnerabilities, such as logic bugs or missing authorization checks, cannot be detected by generic firewall rules." That limit is the same boundary we describe in SDK-based security versus WAF versus API gateway, and it's why a WAF is a layer rather than an answer.
When building in-house wins
Building in-house wins in five cases: when your policy needs your own domain model, when you already have a platform team, at extreme scale, when the data can't leave your environment, and when vendor dependency is a risk that you can't absorb. Building is the right call more often than vendors admit.
The strongest case is a policy that needs your domain model, and Shopify published the clearest example of it. Shopify moved its API from request counting to calculated GraphQL query complexity because clients "use the same amount of credits regardless, even if they don't need all the data in an API response", and because writes "produce side effects that demand more load on servers than GET requests". No generic limiter can express "this query is 40 times more expensive than that one". If your policy depends on semantics only your application knows, the rule belongs in your code. Arcjet's own token bucket exists for a mild version of this, and the reasoning extends further in.
The second case is a platform team that you already run. The marginal cost of one more control on a staffed internal platform differs completely from the cost of standing that platform up. Google's Site Reliability Engineering puts the minimum sustainable single-site on-call rotation at eight engineers, and caps aggregate operational work at 50% of SRE time. If you already run that, a rate limiter is a smaller step for you than for most. If you don't, the rotation is the cost, not the code.
The third case is extreme scale, which changes the arithmetic. Usage-based pricing is good value until your volume makes it your largest infrastructure line item. The most fully documented public case is 37signals, whose founder reports taking a cloud bill "from the original $3.2 million/year run rate" to $1.3 million, with roughly $700,000 of hardware "entirely recouped during 2023". Read it as advocacy from an unusually favorable case – stable workload, strong in-house operations, no regulatory constraint – and then run your own numbers.
Dropbox's S-1 is the more rigorous datum, because it's an SEC filing rather than a blog post, and because it discloses both sides. Moving off a third-party provider removed $92.5 million of vendor spend in one year, and added back $53.0 million in "depreciation, facilities, and support expense" for infrastructure they now operated themselves. Owning it consumed well over half the saving. That ratio is the number to carry into your own model.
The fourth case is data that you can't send anywhere. For some organizations this is a legal fact rather than a preference. Under the GDPR, Chapter V governs any transfer of personal data to a third country, and Schrems II both invalidated the EU-US Privacy Shield and required supervisory authorities to suspend transfers where standard contractual clauses "are not or cannot be complied with in that third country". In EU financial services, the Digital Operational Resilience Act (DORA) has applied since January 2025 and subjects reliance on critical ICT third parties to mandated contractual terms and direct supervisory oversight.
Don't overstate the data constraint, though. Transfers to the US aren't blocked. The Commission adopted a new adequacy decision for the EU-US Data Privacy Framework in July 2023, and the General Court upheld it in September 2025. The US remains on the Commission's adequacy list for organisations participating in the framework, with an appeal pending. What Chapter V leaves you with is a transfer assessment you have to make and document, not a prohibition.
Residency commitments also tend to carve out the category this page is about. Microsoft's EU Data Boundary documentation excludes Defender for Endpoint, Defender for Identity, and Defender for Cloud Apps, on the stated grounds that those services "require operations of global systems including artificial intelligence, automation, and humans on global data sets to hunt global customer threats". That reasoning is sound, because threat hunting is global work. It also means a data boundary you partly bought for security reasons need not cover your security tooling. Ask where each control inspects.
Be precise rather than absolute, because the answer differs per control. As the architecture section sets out, sensitive information detection never sends the body, while prompt injection detection does. Evaluate control by control, not vendor by vendor.
The fifth case is vendor dependency, which is a real risk. Buying moves a failure mode rather than removing it, and the public record is specific. A CrowdStrike content update live for 78 minutes affected 8.5 million Windows devices. A single regular expression cost Cloudflare 82% of its traffic for about half an hour. One customer's valid configuration change made 85% of Fastly's network return errors. Cloudflare's worst outage since 2019 originated in a bot management configuration file, which is exactly the kind of vendor-managed security config a buyer cannot inspect.
The sharper version of this risk is that exiting is largely theoretical. In the quarter after its outage, CrowdStrike told the SEC that while it had seen "delays in creating sales opportunities and longer sales cycles", it had "not experienced high levels of customer churn following the incident", and reported a dollar-based net retention rate of 115%. A vendor can blue-screen 8.5 million machines and keep its customers. Plan for a vendor failure you have to absorb, because that is the usual outcome, and prefer controls you can reason about and turn off yourself.
Terms change too. When Redis relicensed in 2024 it put an end date on security patches for the license you were already on. Google shut down reCAPTCHA v1 outright and has since deprecated its successor's documentation into a different Google Cloud product. Buying includes the risk that your dependency's terms move on someone else's schedule.
Mitigate it deliberately rather than hoping: understand the fail-open behavior, decide it per action, and know what your application does when the vendor is unreachable.
Building usually loses in the common case: a control that every application needs, ordinary requirements, no platform team, and failure modes subtle enough that you'll learn them in production.
What buying costs, concretely
Arcjet prices a monthly base plan plus metered usage. As of September 25, 2026, the plans are $25 a month for Individual and $299 a month for Startup, with Enterprise custom, and a 15-day trial, after which an account moves to a free plan capped at 10,000 requests a month. Usage is billed separately: $5 per million web requests, and $50 per million agent requests. Log retention differs by plan – one hour on Individual, 24 hours on Startup, and custom on Enterprise – which matters if you're relying on it for evidence. For longer retention and alerting, SIEM export to Datadog, Splunk, SentinelOne, Panther, and Amazon S3 is available on the Enterprise plan. See compliance evidence for AI agent activity for why that's not an archive.
Build the comparison with the build side from figures that you can check rather than from a vendor's calculator.
US Bureau of Labor Statistics figures for May 2025 put the median annual wage for software developers at $135,980 and for information security analysts at $129,180. Wages aren't the employer's cost, though: the BLS Employer Costs for Employee Compensation release for June 2026 reports that wages and salaries are 70.0% of employer costs for private industry workers, which makes the loading roughly 1.43 times salary before you count recruiting, equipment, or management.
The operational floor adds more. Google's Site Reliability Engineering states that "the minimum number of engineers needed for on-call duty from a single-site team is eight". Multiply that out at the median plus loading and a sustainable rotation is roughly $1.55 million a year in fully loaded payroll – that arithmetic is ours, not a published figure, and it buys you the rotation, not the security capability. The same book caps aggregate operations work at 50% of SRE time and budgets roughly six hours for each incident, covering root-cause analysis, remediation, and follow-up, which is why the rotation has to be that size.
Then add the recurring work each control brings. Every figure here is sourced earlier on this page:
- A bot corpus that grew from 36 to 165 entries in two years, and whose largest member changed entirely.
- A rule set shipping roughly monthly, whose own maintainers warn that PL4 tuning "could take many weeks".
- A PII model that needs a domain evaluation set, because stock recall on direct identifiers measured 0.460.
- A Redis cluster whose own documentation warns you about acknowledged writes being lost.
For most teams the deciding factor isn't the invoice. Andy Jassy put the general version of it in Amazon's 2024 shareholder letter, filed with the SEC: "Why should builders spend 80% of their time on the undifferentiated heavy lifting vs. their unique customer experience?" None of the work on this page differentiates your product, and all of it competes for the engineers who could build what does.
Check the statistics that you use in a business case. Several of the statistics that circulate in build-versus-buy arguments don't survive tracing. The claim that 100 ms of latency costs Amazon 1% of sales has no Amazon publication behind it; it traces to a presentation by a former Amazon engineer, Greg Linden, which Kohavi et al. at KDD 2014 cite. The same paper gives the nearest controlled measurement: a 250 ms server delay moved revenue about 1.5% at Bing, where the authors found a linear approximation reasonable across the delays they tested. The frequently quoted $4.45 million average breach cost is the 2023 IBM edition, three editions stale; the 2026 edition reports $4.99 million. If you're building a business case, check your own figures the way you'd want a vendor to check theirs.
How to decide
To decide, work through six steps for each control that you're considering:
- List the controls you actually need, by the consequential actions in your application rather than by feature name. Authentication endpoints, anything that spends money, anything that sends messages outward, and any tool an agent can invoke.
- For each one, decide whether it's differentiating. If a competitor having the same control wouldn't hurt you, it's table stakes.
- Cost the maintained version, not the first version. Include the corpus, the tuning, the on-call, and the false-positive budget.
- Check the data-residency constraint per control, not for the vendor as a whole, since the answer differs by control.
- Decide the failure behavior per action before you deploy anything. Fail open on a search page and fail closed on a payment is a coherent policy; one global setting isn't.
- Run in dry run first. Every Arcjet rule takes
mode: "DRY_RUN", which evaluates and records without blocking, andarcjet analyze dry-run-impactquantifies what would have happened.
To see the enforcement points in your own stack, the agent get started guide installs a skill that makes your coding agent framework-aware, and the CLI and MCP server let it inspect real decisions rather than guess.
Related reading: What is runtime application security? · Enforce security rules at runtime in code · AI agent security platforms compared
Frequently asked questions
What is Arcjet?
Arcjet is the AI agent runtime security platform. It discovers the agents running in your organization, enforces policy across every action, prompt, and tool call, and keeps the evidence to prove what happened. For agents and web apps that you build, import an SDK and call protect() on an HTTP request or guard() on an action with no request object. For Claude Code, GitHub Copilot, Cursor, OpenAI Codex, and Muse Code, Arcjet runs from the hook each agent already fires, with no SDK.
What features does Arcjet include?
Arcjet includes Shield WAF, rate limiting in three algorithms, bot protection, email validation, sensitive information detection, prompt injection detection, content moderation, filters, signup form protection, and agent guards for tool calls, with centrally managed Guard policies and destination threat analysis. Rules run through protect() on HTTP requests or guard() on other actions. Coding agents are covered through their hooks, and the Enterprise plan adds SIEM export.
Which Arcjet checks run locally and which call the cloud?
SDK-local sensitive information detection runs entirely in your process and never sends the request body. Shield, rate limiting, bot database lookups, email verification, and IP reputation filters need a Cloud API call, which Arcjet documents at typically 20 to 30 ms, with one call per request regardless of rule count. Prompt injection detection sends the prompt text to the Cloud API and adds approximately 100 ms.
Is it cheaper to build application security in-house?
Rarely, once you cost the maintained version rather than the first working version. Google's SRE book puts a sustainable single-site on-call rotation at eight engineers, and each control brings recurring work, such as a WAF rule set whose maintainers warn that tuning can take weeks. Building wins when your policy needs your own domain model, when you already run the platform, at extreme scale, or when data residency rules out a vendor.
Why is prompt injection detection not enough on its own?
Prompt injection detection isn't enough on its own because detection degrades against an adversary who adapts. OWASP's 2026 Top 10 states that no reliable prevention mechanism exists and that defense is architectural rather than interceptive, and researchers bypassed 12 recent defenses with attack success above 90%. Run detection as a layer, then authorize the action itself at the point of the side effect.
What are the documented limits of Arcjet Shield?
Shield analyzes request headers and query parameters, not the request body, and the analysis happens on the Arcjet platform after the request is reported. Body inspection is a separate feature, sensitive information detection, which runs locally instead. Shield is available as a remote rule, so a security team can enable it without a deploy.
AI runtime security in your code
Protect your AI agent workflows with Arcjet
Arcjet runs inside your application, where it can use runtime context to enforce agent actions and budgets, detect prompt injection, and protect sensitive information before a workflow acts.