arcjet/arcjet-js/integrate-arcjet-guard-eve

Claude Code and agent skill by arcjet/arcjet-js

Trust tier 3
  • community
Source check pending

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

Integrate Arcjet security into a Vercel Eve agent using @arcjet/guard — add guard gates to tools and connections, screen inbound messages, and record agent lifecycle events correlated to the session. Use when asked to add Arcjet to an Eve agent, rate limit its tools, guard connection access, or screen inbound messages.

Install

npx vskill@latest install arcjet/arcjet-js/integrate-arcjet-guard-eve

SKILL.md

Integrate Arcjet Guard into a Vercel Eve agent

@arcjet/guard's Vercel Eve v0 namespace wraps the agent's existing Arcjet client. It never talks to the Arcjet API itself. Four surfaces, one decision rule:

  • An authored tool (agent/tools/*.ts) → guardTool() if you need its

execution outcome at the call site, or guardApproval() if you only need to gate it. Only guardTool observes success or failure.

  • A connection's operations (agent/connections/*.ts) → guardApproval()

on the connection's approval field. There is no local execute; nothing else can gate these.

  • An inbound message (agent/channels/*.ts) → guardInbound() to screen

text before the agent sees it. This is the only place a turn can be declined before it starts.

  • Everything else → arcjetHooks() to observe agent lifecycle events.

Hooks are observe-only by design and cannot block.

The three in-session helpers correlate by session id, so their decisions land on one Sequence. guardInbound runs before the session exists and correlates by whatever identity the channel has, so its decision lands on a _second_ Sequence. arcjetHooks emits an eve.session-started record carrying both, which is what lets you pivot from one to the other. This is not hosted Claude Managed Agents (@arcjet/guard/claude-managed-agents/v0).

Questions to ask the human first

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

  1. Which tools and connections are risky (external side effects,

irreversible, spends money, sends messages)? Those get gates. Purely informational tools can be left unguarded or gated with no rules.

  1. What limits? (e.g. "10 lookups/min per order" → tokenBucket;

"5 integrations/hour" → slidingWindow.)

  1. Who is the user for metadata — an opaque user/tenant/installation ID

(never PII)? Default: the Eve principal from the session context.

  1. Is an Arcjet outage unacceptable? Should the agent be blocked if the guard

is unavailable? Every helper defaults to onGuardError: "deny", including the channel. Ask explicitly about the channel anyway: failing closed there means the agent stops answering entirely for the duration of the outage, so "allow" is a routine and legitimate choice at that one call site.

The six things readers get wrong

State plainly why each applies to Eve, not other frameworks:

  1. Hooks cannot reject a turn. Their handlers return void. If the request

is "block prompt injection", the answer is guardInbound at the channel, not a hook. Hooks are for audit trails, not enforcement.

  1. The import path is versioned and there is no alias. @arcjet/guard/vercel-eve/v0.

@arcjet/guard/vercel-eve does not resolve, and neither does /v1. The segment tracks Eve's major, and Eve is pre-1.0, so it gets v0. When Eve ships 1.0, a /v1 path will be added alongside this one.

  1. Correlation is not passed; it is read from the session. Never call

createAgentContext inside an Eve callback — the session id already is the run identity, and generating a second one splits the Sequence. eveAgentContext is exported for callers who need the context explicitly. Three of the four helpers call it themselves; guardInbound runs before the session exists, so it takes an explicit correlationId instead.

  1. `approval` is one field per tool or connection. It can be a function

(request-time only) or { request, response }. You still cannot assign always()/once()/never() from eve/tools/approval _alongside_ guardApproval — the slot holds one value. onAllow: "user-approval" is how you require a human after the request-time gate. The optional response policy is how you authorize who may approve the parked request. A rejected response leaves the approval pending; it does not deny the tool. HITL clients answer with cancel, not deny. Request-time denials are still { type: "denied" }. Same trap as Google ADK requireConfirmation and Cloudflare Think needsApproval: HITL is not a policy gate.

  1. `defineDynamic` tools are not covered. Eve's compiler hoists a dynamic

tool's inline execute to a module-scope step function, so a wrapper is not visible to it. Gate those with guardApproval() instead — the approval gate runs at decision time.

  1. A denial from `guardTool` throws (Eve projects it as a failed action.result),

whereas a denial from guardApproval is a denied status carrying a reason the model reads. Prefer the gate when you want the model to adapt; use guardTool when you need the outcome.

Step 1: Install and find the guard client

Install @arcjet/guard (required), plus eve (optional peer, needed for @arcjet/guard/vercel-eve/v0 and must be on Node 24+). Every agent helper lives on that one path. Always use explicit versions: @arcjet/guard/vercel-eve/v0 resolves, but @arcjet/guard/vercel-eve does not — omitting the version is deliberate (it prevents silent API breaking changes when a new major version is supported). Attempting to import from an unversioned path throws ERR_PACKAGE_PATH_NOT_EXPORTED.

sh
npm install @arcjet/guard eve

Note: Eve requires Node.js >= 24. @arcjet/guard supports Node >= 22, but the Eve integration does not. Verify the agent's engines declares ">=24" or note the floor in deployment docs.

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: Gate authored tools

ts
import { tokenBucket, policyInput } from "@arcjet/guard";
import { guardTool } from "@arcjet/guard/vercel-eve/v0";
import { defineTool } from "eve/tools";
import { z } from "zod";
import { arcjet } from "../arcjet.js";
const lookupLimit = tokenBucket({
bucket: "lookups",
refillRate: 10,
intervalSeconds: 60,
maxTokens: 10,
});
export default guardTool(
arcjet,
defineTool({
description: "Look up an order by ID",
inputSchema: z.object({ orderId: z.string() }),
async execute(input) {
return { orderId: input.orderId, status: "shipped" };
},
}),
{
action: "order.looked-up",
actor: userId,
inputs: (input) => ({
orderId: policyInput.server.string(input.orderId),
}),
rules: (input) => [lookupLimit({ key: input.orderId, requested: 1 })],
},
);
  • Omit rules to submit none. The guard call still happens, so the decision is

correlatable and the tool can be managed via policy configured outside the code.

  • 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.

  • rules may be a callback over the tool's parsed input, computed from the

data being acted on.

  • On DENY the tool's execute never runs; Eve projects it as a failed

action.result. The model receives details about the denial.

  • Guard policy unavailability: if the guard cannot be evaluated (e.g. Arcjet

API unreachable), the default is onGuardError: "deny" — the tool is blocked and Eve reports the error. For read-only operations like lookups, set onGuardError: "allow" if availability matters more than enforcement.

  • A rules or metadata callback that throws, or returns a value Guard

cannot use, never skips the guard call: Guard is called without that value (no local rules, or no policy metadata), so remote policy still evaluates the call and a DENY stands. Otherwise onGuardError decides as for an outage. guardApproval handles its callbacks the same way.

Tool-only: guardTool is called at tool invocation time and observes the outcome. If you only need to gate the tool without observing its result, use guardApproval() instead. To require a human after the request-time gate, set onAllow: "user-approval" and, when you need to authorize the responder, add a response policy.

Step 3: Gate connection operations

ts
import { tokenBucket } from "@arcjet/guard";
import { guardApproval } from "@arcjet/guard/vercel-eve/v0";
import { defineOpenAPIConnection } from "eve/connections";
import { arcjet } from "../arcjet.js";
const apiLimit = tokenBucket({
bucket: "api-access",
refillRate: 30,
intervalSeconds: 60,
maxTokens: 30,
});
export default defineOpenAPIConnection({
description: "Orders API",
spec: "https://api.example.com/openapi.json",
approval: guardApproval(arcjet, {
action: "orders-api.read",
rules: (ctx) => [apiLimit({ key: ctx.session.id, requested: 1 })],
onAllow: "user-approval",
response: {
action: "orders-api.approved",
rules: (ctx) => [apiLimit({ key: ctx.response.principal.principalId, requested: 1 })],
},
}),
operations: {
allow: ["GetOrder"],
},
});
  • The request-time callback receives the ApprovalContext which carries

session.id, so you can key limits per user/session.

  • On request-time DENY the operation is blocked; Eve returns a denied status

the model can read and adapt to. Contrast guardTool, which throws.

  • onAllow: "user-approval" parks the call for a human. HITL clients answer

with cancel (not deny). A request-time denial is still { type: "denied" }.

  • The optional response policy authorizes the responder, which Eve passes

as ctx.response.principal; Eve releases before 0.69 pass it as ctx.responder instead. ALLOW returns { status: "allowed" }. DENY returns { status: "rejected", reason } and leaves the approval pending.

  • This gate is the only way to protect connection operations — there is no

middleware or hook alternative.

Step 4: Screen inbound messages

ts
import { detectPromptInjection } from "@arcjet/guard";
import { guardInbound } from "@arcjet/guard/vercel-eve/v0";
import { defineChannel, POST } from "eve/channels";
import { arcjet } from "../arcjet.js";
export default defineChannel({
routes: [
POST("/webhook", async (req, args) => {
const body = (await req.json()) as Record<string, unknown>;
const message = body.message as string | undefined;
const conversationId = body.conversationId as string | undefined;
if (!message || typeof message !== "string") {
return new Response(JSON.stringify({ error: "Missing message" }), { status: 400 });
}
// Require a stable conversation identity. A generated or per-request id
// joins to nothing, and `from()` would mint a new continuation every
// time, so no session is ever resumed.
if (!conversationId || typeof conversationId !== "string") {
return new Response(JSON.stringify({ error: "Missing conversationId" }), { status: 400 });
}
// Authenticate the caller before trusting a body-supplied conversation
// id: `from()` resolves it to whichever session currently owns that
// address, so an unauthenticated route lets anyone post into — and read
// the decisions of — a conversation whose id they can guess.
//
// The same value is the guard's correlation id and the channel-local
// continuation address, which is what makes the two Sequences joinable.
const correlationId = conversationId;
const verdict = await guardInbound(arcjet, message, {
rules: [detectPromptInjection()(message)],
action: "message.received",
correlationId,
});

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 90 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

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

Source

Popularity

Repository stars
681
Repository forks
31
7-day trend score
33

Stars and forks are for the whole GitHub repository, not this skill alone. 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-eve/badge)](https://verified-skill.com/skills/arcjet/arcjet-js/integrate-arcjet-guard-eve)

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