Model or dataset
arcjet/arcjet-js avatar
arcjet/arcjet-js

Runtime guards for tool calls, and a warning about your own proxy

Runtime security for AI apps and agents: prompt injection detection, tool-call authorization, sensitive-data redaction, bot protection, and rate limiting. Drop it into your JS/TS code.

684 stars33 forksTypeScriptApache-2.0

At a glance

What is it?
The Arcjet JavaScript SDK is a monorepo of runtime security packages for AI applications that splits its whole API in two depending on whether an incoming request object exists, ships a guard for tool calls in workers and queues where there is no HTTP layer, blocks install scripts from ten transitive dependencies, and pins twelve transitive versions by hand in its root manifest, while the most useful paragraph in the documentation is the one warning that your client IP may be a spoofable forwarding header.
Who is it for?
It fits a team shipping an agent in JavaScript or TypeScript that has realised its tool calls are unauthenticated entry points, since the guard path is the part that has no analogue in most web security tooling. Three things to check before you rely on it.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 5, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One question decides which package you need

The SDK is split into two product lines, and the split is drawn on a single technical fact rather than on frameworks.

Request protection is for anything that receives an HTTP request: route handlers, API endpoints and middleware. You install a framework package and call a protection function on the request.

Guard protection is for anything without an HTTP request: agent tool calls, MCP server handlers, queue workers and background jobs. You install one package and call a guard function around the work.

That distinction is the thing to internalise, because the second category is where almost all agent security work has been missed. A tool call is an entry point. It accepts input chosen by a model, it can read data, and it can hit APIs. None of it passes through your HTTP middleware, so anything you enforce at the edge does not apply to it.

The decision rule the documentation gives is one sentence: if you have an HTTP request, use a framework SDK; if you do not, use the guard. And both can be used in the same project, which is the normal case for an application with a chat endpoint and the tool handlers behind it.

That also explains why the guard package is not in the framework table. It is not a framework adapter, and treating it as one would obscure the only interesting thing about it.

Twelve framework packages over a handful of adapters

The framework table is longer than the number of adapters it lists, and reading it carefully shows the real structure.

Twelve rows of frameworks. Roughly a dozen packages. So the mapping is not one to one, and the reuse is where the maintenance burden lives.

One Node package serves the Node runtime itself and Express, and it is also listed for Hono alongside the Bun package, meaning Hono can run on either runtime and the package choice follows the runtime rather than the framework. That is the correct factoring, since a framework that runs on both is two integrations in one row.

The rest are one package each: Next.js, Fastify, NestJS, Nuxt, Remix, React Router, SvelteKit, Astro, Bun and Deno. Deno's install line is notable, using the platform's own package syntax with an npm prefix rather than npm itself.

For the guard path there is a single install command and a single package, with no per-framework variants. So the framework fan-out is entirely on the request side, which is consistent with the design: request handling is where frameworks differ, and tool invocation is where they do not.

The help section completes the picture with working starter projects for every framework and a set of blueprints, which are described as recipes for common security patterns. Recipes rather than a reference is the right format for the question people actually ask, which is where do I put this check.

Your client address may be a header anyone can set

The most useful paragraph in this documentation is a warning, and it is placed at the point of installation rather than buried in a reference.

The problem: when the platform does not provide a client address, the library may fall back to a forwarding header. Those headers can be spoofed by a client that can reach your application directly, or by a proxy that passes through values the client supplied.

What the library does about it is the interesting part. It does not refuse the request. It continues protecting the request, logs one warning for the lifetime of each client instance, and marks the source as unverified in a named provenance field inside its debug output.

So the design decision is to keep protecting and to make the degradation visible exactly once. A per-instance warning avoids log spam while still telling an operator that something is wrong, and the provenance value means you can assert on it in your own telemetry rather than reading logs.

The remediation guidance is then specific. Make the application reachable only through a proxy that overwrites or safely appends forwarding headers. List every trusted hop in the configuration. Use a helper for a named proxy service where one is offered. Trusting the entire address space emits a configuration warning rather than being silently accepted.

There is also an escape hatch for applications that already know their client's address: pass that validated value explicitly to both the debugging call and the protection call, and malformed values are rejected.

The skill is discovered without running package code

Installation is directed at an AI coding agent, and the mechanism is unusual enough to be worth understanding.

You log in with a command-line tool first, then run an installer that discovers the security skill from your installed packages:

sh
npx @arcjet/cli auth login
npx @tanstack/intent@latest install

The documentation stresses that discovery happens without executing package code. That distinction matters: the usual way a package influences an agent is by running something at install time, and this path does not.

Then the allowlist. You add an entry to your manifest naming which packages are permitted to surface skills, and the example shows exactly two. So a malicious transitive dependency cannot introduce instructions to your agent unless it is on a list you wrote.

The next step is scoping. You load only the skill for the current task, with a command naming the specific skill, rather than installing all of them into every session. That keeps the instructions your agent reads proportional to the job.

Two caveats are stated plainly. Editor hooks installed by the tool are described as convenience and explicitly not a security boundary, which is a sentence that saves people from trusting the wrong thing. And the older standalone marketplace install still works, but the documentation recommends the versioned package when you want the skill to match the SDK version already in your node_modules.

There is also a plugin for two specific editors that bundles skills, an MCP server and coding rules, for teams who want one install rather than three.

Ten dependencies are forbidden from running install scripts

The root manifest contains a block that is unusual to see in a published package, and it is the clearest statement of the project's security posture.

Ten dependencies have their install scripts disabled. They are, in the order listed: a Firebase utility, a Google generative AI client, a compression library for a MongoDB driver, a polyfill bundle, two build-tool packages, a filesystem-watcher native module, a compression binding, a native inference runtime, a protobuf runtime, an image library, and a JavaScript runtime.

Every one of these is a package whose postinstall step downloads, compiles or links native code. That is the exact category that supply-chain attacks target, because a compromised package does not need a vulnerability in your application if it owns your build step.

So the repository has decided that those install scripts are never needed for its own tests, and it says so declaratively rather than in prose. That is the correct way to express it, because it is enforced by the package manager rather than by a reviewer remembering a rule.

The rest of the tooling in the same block is pinned to exact versions rather than ranges: a formatter, a linter, a type-aware lint companion, and the task runner. Four exact pins in a monorepo root is the discipline you would expect from a project whose product is a security library.

Twelve hand-written overrides for transitive versions

The same root manifest carries an overrides block, and reading it tells you what the maintainers are actually spending their time on.

Two model provider clients and their shared utility package are pinned to exact versions. So is an archive library, and two different versions of a URI parser are forced on two different parents, because the two parents need different ones and the conflict has to be resolved per branch rather than globally.

Two packages have an image library forced upward with a floor. One has an HTTP client forced upward with a floor. One has a cookie implementation forced upward with a floor, and that override is nested inside a specific framework integration's dependency, which means the fix is scoped to the one package that needed it.

Every one of those is a transitive dependency pinned by hand, which means every one of them is work: someone hit a build failure or a security advisory, worked out the minimal scope, and wrote a line here. That work is invisible in the package's public API and entirely load-bearing.

The engine requirements are equally specific. Node must be a narrow band of two major versions with minimum patch levels, and the package manager itself is pinned. That is unusual for a library that runs on modern JavaScript everywhere, and it is consistent with the rest: this is a project that has decided the cost of being permissive is higher than the cost of being narrow.

So an integrator should read both blocks before upgrading anything, and expect to merge overrides of their own.

Redaction ships twice, once compiled to WebAssembly

The repository layout is a monorepo with the framework adapters at the top and a layer of small building blocks underneath, and the names describe the product.

There is an address-resolution package and a headers package, which is where the client IP logic and its debugging helpers live. There is a transport package and a protocol package, which is how a call leaves your process. There is a runtime package, a logger, a cache, a body helper, and a duration helper.

Then the parts that are the product. A redaction package, and a second copy of it compiled to WebAssembly. An analysis package, and again a WebAssembly version. Those two pairs are the sensitive-data handling, and the duplication is there because one runtime cannot do both jobs: the WebAssembly build runs where you cannot install a native module, and the JavaScript build runs where you want the faster path.

Two package names are worth pausing on. One is described as a rampart for sensitive information, which is the same idea as the redaction package under a different metaphor, and having both suggests one is the detection pass and the other the redaction pass. One is a stable hash, which is almost certainly there so that redaction can be deterministic and so identical values hash identically across runs.

Then the lineage is visible in the tree. Three packages carry an older product name in their directory, along with framework variants of it, and one of the overrides in the root manifest is scoped inside that older package's dependency. So an acquired or renamed project is still shipping here, and its transitive pins are still being maintained by these maintainers.

Editorial conclusion

It fits a team shipping an agent in JavaScript or TypeScript that has realised its tool calls are unauthenticated entry points, since the guard path is the part that has no analogue in most web security tooling. Three things to check before you rely on it. The client address is only as trustworthy as your proxy configuration, and the library is explicit that a spoofable forwarding header still yields a warning rather than a refusal, so the deployment hardening is yours to do. The install path runs through a skill discovered from installed packages, and the package declares which packages may surface skills, so that allowlist is part of your security posture. And this is a monorepo with two entry points that can coexist, so decide per surface whether you have a request or you have a tool call.

Frequently asked questions

What is Arcjet used for?

It is runtime security for AI applications and agents, called inside your code before an action happens. The capabilities named are prompt injection detection, authorisation of agent tool calls, redaction of sensitive data, bot protection and rate limiting. The stated model is that your AI features take real actions, calling tools, reading data and hitting APIs, so each action is enforced in real time and then audited.

What is the difference between @arcjet/guard and a framework SDK?

It depends on whether you have an incoming HTTP request. Framework packages protect route handlers, endpoints and middleware. The guard package protects anything without a request object: agent tool calls, MCP server handlers, queue workers and background jobs. Both can be used in the same project, which is the normal case when a chat endpoint sits in front of tool handlers.

Which frameworks does the Arcjet JS SDK support?

Twelve rows are listed against about a dozen packages: Next.js, Node.js, Bun, Deno, Express, Fastify, Hono, NestJS, Nuxt, Remix, React Router, SvelteKit and Astro. The Node package serves both the Node runtime and Express and is also offered for Hono alongside the Bun package, since Hono runs on either runtime. Guard protection is a single package with no framework variants.

How does Arcjet handle a client IP that comes from a forwarding header?

When the platform does not supply a client address it may use a forwarding header such as X-Forwarded-For, which a client can spoof if it can reach the application directly or the proxy preserves supplied values. Arcjet continues protecting the request, logs one warning for the lifetime of each client instance, and marks the source as unverified in a client IP provenance field. The fix is to make the application reachable only through a proxy that overwrites those headers and to list every trusted hop in configuration.

How does the Arcjet skill get into your coding agent?

An installer discovers the skill from your installed packages without executing package code, and you add an allowlist entry to your manifest naming which packages may surface skills, so only those two can. You then load only the skill for the current task. The documentation notes that editor hooks installed by the tool are convenience rather than a security boundary, and recommends the versioned package over the older marketplace install so the skill matches your installed SDK.

Official sources

  1. arcjet/arcjet-js on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/arcjet-arcjet-js.svg)](https://hysenlabs.com/projects/arcjet-arcjet-js)