arcjet/arcjet-js/integrate-arcjet-guard-genkit
Claude Code and agent skill by arcjet/arcjet-js
- community
Checking that the SKILL.md file is still in the repository.
Integrate Arcjet security into a Genkit JS agent using @arcjet/guard — wrap ai.defineTool, put guardMiddleware on generate({ use }) for unwrapped / MCP / filesystem tools, and read a caller-owned id from generate({ context }). Use when asked to add Arcjet to genkit, rate limit its tools, screen inbound messages, or block prompt injection / PII.
Install
npx vskill@latest install arcjet/arcjet-js/integrate-arcjet-guard-genkitSKILL.md
Integrate Arcjet Guard into a Genkit app
@arcjet/guard's Genkit v1 namespace wraps the agent's existing Arcjet client. It never talks to the Arcjet API itself. Three surfaces, one decision rule:
- An authored tool (
ai.defineTool(config, handler)) →guardTool().
After defineTool the object is a ToolAction; generate() calls it as a function. DENY returns a structured ArcjetDenialResult. Do not throw. Do not call interrupt(). Do not throw ToolInterruptError.
- Filesystem / MCP / unwrapped tools →
guardMiddleware(). A
generate({ use }) middleware whose tool hook is the generate()-wide gate. It denies by returning a completed ToolResponsePart without calling next(). Already-branded tools are skipped when they can be looked up. Requires the generateMiddleware tool hook (Genkit >= 1.33).
- Correlation →
genkitContext()reads a field the integrator put
on generate({ context }) / the tool handler's { context } (correlationId, then sessionId, then conversationId, then a caller-owned flow / run id). It never mints a new id. It never reads traceId. It never treats interrupt / resumed as correlation.
This namespace is JS `genkit()` + `ai.defineTool` + `ai.generate`. Not Go / Python Genkit. Do not also wrap the same tool with @arcjet/guard/vercel-ai/v7 or @arcjet/guard/claude-managed-agents/v0. Zod is Genkit's, not ours.
Screen user text before generate() — there is no inbound hook. Middleware model is not Guard.
There is no first-class inbound channel, so there is no guardInbound. Put prompt-injection (and other inbound rules) in the application before ai.generate() / chat.send(). The middleware model hook intercepts the model call, not user text. It is not this policy gate.
interrupt() / defineInterrupt / toolApproval are HITL, not a policy gate.
interrupt() / defineInterrupt / @genkit-ai/middleware toolApproval / restartTool / finishReason === "interrupted" is human-in-the-loop. Same trap as Mastra requireApproval, Claude canUseTool, LangGraph interrupt(), OpenAI Agents needsApproval, Google ADK requireConfirmation, and Cloudflare Think needsApproval. There is no guardApproval. Do not wrap them as Guard.
Deny inside defineTool (and guardMiddleware's tool hook). MCP and filesystem-injected tools skip an unwrapped handler.
The authored defineTool handler is the deny point for tools you own. Filesystem middleware tools, MCP tools, and anything not wrapped with guardTool skip that handler. guardMiddleware is the generate()-wide gate for those. returnToolRequests: true means the app calls the tool itself — guardTool on the defineTool handler still gates that; guardMiddleware does not run if they never generate() the tool.
guardMiddleware can deny. Genkit's resolveToolRequest treats a ToolResponsePart returned without calling next() as a completed tool result. Throwing ToolInterruptError sets finishReason: "interrupted" (HITL — do not do this). Returning undefined drops the tool request (do not do this).
generate({ use }) must receive a plain object `{ name, instantiate }`. A raw function becomes a _model_ hook only. A function with instantiate
pluginthrows “must be called with ()”.
Questions to ask the human first
Ask only what you cannot infer from the code; suggest defaults.
- Which tools are risky (external side effects, irreversible, spends
money, sends messages)? Those get guardTool. MCP / filesystem / tools you did not author get guardMiddleware.
- What limits? (e.g. "10 lookups/min per order" →
tokenBucket.) - Who is the user for metadata — an opaque user/tenant ID (never PII)?
Default: none. Pass it via metadata on the policy. Put the conversation / session id you already have on ai.generate({ context: { sessionId } }) _and_ on guardMiddleware({ sessionId }) — the tool hook does not receive ALS context today. That id is the correlation id, not the user.
- Is an Arcjet outage unacceptable? Every helper defaults to
onGuardError: "deny". Ask explicitly about inbound screening before generate(): failing closed there means the agent does not run 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
- There is no `guardInbound`. Screen prompt injection before
ai.generate() / chat.send(). Middleware model is not Guard.
- `interrupt()` is not a policy gate. It is HITL. Use
guardTool
or guardMiddleware. A denial is a completed toolResponse, not finishReason: "interrupted".
- The import path is versioned and there is no alias.
@arcjet/guard/genkit/v1. @arcjet/guard/genkit does not resolve.
- Correlation is read, never minted. Do not call
createAgentContext
inside a generate / tool callback — that generates a second id and splits the Sequence. Put the id you already chose on generate({ context }). Do not read Session.sessionId from a Session constructed without an id — that class mints a UUID. Do not use traceId (OTel / Genkit mints one). Do not treat interrupt / resumed as correlation.
- Do not double-wrap with `@arcjet/guard/vercel-ai/v7`.
guardTool
throws if the tool already carries the Arcjet protection brand.
- A denial from `guardTool` is a structured object, not a throw.
Wrap the returned ToolAction (the callable generate() invokes), not the inner handler. outputSchema validation runs _inside_ action(). Wrapping outside means DENY returns ArcjetDenialResult without schema check, so the model still sees a completed tool result. Wrapping the inner handler would throw on schema mismatch and fail generate().
Step 1: Install and find the guard client
Install @arcjet/guard (required), plus genkit (optional peer, needed for @arcjet/guard/genkit/v1). Always use the versioned path: @arcjet/guard/genkit/v1 resolves; @arcjet/guard/genkit throws ERR_PACKAGE_PATH_NOT_EXPORTED. Zod is Genkit's peer, not ours — install zod only if the app already uses it outside genkit. Public API is import { genkit, z } from "genkit". Node 22+.
If the agent has no guard client yet, launch one once at module scope:
Step 2: Gate authored tools
- Omit
rulesto submit none. The guard call still happens. - Optional
actorandinputs(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.
- On DENY the closed-over handler never runs. The model receives
{ arcjetDenied: true, reason, message, retryable } as toolResponse.output.
- Default
onGuardError: "deny"blocks the tool if Arcjet is unreachable,
or if a rules, metadata or sessionId callback throws or returns a value Guard cannot use. A failed callback never skips the guard call: Guard is called without its value, so remote policy still evaluates the call and a DENY stands.
- Prefer omitting
outputSchemaon guarded tools, or verify the schema
accepts ArcjetDenialResult / your onDeny shape. A denial is not schema-checked because the wrapper sits outside action().
multipart: truetools work too. Their denial is returned as the
output field of the multipart response, which is the field generate() copies to toolResponse.output.
Step 3: Gate unwrapped / MCP / filesystem tools
Already-branded (guardTool) actions skip the middleware guard when they can be looked up on the registry. Tools that cannot be looked up are still gated.
generate({ context }) is delivered to authored handlers via ALS. The tool-hook ctx from toRunOptions is only { metadata, resumed } — no ALS context. Put the same id on policy.sessionId when you need tool-time correlation through the hook.
Step 4: Screen inbound before generate
There is no guardInbound.
Step 5: Correlation
Put the id you already have on the app context you pass to generate():
Preference order: context.correlationId, then context.sessionId, then context.conversationId, then a caller-owned flowId / runId, then the envelope copies, then init.sessionId. If none is a valid 1–256 printable-ASCII string, the call is uncorrelated rather than joined to a generated id nobody has.
Never read Session.sessionId from a Session constructed without an id — that class mints a UUID. Never read traceId. Never treat interrupt / resumed as correlation.
Verify the integration
This is the start of the file. Read the full SKILL.md on GitHub.
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
- SKILL.md in the repositoryCheckingarcjet-guard/skills/integrate-arcjet-guard-genkit/SKILL.md
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
[](https://verified-skill.com/skills/arcjet/arcjet-js/integrate-arcjet-guard-genkit)