AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified Apache-2.0 Self-run

Arcjet

skill-arcjet-skills-arcjet · by arcjet

Add Arcjet security protection to any code path — HTTP route handlers, API endpoints, AI agent tool calls, MCP servers, background jobs, and queue workers. Covers rate limiting, bot detection, email validation, prompt injection detection, sensitive information blocking, content moderation, and abuse prevention. Works with JavaScript/TypeScript, Python, and Go across Next.js, Express, Fastify, Sve…

No reviews yet
0 installs
10 views
0.0% view→install

Install

$ agentstack add skill-arcjet-skills-arcjet

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-arcjet-skills-arcjet)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
28d ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Arcjet? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Arcjet

Contents

  • [Add Arcjet Protection to Your App](#add-arcjet-protection-to-your-app)
  • [Choosing Protections](#choosing-protections)
  • [Resources](#resources)

Add Arcjet Protection to Your App

Checklist

  • [ ] Step 1: Verify language support (JS/TS, Python, or Go only — stop if unsupported)
  • [ ] Step 2: Connect to Arcjet platform (CLI → MCP → manual Console setup)
  • [ ] Step 3: Detect protection type and read the appropriate reference file
  • [ ] Step 4: Implement protection (separate client file, correct SDK, correct patterns)
  • [ ] Step 5: Verify decisions are firing correctly (trigger a real call, then check CLI / MCP / Console)

Step 1: Check Language Support

If the project's server-side code is not JavaScript, TypeScript, Python, or Go → tell the user in chat that Arcjet doesn't support their language yet. Don't modify the project, don't write a NOTES.md, don't invent a package. Just say it and stop.

Step 2: Get an ARCJET_KEY into the project's env file

Before writing any code, the project needs a real ARCJET_KEY in its env file. Don't write Arcjet code first and "leave the key as a TODO" — that just produces dead code. Get the key first, then wire it up.

In order of preference:

  1. Arcjet CLI (preferred). Check whether you're already signed in, then retrieve a key.
  2. Arcjet MCP server (endpoint: https://api.arcjet.com/mcp) — for clients with built-in MCP. See [references/mcp.md](references/mcp.md).
  3. Manual (last resort): tell the user to grab a key from https://app.arcjet.com.
CLI bootstrap (the normal path)
npx -y @arcjet/cli@latest auth status        # is the user already signed in?
# if not signed in:
npx -y @arcjet/cli@latest auth login         # browser device flow, see references/cli.md

npx -y @arcjet/cli@latest teams list --output json --fields id,name
npx -y @arcjet/cli@latest sites list --team-id  --output json --fields id,name
# if no suitable site exists:
npx -y @arcjet/cli@latest sites create --team-id  --name ""

npx -y @arcjet/cli@latest sites get-key --site-id  --output json --fields key

Write the key value to the project's env file as ARCJET_KEY=ajkey_.... Match whatever the project already does — filename, .env.example companion, .gitignore entry. If the project doesn't have a convention yet, default to whatever the framework expects and add the env file to .gitignore. Never hardcode the key in source.

See [references/cli.md](references/cli.md) for install options beyond npx, agent-mode flags, and the full command reference.

Install the SDK with the project's package manager

Once you know which SDK you need (Step 3 below), install it via the package manager the project already uses — npm install, pnpm add, yarn add, bun add, pip install, uv add, poetry add, go get, etc. Don't hand-edit package.json / requirements.txt / go.mod and guess a version: typed versions tend to be wrong (arcjet>=1.0.0 doesn't exist for the Python SDK; ^1.0.0 is stale for @arcjet/next; Go should use the module tag, not a copied pseudo-version), and the lockfile/module metadata won't get updated. Let the package manager pick the real version and pin it.

Step 3: Detect Protection Type and Read Reference

Determine which protection type applies:

| | Request-based | Guard | |---|---|---| | When to use | Code has an HTTP request object (Express req, Next.js Request, FastAPI Request, etc.) | No HTTP request (tool calls, MCP handlers, queue workers, background jobs, agent loops) | | JS/TS SDK | @arcjet/next, @arcjet/node, @arcjet/fastify, etc. | @arcjet/guard | | Python SDK | arcjet (with arcjet() / arcjet_sync()) | arcjet (with launch_arcjet() / launch_arcjet_sync()) | | Go SDK | github.com/arcjet/arcjet-go (with NewClient) | github.com/arcjet/arcjet-go (with NewGuardClient) | | Entry point | protect(request) / Protect(ctx, r) | guard(label, rules) / Guard(ctx, request) |

A single project can use both — e.g. request-based on API routes and guard on agent tool calls.

Common misclassifications to watch for:

  • MCP servers: the word "server" is misleading. MCP tools don't receive HTTP requests — they're invoked by an MCP client over stdio or SSE. Use Guard, not request-based.
  • Background jobs / queue consumers: no HTTP request at the protection site. Use Guard.
  • Server actions / RPC over HTTP (Next.js server actions, tRPC, etc.): there is an HTTP request underneath. Use request-based.
  • Agent tool calls inside a request handler: if you want to limit per-user-per-route, request-based is fine. If you want per-tool budgets independent of any HTTP boundary, use Guard at the tool call site.

Read the appropriate reference:

  • Request-based JS/TS: [references/requestsjavascript.md](references/requestsjavascript.md)
  • Request-based Python: [references/requestspython.md](references/requestspython.md)
  • Request-based Go: [references/requestsgo.md](references/requestsgo.md)
  • Guard JS/TS: [references/guardsjavascript.md](references/guardsjavascript.md)
  • Guard Python: [references/guardspython.md](references/guardspython.md)
  • Guard Go: [references/guardsgo.md](references/guardsgo.md)

These references explain architectural decisions and patterns that can't be inferred from the source code alone. For exact API signatures, read the installed package's types and doc comments.

Step 4: Implement Protection

Follow the patterns in the reference file from Step 3. Key principles:

Request-based (HTTP routes):
  • Create shared clients outside handlers and include Shield as a base rule. Use the exact constructor and rule names from the language reference.
  • In JavaScript/TypeScript, use withRule() for route-specific rules and decision.isDenied() for the result.
  • In Python, pass the complete rule list to arcjet() / arcjet_sync(); there is no with_rule() client method. Check decision.is_denied().
  • In Go, create one NewClient at package scope. WithRule() derives route-specific clients and returns (*Client, error), so handle initialization errors. Check decision.IsDenied().
  • Call protect() / Protect() inside each route handler (not in app-level middleware), once per request.
  • Map denial reasons to HTTP responses. Only branch on reasons that produce a different response — there is no point in a Shield-specific arm that returns the same status as the default 403.
  • Put the language's userId characteristic selector on the specific rule that needs it, then pass a trusted, authenticated user ID at protection time. Never rate limit by a client-controlled header unless a trusted proxy strips and rewrites it.
Guard (non-HTTP code):
  • Client at module scope with launchArcjet() (JS) or launch_arcjet() / launch_arcjet_sync() (Python — pick async vs sync to match the function you're protecting).
  • In Go, create one NewGuardClient at package scope.
  • Rules declared at module scope. Give each rule a meaningful label so they show up usefully in the Console.
  • One guard() call per specific operation, with a hardcoded label like "tools.get-weather" or "queue.summarize". Put it wherever you already know exactly what's happening — that can be inside the tool/task function itself, or right before calling it from a dispatch arm. Both work; pick whichever makes error propagation cleaner. What to avoid is the generic-dispatcher pattern (handleToolCall(name, args) calling guard(label=f"tools.{name}")) — interpolated labels break grep and produce messy Console groupings.
  • Label naming rules: labels are validated server-side as slugs — lowercase letters, digits, dash (-), and dot (.) only, must start and end with a letter or digit, max 256 bytes. Underscores, uppercase, and slashes are rejected even though some SDK TSDoc comments claim otherwise. Use tools.get-weather, not tools.get_weather or Tools.GetWeather.
  • Pass metadata on the guard() call when you have useful auditing context (metadata={"user_id": user_id, "request_id": ...}). It appears in the Console alongside the decision.
  • Branch on which rule denied, not just on DENY. Use the per-rule accessors (e.g. userLimit.deniedResult(decision) for retry-after info) or the flat reason string (decision.reason === "PROMPT_INJECTION" in JS, decision.reason == "PROMPT_INJECTION" in Python) so the error you surface to the caller tells them why — "rate limited, retry in 12s" vs "input flagged as prompt injection" — instead of a generic "blocked." Note: guard's decision.reason is a flat string literal, unlike the request-based SDK's tagged-helper API.
  • Every rate-limit rule needs a key and a bucket:
  • Per-user context (agent tool calls inside a logged-in session, queue jobs with a user_id): use the user/session id as the key.
  • No user context (stdio MCP server, single-tenant worker): use a stable identifier you control — instance id, deployment name, or a literal like "default". Just be explicit.
  • Check decision.conclusion === "DENY" (JS), decision.conclusion == "DENY" (Python), or decision.IsDenied() / decision.Conclusion == arcjet.ConclusionDeny (Go) before proceeding.
Conventions outside the Arcjet flow

For everything that isn't an Arcjet-specific decision — dev scripts, file/module layout, named-vs-default exports, comment style, env-file naming, type hints, error class patterns — match the project's existing conventions. If the project has no convention yet, default to modern best practice for the language. This skill is opinionated about where Arcjet goes and how its API is used; it shouldn't reach further than that.

Step 5: Verify Decisions

After wiring up protection, confirm it's actually firing. Three steps:

1. Type-check / build first. Run tsc, next build, python -m py_compile, or whatever check command the project uses. Catches wrong imports, wrong rule names, and stale type signatures before the user does.

2. Trigger a real call so a decision exists to check. Without one, the Console and CLI are empty and you can't tell whether protection is actually wired up.

  • Request-based: start the dev server (npm run dev, uvicorn main:app --reload, etc.) and curl the protected route. To trip a rate limit, loop the call: for i in {1..50}; do curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/api/your-route; done — you should see a mix of 200s and 429s once the limit is hit.
  • Guard: invoke the protected function directly. A tiny script that imports the tool/task function and calls it twice (once to allow, once to exceed the limit) is usually the fastest path — e.g. node -e "import('./src/tools.js').then(m => m.getWeather('SF', 'user_123'))" or python -c "from worker import process_job; process_job({'user_id': 'user_123'})". For MCP servers, send a tool call via the MCP client / inspector. For queue workers, enqueue a real job. Don't try to test guard by curling anything — there's no HTTP surface.

3. Confirm the decision in the Arcjet platform.

  • CLI: npx -y @arcjet/cli@latest requests list --site-id (request-based) or ... guards list --site-id (Guard)
  • MCP: list-requests / list-guards
  • Console: https://app.arcjet.com

For deeper investigation: arcjet requests explain --site-id --request-id or arcjet guards explain --site-id --guard-id .

If you can't run the app in the current environment, tell the user exactly what to do (which command to run, what to look for in the output) instead of silently skipping verification.

Gotchas

  • Wrong SDK/client: @arcjet/guard, arcjet.guard, and Go's NewGuardClient are for non-HTTP code. @arcjet/node / @arcjet/next / Python arcjet() / Go NewClient are for HTTP routes. Using the wrong one is the most common mistake.
  • Wrong placement: protect() must not be called in Express middleware or Next.js middleware. Call it inside each route handler.
  • Wrong layer for guard(): don't put guard() in a handleToolCall(name, args) dispatcher — put it inside each specific tool / task function so the label and metadata can be hardcoded.
  • Hand-edited dependency manifests: don't append "arcjet": "^1.0.0" to package.json or arcjet>=1.0.0 to requirements.txt. Run the project's package manager so the version is real and the lockfile updates.
  • Double-counting: Calling protect() or guard() multiple times for the same operation counts against rate limits multiple times.
  • Never hardcode ARCJET_KEY — always use environment variables.

Choosing Protections

When you need to pick which rules address the user's concern — bot abuse, rate limits, prompt injection, signup spam, PII, IP filtering, etc. — load [references/choosingprotections.md](references/choosingprotections.md). It maps common problems to Arcjet rules and explains the tradeoffs between strategies (e.g. token bucket vs sliding window). The mapping doesn't need to be in your context for the rest of the workflow.

Resources

For exact API signatures, parameter names, and the full set of rules and helpers, read the installed SDK's source — types and docstrings are the source of truth:

  • Python SDK: https://github.com/arcjet/arcjet-py — arcjet package (request protection) and arcjet.guard subpackage (non-HTTP guard).
  • JavaScript / TypeScript SDK: https://github.com/arcjet/arcjet-js — monorepo with framework-specific packages (@arcjet/next, @arcjet/node, @arcjet/fastify, @arcjet/sveltekit, @arcjet/guard, etc.).
  • Go SDK: https://github.com/arcjet/arcjet-go — github.com/arcjet/arcjet-go module with request and guard clients. The initial tagged release is v0.1.0.
  • Docs: https://docs.arcjet.com — narrative guides, blueprints, and product reference.
  • Console: https://app.arcjet.com — sites, keys, and decision history.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.