npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@arcjet/guard

v1.4.0

Published

Arcjet Guards SDK — AI guardrails for rate limiting, prompt injection detection, and sensitive info detection

Downloads

1,556

Readme

@arcjet/guard

Arcjet is the runtime security platform that ships with your AI code. Guards provide rate limiting, prompt injection detection, sensitive information detection, and custom rules for AI tool calls and other backend operations. Every feature works on Node.js, Deno, Bun, and Cloudflare Workers.

This is the Arcjet Guards SDK.

Getting started

  1. Get your Arcjet key at app.arcjet.com
  2. npm install @arcjet/guard
  3. Pass your key to launchArcjet({ key: process.env.ARCJET_KEY! })
  4. Add a guard to your code — see the quick start below

npm package | GitHub source | Other SDKs

Features

  • 🪣 Rate Limiting — token bucket, fixed window, and sliding window algorithms; model AI token budgets per user.
  • 🛡️ Prompt Injection Detection — detect and block prompt injection attacks before they reach your LLM.
  • 🕵️ Sensitive Information Detection — block PII, credit cards, and custom patterns from entering your AI pipeline.
  • 🔧 Custom Rules — define your own local evaluation logic with arbitrary data.

Quick start

This example protects an AI tool call with token bucket rate limiting and prompt injection detection.

import { launchArcjet, tokenBucket, detectPromptInjection } from "@arcjet/guard";

// Create the Arcjet client once at module scope
const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });

// Configure reusable rules
const limitRule = tokenBucket({ refillRate: 10, intervalSeconds: 60, maxTokens: 100 });
const piRule = detectPromptInjection();

// Per request — create rule inputs each time
const rl = limitRule({ key: userId, requested: tokenCount });
const decision = await arcjet.guard({
  label: "tools.weather",
  rules: [rl, piRule(userMessage)],
});

// Overall decision
if (decision.conclusion === "DENY") {
  if (decision.reason === "RATE_LIMIT") {
    throw new Error("Rate limited — try again later");
  }
  if (decision.reason === "PROMPT_INJECTION") {
    throw new Error("Prompt injection detected — please rephrase");
  }
  throw new Error("Request denied");
}

// Check for errors (fail-open — errors don't cause denials)
if (decision.hasError()) {
  console.warn("At least one rule errored");
}

// From a RuleWithInput — result for this specific submission
const r = rl.result(decision);
if (r) {
  console.log(r.remainingTokens, r.maxTokens);
}

// From a RuleWithConfig — first denied result across all submissions
const denied = limitRule.deniedResult(decision);
if (denied) {
  console.log(denied.remainingTokens); // 0
}

// Proceed with your AI tool call...

Rate limiting

Token bucket

Use this when requests have variable cost — for example, an LLM endpoint where each call consumes a different number of tokens. The bucket refills at a steady rate and allows bursts up to maxTokens.

import { launchArcjet, tokenBucket } from "@arcjet/guard";

const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });

const limitRule = tokenBucket({
  bucket: "user-tokens", // Optional — defaults to "default-token-bucket"
  refillRate: 2_000, // Refill 2,000 tokens per interval
  intervalSeconds: 3600, // Refill every hour
  maxTokens: 5_000, // Maximum 5,000 tokens in the bucket
});

const decision = await arcjet.guard({
  label: "tools.chat",
  rules: [limitRule({ key: userId, requested: tokenEstimate })],
});

if (decision.conclusion === "DENY" && decision.reason === "RATE_LIMIT") {
  throw new Error("Rate limit exceeded");
}

Fixed window

Use this when you need a hard cap per time period — the counter resets at the end of each window. Simple to reason about, but allows bursts at window boundaries. If that matters, use sliding window instead.

import { launchArcjet, fixedWindow } from "@arcjet/guard";

const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });

const limitRule = fixedWindow({
  bucket: "page-views", // Optional — defaults to "default-fixed-window"
  maxRequests: 1000, // Maximum requests per window
  windowSeconds: 3600, // 1-hour window
});

const decision = await arcjet.guard({
  label: "api.search",
  rules: [limitRule({ key: teamId })],
});

Sliding window

Use this when you need smooth rate limiting without the burst-at-boundary problem of fixed windows. The server interpolates between the previous and current window, so limits are enforced across any rolling time span. Good default choice for API rate limits.

import { launchArcjet, slidingWindow } from "@arcjet/guard";

const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });

const limitRule = slidingWindow({
  bucket: "event-writes", // Optional — defaults to "default-sliding-window"
  maxRequests: 500, // Maximum requests per interval
  intervalSeconds: 60, // 1-minute rolling window
});

const decision = await arcjet.guard({
  label: "api.events",
  rules: [limitRule({ key: userId })],
});

Prompt injection detection

Detect and block prompt injection attacks — attempts to override your AI model's instructions — before they reach your model. Also useful for scanning tool call results that contain untrusted input (e.g. a "fetch" tool that loads a webpage which could embed injected instructions).

import { launchArcjet, detectPromptInjection } from "@arcjet/guard";

const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });

const piRule = detectPromptInjection();

const decision = await arcjet.guard({
  label: "tools.chat",
  rules: [piRule(userMessage)],
});

if (decision.conclusion === "DENY" && decision.reason === "PROMPT_INJECTION") {
  throw new Error("Prompt injection detected — please rephrase your message");
}

// Forward to your AI model...

Sensitive information detection

Detect and block PII in text content. Use allow / deny to filter which entity types trigger a denial. Built-in entity types are CREDIT_CARD_NUMBER, EMAIL, PHONE_NUMBER, and IP_ADDRESS.

import { launchArcjet, localDetectSensitiveInfo } from "@arcjet/guard";

const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });

const si = localDetectSensitiveInfo({
  deny: ["CREDIT_CARD_NUMBER", "PHONE_NUMBER"],
});

const decision = await arcjet.guard({
  label: "tools.summary",
  rules: [si(userMessage)],
});

if (decision.conclusion === "DENY" && decision.reason === "SENSITIVE_INFO") {
  throw new Error("Sensitive information detected");
}

Custom rules

Define your own local evaluation logic with arbitrary key-value data. When evaluate is provided, the SDK calls it locally before sending the request. The function receives (config, input, { signal }) and must return { conclusion: "ALLOW" | "DENY" }.

import { launchArcjet, defineCustomRule } from "@arcjet/guard";

const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });

const topicBlock = defineCustomRule<
  { blockedTopic: string },
  { topic: string },
  { matched: string }
>({
  evaluate: (config, input) => {
    if (input.topic === config.blockedTopic) {
      return { conclusion: "DENY", data: { matched: input.topic } };
    }
    return { conclusion: "ALLOW" };
  },
});

const rule = topicBlock({ data: { blockedTopic: "politics" } });

const decision = await arcjet.guard({
  label: "tools.chat",
  rules: [rule({ data: { topic: userTopic } })],
});

Decision inspection

Every .guard() call returns a Decision object. You can inspect it at three levels of detail:

const rl = limitRule({ key: userId, requested: tokenCount });
const decision = await arcjet.guard({
  label: "tools.weather",
  rules: [rl, piRule(userMessage)],
});

// Overall decision
decision.conclusion; // "ALLOW" | "DENY"
decision.reason; // "RATE_LIMIT" | "PROMPT_INJECTION" | ... (only on DENY)

// Error check (fail-open — errors don't cause denials)
decision.hasError(); // true if any rule errored

// Per-rule results — iterate all
for (const result of decision.results) {
  console.log(result.type, result.conclusion);
}

// From a RuleWithInput — this specific submission's result
const r = rl.result(decision);
if (r) {
  console.log(r.remainingTokens, r.maxTokens);
}

// From a RuleWithConfig — first denied result across all submissions
const denied = limitRule.deniedResult(decision);
if (denied) {
  console.log(denied.remainingTokens); // 0
}

Methods available on both RuleWithConfig and RuleWithInput:

| Method | RuleWithConfig (e.g. limit) | RuleWithInput (e.g. rl) | | ------------------------ | ------------------------------- | ---------------------------------- | | results(decision) | All results for this config | Single-element or empty array | | result(decision) | First result (any conclusion) | This submission's result | | deniedResult(decision) | First denied result | This submission's result if denied |

Best practices

  • Create the client and rule configs once at module scope, not per request. The client holds a persistent connection (HTTP/2 on Node.js); rule configs carry stable IDs used for server-side aggregation.

    // Create the client once at module scope
    const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });
    
    // Configure reusable rules (also at module scope)
    const limitRule = tokenBucket({ refillRate: 10, intervalSeconds: 60, maxTokens: 100 });
    
    // Per request — created each time
    const decision = await arcjet.guard({
      label: "tools.weather",
      rules: [limitRule({ key: userId })],
    });
  • Don't wrap launchArcjet() in a helper function. This defeats connection reuse:

    // Bad — creates a new client (and connection) every call
    function getArcjet() {
      return launchArcjet({ key: process.env.ARCJET_KEY! });
    }
    const decision = await getArcjet().guard({ ... });
    
    // Good — reuses the client
    const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });
    const decision = await arcjet.guard({ ... });
  • Start rules in DRY_RUN mode to observe behavior before switching to LIVE. This lets you tune thresholds without affecting real traffic:

    const limitRule = tokenBucket({
      mode: "DRY_RUN",
      refillRate: 10,
      intervalSeconds: 60,
      maxTokens: 100,
    });
  • Handle errors explicitly. Check decision.hasError() to detect rules that errored during evaluation. The SDK fails open — an errored rule does not cause a denial:

    if (decision.hasError()) {
      console.error("Guard error — proceeding with caution");
    }
  • Use labels to identify protection boundaries. Labels appear in the Arcjet dashboard and help correlate decisions with specific tool calls or API endpoints.

  • Use bucket on rate limit rules to name your counters in the dashboard. Different configs sharing the same bucket name still get independent counters — a config hash is appended server-side.

MCP server

Connect your AI assistant to the Arcjet MCP server at https://api.arcjet.com/mcp to manage sites, retrieve SDK keys, and more. See the docs for setup instructions.

Runtime support

| Runtime | Minimum version | | ------------------ | ------------------------ | | Node.js | 22.18.0 | | Bun | 1.3.0 | | Deno | stable / lts | | Cloudflare Workers | compat date 2025-09-01 |

[!TIP] Import from @arcjet/guard — the correct transport is selected automatically via conditional exports (HTTP/2 on Node.js and Bun, fetch-based on Deno and Cloudflare Workers).

License

Apache License, Version 2.0 © Arcjet Labs, Inc.