arcjet/arcjet-js/integrate-arcjet-guard-claude-managed-agents

Claude Code and agent skill by arcjet/arcjet-js

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Integrate Arcjet security into Claude Managed Agents (hosted REST+SSE, beta managed-agents-2026-04-01) using @arcjet/guard — screen user.message / initial_events before sessions.events.send, and gate custom tools on agent.custom_tool_use. Use when asked to add Arcjet to Claude Managed Agents, rate limit custom tools, or block prompt injection on hosted sessions. Not the Claude Agent SDK.

Install

npx vskill@latest install arcjet/arcjet-js/integrate-arcjet-guard-claude-managed-agents

SKILL.md

Integrate Arcjet Guard into Claude Managed Agents

@arcjet/guard's Claude Managed Agents v0 namespace wraps the agent's existing Arcjet client. It never talks to the Arcjet API itself. Anthropic runs the hosted tool loop. There is no PreToolUse.

This is not @arcjet/guard/claude-agent-sdk/v0. Do not reuse that adapter, its hooks, or guardTool.

  • Inbound text (user.message / initial_events) → guardEvents()

before sessions.events.send. DENY does not send. There is no guardInbound.

  • A custom tool you execute (agent.custom_tool_use) →

guardCustomTool(). DENY does not run the tool; send user.custom_tool_result with is_error: true and error text (that field exists on the real events API). Self-hosted EnvironmentWorker / betaTool({ run }) uses the same gate. The CLI worker cannot register custom tools.

  • Correlation → claudeManagedAgentsContext() is caller-owned only.

It never mints. It never treats Anthropic session/event ids as if we created them. Never traceId.

What cannot be gated

Default permission_policy: always_allow cannot be gated. Anthropic executes bash/read/write in the cloud sandbox before your process sees an event. web_search / web_fetch always run on Anthropic. MCP: Anthropic is the client; customer-side Guard is on custom tools and MCP servers you host. user.tool_confirmation is HITL (always_ask), not a policy gate — do not add a confirmation helper as the happy path. Same trap as Cloudflare Think needsApproval.

protect() stays fail-open. These helpers are fail-closed.

Docs: https://docs.arcjet.com/guards/claude-managed-agents/ (shared JS+Python page). Do not edit /guards/claude-agent-sdk/ or /guards/claude-agent-sdk-py/.

Questions to ask the human first

Ask only what you cannot infer from the code; suggest defaults.

  1. Which custom tools does the app execute on agent.custom_tool_use?

Those get guardCustomTool. Built-ins under always_allow cannot.

  1. What limits? (e.g. "10 lookups/min per order" → tokenBucket.)
  2. Who is the user for metadata — an opaque user/tenant ID (never PII)?

Default: none. Session/event ids from Anthropic are not the correlation id.

  1. Is an Arcjet outage unacceptable? Every helper defaults to

onGuardError: "deny", which also decides a call whose rules or metadata callback threw or returned a value Guard cannot use: Guard is still called without that value, and a DENY stands. Ask explicitly about inbound guardEvents: failing closed there means the turn is not sent.

The things readers get wrong

  1. This is not the Claude Agent SDK. No guardTool, no guardHooks,

no PreToolUse, no UserPromptSubmit. Do not also wrap with @arcjet/guard/claude-agent-sdk/v0 or @arcjet/guard/vercel-ai/v7.

  1. There is no `guardInbound`. The helper is guardEvents.
  2. The import path is versioned and there is no alias.

@arcjet/guard/claude-managed-agents/v0. @arcjet/guard/claude-managed-agents does not resolve. There is no /v1.

  1. Correlation is caller-owned, never minted. Do not pass

session.id / event ids to claudeManagedAgentsContext as if Arcjet created them. Never traceId.

  1. Default `always_allow` cannot be gated. Do not claim we can block

Anthropic-cloud bash/read/write.

  1. On custom-tool DENY, send `user.custom_tool_result`. Do not invent

fields the schema lacks. is_error exists — use it. Do not run the tool. On ALLOW, the caller must still send the success result or the session idles.

  1. **Do not mix a gated user turn with a tool result in one send unless

you mean to.** guardEvents will still forward non-user.message events when the turn is denied, so a batched user.custom_tool_result is not dropped. Prefer sending tool results on their own.

  1. Do not add a `claude-managed-agents` example in this SDK repo.

Examples belong in arcjet/examples.

Step 1: Install and find the guard client

Install @arcjet/guard (required), plus @anthropic-ai/sdk (optional peer, needed for types). Always use the versioned path.

sh
npm install @arcjet/guard @anthropic-ai/sdk

If the agent has no guard client yet, launch one once at module scope:

ts
import { launchArcjet } from "@arcjet/guard";
export const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });

Step 2: Screen inbound before sessions.events.send

ts
import { detectPromptInjection } from "@arcjet/guard";
import { claudeManagedAgentsContext, guardEvents } from "@arcjet/guard/claude-managed-agents/v0";
import { arcjet } from "./arcjet.js";
async function sendUserMessage(userText: string) {
const ctx = claudeManagedAgentsContext({ correlationId: conversationId });
const events = [
{ type: "user.message" as const, content: [{ type: "text" as const, text: userText }] },
];
const verdict = await guardEvents(
arcjet,
{
events,
inbound: {
action: "message.received",
rules: ({ text }) => [detectPromptInjection()(text)],
},
context: ctx,
},
(body) => client.beta.sessions.events.send(session.id, body),
);
if (!verdict.allowed) {
return verdict.message;
}
}

The same helper gates sessions.create({ initial_events }) — pass those events and send them only on ALLOW.

To screen PII in the same text, add localDetectSensitiveInfo({ backend: rampart() })(text) to inbound.rules. rampart comes from @arcjet/sensitive-info-rampart, which detects PII on-device in your app process before anything is sent to Anthropic.

Step 3: Gate custom tools you execute

ts
import { tokenBucket, policyInput } from "@arcjet/guard";
import { guardCustomTool } from "@arcjet/guard/claude-managed-agents/v0";
import { arcjet } from "./arcjet.js";
const lookupLimit = tokenBucket({
refillRate: 10,
intervalSeconds: 60,
maxTokens: 10,
});
const stream = await client.beta.sessions.events.stream(session.id);
for await (const event of stream) {
if (event.type === "agent.custom_tool_use") {
const gated = await guardCustomTool(
arcjet,
{
event,
execute: (input) => lookupOrder(input),
send: (result) => client.beta.sessions.events.send(session.id, { events: [result] }),
},
{
action: "order.looked-up",
actor: userId,
inputs: (input) => ({
orderNumber: policyInput.server.string(String(input["orderNumber"])),
}),
rules: (input) => [lookupLimit({ key: String(input["orderNumber"]), requested: 1 })],
context: ctx,
},
);
if (gated.allowed) {
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.custom_tool_result",
custom_tool_use_id: event.id,
content: [{ type: "text", text: JSON.stringify(gated.output) }],
},
],
});
}
}
}

Self-hosted: wrap betaTool({ run }) with the same guardCustomTool (pass the tool as the second argument). The CLI worker cannot register custom tools.

Optional actor and inputs (static, or a resolver over this adapter's native call — parsed input plus trusted runtime/context) are forwarded on the guard call so a remote policy that declares those names can evaluate. Build each input with policyInput. A resolver that throws or returns a value Guard cannot use is left out of the guard call, and onGuardError decides.

Step 4: Correlation

Mint (or load) a conversation id the app owns. Pass it to claudeManagedAgentsContext({ correlationId }). Do not use Anthropic session.id or event ids as if Arcjet created them. If the id is missing or invalid, the call is uncorrelated rather than joined to a generated id nobody has.

Verify the integration

  1. npm run typecheck passes.
  2. Exercise inbound PI (send is not called), a custom-tool deny (execute

is not called; user.custom_tool_result with is_error: true is sent), a rate limit, and fail-closed (an unreachable guard).

  1. Confirm in the Arcjet dashboard that decisions share the caller-owned

conversation id — not an Anthropic session/event id you did not pass.

  1. Manual E2E with a real ARCJET_KEY is still-to-verify until you run it.

A full working demo belongs in `arcjet/examples` (follow-up; do not add an example under examples/ in the JS SDK repo).

Note: capture events are fire-and-forget and batched, so events can lag the decisions they accompany by a few seconds. A dropped event is diagnosed, never thrown.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
5

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/arcjet/arcjet-js/integrate-arcjet-guard-claude-managed-agents/badge)](https://verified-skill.com/skills/arcjet/arcjet-js/integrate-arcjet-guard-claude-managed-agents)

Something wrong with this skill or its page? Tell us.