# Arcjet - JS SDK
[Arcjet][arcjet] 是随您的 AI 代码一起提供的运行时安全平台。阻止机器人和自动化攻击消耗您的 AI 预算、泄露数据或滥用工具。
这是一个包含多个 [Arcjet][arcjet] 开源包的 monorepo,专为 JS 设计。
## 快速开始
1. **获取 API 密钥** — [在 `app.arcjet.com` 注册](https://app.arcjet.com)。
2. **安装对应框架的 SDK:** 每个功能都可在任意 JavaScript 应用中使用。
| 框架 | 包 | 安装命令 |
| ------------ | --------------------------- | ----------------------------- |
| Next.js | `@arcjet/next` | `npm i @arcjet/next` |
| Node.js | `@arcjet/node` | `npm i @arcjet/node` |
| Bun | `@arcjet/bun` | `bun add @arcjet/bun` |
| Deno | `@arcjet/deno` | `deno add npm:@arcjet/deno` |
| Express | `@arcjet/node` | `npm i @arcjet/node` |
| Fastify | `@arcjet/fastify` | `npm i @arcjet/fastify` |
| Hono | `@arcjet/node` 或 `@arcjet/bun` | `npm i @arcjet/node` |
| NestJS | `@arcjet/nest` | `npm i @arcjet/nest` |
| Nuxt | `@arcjet/nuxt` | `npm i @arcjet/nuxt` |
| Remix | `@arcjet/remix` | `npm i @arcjet/remix` |
| React Router | `@arcjet/react-router` | `npm i @arcjet/react-router` |
| SvelteKit | `@arcjet/sveltekit` | `npm i @arcjet/sveltekit` |
| Astro | `@arcjet/astro` | `npm i @arcjet/astro` |
3. **设置环境变量:**
```
# .env.local(或您的框架的 env 文件)
ARCJET_KEY=ajkey_yourkey
```
4. **保护路由** — 参考 [AI 保护示例](#vercel-ai-sdk-example)
或下方的 [功能示例](#features)。
### 获取帮助
[加入我们的 Discord 服务器][discord-invite] 或 [联系支持][support]。
- [文档](https://docs.arcjet.com) — 完整参考与指南
- [示例应用](#example-apps) — 每个框架的启动项目
- [蓝图](#blueprints) — 常见安全模式的解决方案
## 功能特性
- 🔒 [提示注入检测](#prompt-injection-detection) — 在到达 LLM 之前检测并阻止提示注入攻击。
- 🤖 [机器人防护](#bot-protection) — 阻止爬虫、凭证填充和 AI 爬虫滥用端点。
- 🛑 [速率限制](#rate-limiting) — 令牌桶、固定窗口和滑动窗口算法;按用户控制 AI 令牌预算。
- 🕵️ [敏感信息检测](#sensitive-information-detection) — 阻止 PII、信用卡号和自定义模式进入 AI 管道。
- 🛡️ [防护盾 WAF](#shield-waf) — 防御 SQL 注入、XSS 及其他常见 Web 攻击。
- 📧 [邮箱验证](#email-validation) — 在注册时阻止一次性、非法和无法投递的地址。
- 📝 [注册表单防护][feature-signup-protection] — 结合机器人防护、邮箱验证和速率限制保护注册表单。
- 🎯 [请求过滤](#request-filters) — 基于 IP、路径、头部和自定义字段的表达式规则。
- 🌐 [IP 分析](#ip-analysis) — 包含地理定位、ASN、VPN、代理、Tor 和主机检测。
- 🧩 [Arcjet Guard](#arcjet-guard) — 用于 AI 代理工具调用和后台任务的低级 API(无 HTTP 请求)。
## 示例应用
- [Astro][github-arcjet-example-astro]
- [Deno][github-arcjet-example-deno]
- [Express][github-arcjet-example-express]
- [FastAPI][github-arcjet-example-fastapi]
- [Fastify][github-arcjet-example-fastify]
- [NestJS][github-arcjet-example-nestjs]
- [Next.js][github-arcjet-example-nextjs] ([在线体验][arcjet-example])
- [Nuxt][github-arcjet-example-nuxt]
- [React Router][github-arcjet-example-react-router]
- [Remix][github-arcjet-example-remix]
- [SvelteKit][github-arcjet-example-sveltekit]
- [Tanstack Start][github-arcjet-example-tanstack-start]
## 蓝图
- [AI 配额控制][blueprint-ai-quota-control]
- [Cookie 横幅][blueprint-cookie-banner]
- [自定义规则][blueprint-custom-rule]
- [IP 地理定位][blueprint-ip-geolocation]
- [反馈表单][blueprint-feedback-form]
- [恶意流量][blueprint-malicious-traffic]
- [支付表单][blueprint-payment-form]
- [抽样流量][blueprint-sampling-traffic]
- [VPN 与代理][blueprint-vpn-proxy]
## 用法
阅读 [`docs.arcjet.com`][arcjet-docs] 的文档。
### Vercel AI SDK 示例
此示例使用 [Vercel AI SDK]([vercel-ai-sdk]) 保护 Next.js 的 AI 聊天路由,拦截自动客户端以膨胀成本、强制执行每用户令牌预算、检测消息中的敏感信息,并在到达模型前阻止提示注入攻击。
```
// app/api/chat/route.ts
import { openai } from "@ai-sdk/openai";
import arcjet, {
detectBot,
detectPromptInjection,
sensitiveInfo,
shield,
tokenBucket,
} from "@arcjet/next";
import type { UIMessage } from "ai";
import { convertToModelMessages, isTextUIPart, streamText } from "ai";
const aj = arcjet({
key: process.env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com
// Track budgets per user — replace "userId" with any stable identifier
characteristics: ["userId"],
rules: [
// Shield protects against common web attacks e.g. SQL injection
shield({ mode: "LIVE" }),
// Block all automated clients — bots inflate AI costs
detectBot({
mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only
allow: [], // Block all bots. See https://arcjet.com/bot-list
}),
// Enforce budgets to control AI costs. Adjust rates and limits as needed.
tokenBucket({
mode: "LIVE",
refillRate: 2_000, // Refill 2,000 tokens per hour
interval: "1h",
capacity: 5_000, // Maximum 5,000 tokens in the bucket
}),
// Block messages containing sensitive information to prevent data leaks
sensitiveInfo({
mode: "LIVE",
// Block PII types that should never appear in AI prompts.
// Remove types your app legitimately handles (e.g. EMAIL for a support bot).
deny: ["CREDIT_CARD_NUMBER", "EMAIL"],
}),
// Detect prompt injection attacks before they reach your AI model
detectPromptInjection({
mode: "LIVE",
}),
],
});
export async function POST(req: Request) {
const userId = "user-123"; // Replace with your session/auth lookup
const { messages }: { messages: UIMessage[] } = await req.json();
const modelMessages = await convertToModelMessages(messages);
// Estimate token cost: ~1 token per 4 characters of text (rough heuristic)
const totalChars = modelMessages.reduce((sum, m) => {
const content =
typeof m.content === "string" ? m.content : JSON.stringify(m.content);
return sum + content.length;
}, 0);
const estimate = Math.ceil(totalChars / 4);
// Extract the most recent user message to scan for injection and PII
const lastMessage: string = (messages.at(-1)?.parts ?? [])
.filter(isTextUIPart)
.map((p) => p.text)
.join(" ");
const decision = await aj.protect(req, {
userId,
requested: estimate,
sensitiveInfoValue: lastMessage,
detectPromptInjectionMessage: lastMessage,
});
if (decision.isDenied()) {
if (decision.reason.isBot()) {
return new Response("Automated clients are not permitted", {
status: 403,
});
} else if (decision.reason.isRateLimit()) {
return new Response("AI usage limit exceeded", { status: 429 });
} else if (decision.reason.isSensitiveInfo()) {
return new Response("Sensitive information detected", { status: 400 });
} else if (decision.reason.isPromptInjection()) {
return new Response(
"Prompt injection detected — please rephrase your message",
{ status: 400 },
);
} else {
return new Response("Forbidden", { status: 403 });
}
}
const result = await streamText({
model: openai("gpt-4o"),
messages: modelMessages,
});
return result.toUIMessageStreamResponse();
}
```
### 提示注入检测
检测并阻止提示注入攻击——在到达模型之前防止覆盖 AI 模型指令的尝试。在每次调用 `protect()` 时通过 `detectPromptInjectionMessage` 传递用户消息。
```
import arcjet, { detectPromptInjection } from "@arcjet/next";
const aj = arcjet({
key: process.env.ARCJET_KEY!,
rules: [
detectPromptInjection({
mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only
}),
],
});
export async function POST(request: Request) {
const { message } = await request.json();
const decision = await aj.protect(request, {
detectPromptInjectionMessage: message,
});
if (decision.isDenied() && decision.reason.isPromptInjection()) {
return new Response(
"Prompt injection detected — please rephrase your message",
{ status: 400 },
);
}
// Forward to your AI model...
}
```
### 机器人防护
Arcjet 允许您配置允许或拒绝的机器人列表。指定 `allow` 表示拒绝所有其他机器人。空允许列表会阻止所有机器人。
可用类别:`CATEGORY:ACADEMIC`、`CATEGORY:ADVERTISING`、`CATEGORY:AI`、`CATEGORY:AMAZON`、`CATEGORY:APPLE`、`CATEGORY:ARCHIVE`、`CATEGORY:BOTNET`、`CATEGORY:FEEDFETCHER`、`CATEGORY:GOOGLE`、`CATEGORY:META`、`CATEGORY:MICROSOFT`、`CATEGORY:MONITOR`、`CATEGORY:OPTIMIZER`、`CATEGORY:PREVIEW`、`CATEGORY:PROGRAMMATIC`、`CATEGORY:SEARCH_ENGINE`、`CATEGORY:SLACK`、`CATEGORY:SOCIAL`、`CATEGORY:TOOL`、`CATEGORY:UNKNOWN`、`CATEGORY:VERCEL`、`CATEGORY:WEBHOOK`、`CATEGORY:YAHOO`。您也可以允许或拒绝[特定机器人名称][bot-list]。
```
import arcjet, { detectBot } from "@arcjet/next";
import { isSpoofedBot } from "@arcjet/inspect";
const aj = arcjet({
key: process.env.ARCJET_KEY!,
rules: [
detectBot({
mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only
allow: [
"CATEGORY:SEARCH_ENGINE", // Google, Bing, etc
// Uncomment to allow these other common bot categories:
// "CATEGORY:MONITOR", // Uptime monitoring services
// "CATEGORY:PREVIEW", // Link previews e.g. Slack, Discord
// See the full list at https://arcjet.com/bot-list
],
}),
],
});
export async function GET(request: Request) {
const decision = await aj.protect(request);
if (decision.isDenied() && decision.reason.isBot()) {
return new Response("No bots allowed", { status: 403 });
}
// Arcjet verifies the authenticity of common bots using IP data.
// Verification isn't always possible, so check the results separately.
// https://docs.arcjet.com/bot-protection/reference#bot-verification
if (decision.results.some(isSpoofedBot)) {
return new Response("Forbidden", { status: 403 });
}
return new Response("Hello world");
}
```
机器人可按[类别][feature-bot-protection]和/或[特定机器人名称][bot-list]进行配置。例如,允许搜索引擎和 OpenAI 爬虫,但拒绝所有其他机器人:
```
detectBot({
mode: "LIVE",
allow: ["CATEGORY:SEARCH_ENGINE", "OPENAI_CRAWLER_SEARCH"],
});
```
### 速率限制
Arcjet 支持令牌桶、固定窗口和滑动窗口算法。
令牌桶非常适合控制 AI 令牌预算——设置 `capacity` 为用户可花费的最大令牌数,`refillRate` 为每 `interval` 恢复的令牌数,并通过 `protect()` 中的 `requested` 扣除令牌。
`interval` 接受字符串(`"1s"`、`"1m"`、`"1h"`、`"1d"`)或秒数。使用 `characteristics` 以用户为单位而非 IP 地址跟踪限制。
```
import arcjet, { tokenBucket } from "@arcjet/next";
const aj = arcjet({
key: process.env.ARCJET_KEY!,
characteristics: ["userId"], // Track per user
rules: [
tokenBucket({
mode: "LIVE",
refillRate: 2_000, // Refill 2,000 tokens per hour
interval: "1h",
capacity: 5_000, // Maximum 5,000 tokens in the bucket
}),
],
});
const decision = await aj.protect(request, {
userId: "user-123",
requested: estimate, // Number of tokens to deduct
});
if (decision.isDenied() && decision.reason.isRateLimit()) {
return new Response("AI usage limit exceeded", { status: 429 });
}
```
### 敏感信息检测
检测并请求内容中的 PII。通过 `sensitiveInfoValue` 在每次调用 `protect()` 时传递要扫描的内容。内置实体类型:`CREDIT_CARD_NUMBER`、`EMAIL`、`PHONE_NUMBER`、`IP_ADDRESS`。您也可以提供自定义 `detect` 回调以匹配其他模式。
```
import arcjet, { sensitiveInfo } from "@arcjet/next";
const aj = arcjet({
key: process.env.ARCJET_KEY!,
rules: [
sensitiveInfo({
mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only
deny: ["CREDIT_CARD_NUMBER", "EMAIL", "PHONE_NUMBER"],
}),
],
});
const decision = await aj.protect(request, {
sensitiveInfoValue: userMessage,
});
if (decision.isDenied() && decision.reason.isSensitiveInfo()) {
return new Response("Sensitive information detected", { status: 400 });
}
```
### 防护盾 WAF
保护您的应用程序免受常见 Web 攻击,包括 OWASP Top 10。
```
import arcjet, { shield } from "@arcjet/next";
const aj = arcjet({
key: process.env.ARCJET_KEY!,
rules: [
shield({
mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only
}),
],
});
```
### 邮箱验证
验证并核实电子邮件地址。拒绝类型:`DISPOSABLE`、`FREE`、`NO_MX_RECORDS`、`NO_GRAVATAR`、`INVALID`。
```
import arcjet, { validateEmail } from "@arcjet/next";
const aj = arcjet({
key: process.env.ARCJET_KEY!,
rules: [
validateEmail({
mode: "LIVE",
deny: ["DISPOSABLE", "INVALID", "NO_MX_RECORDS"],
}),
],
});
const decision = await aj.protect(request, {
email: "user@example.com",
});
if (decision.isDenied() && decision.reason.isEmail()) {
return new Response("Invalid email address", { status: 400 });
}
```
### 请求过滤
使用基于表达式的规则过滤请求属性(IP、头部、路径、方法等)。
#### 按国家/地区阻止
限制对特定国家/地区的访问——适用于许可、合规或区域发布。`allow` 列表会拒绝未列出的所有国家/地区:
```
filter({
mode: "LIVE",
// Allow only US traffic — all other countries are denied
allow: ['ip.src.country == "US"'],
});
```
#### 阻止 VPN 和代理流量
阻止匿名流量访问敏感端点——适用于欺诈预防、强制执行地理限制和减少滥用:
```
filter({
mode: "LIVE",
deny: [
"ip.src.vpn", // VPN services
"ip.src.proxy", // Open proxies
"ip.src.tor", // Tor exit nodes
],
});
```
对于更细粒度的处理,在调用 `protect()` 后使用 `decision.ip` 辅助函数:
```
const decision = await aj.protect(request);
if (decision.ip.isVpn() || decision.ip.isTor()) {
return new Response("VPN traffic not allowed", { status: 403 });
}
```
请参考[请求过滤文档][feature-filters]、[IP 地理定位蓝图][blueprint-ip-geolocation] 和 [VPN/代理检测蓝图][blueprint-vpn-proxy] 获取更多细节。
### IP 分析
Arcjet 为每个请求丰富 IP 元数据。使用这些辅助函数根据网络信号制定策略决策:
```
const decision = await aj.protect(request);
if (decision.ip.isHosting()) {
// Requests from cloud/hosting providers are often automated.
// https://docs.arcjet.com/blueprints/vpn-proxy-detection
return new Response("Forbidden", { status: 403 });
}
if (decision.ip.isVpn() || decision.ip.isProxy() || decision.ip.isTor()) {
// Handle VPN/proxy traffic according to your policy
}
// Access geolocation and network details
console.log(decision.ip.country, decision.ip.city, decision.ip.asn);
```
### 自定义特征
使用任意稳定标识符(用户 ID、API 密钥、会话等)而非仅 IP 地址来跟踪和限制请求。
```
const aj = arcjet({
key: process.env.ARCJET_KEY!,
characteristics: ["userId"], // Declare at the SDK level
rules: [
tokenBucket({
mode: "LIVE",
refillRate: 2_000,
interval: "1h",
capacity: 5_000,
}),
],
});
// Pass the characteristic value at request time
const decision = await aj.protect(request, {
userId: "user-123",
requested: estimate,
});
```
## Arcjet Guard
### 与框架 SDK 的区别
| 维度 | 框架 SDK(`@arcjet/next` 等) | `@arcjet/guard` |
|------|------------------------------|-----------------|
| **设计用途** | HTTP 请求保护 | AI 代理工具调用、后台任务 |
| **请求对象** | 必需(`protect(request, ...)`) | 不需要 |
| **规则绑定** | 规则配置一次,通过 `protect()` 选项输入 | 规则配置为函数,每次调用时传入输入 |
| **速率限制键** | IP 或 `characteristics` 字典 | 显式 `key` 字符串(SHA-256 哈希后发送) |
| **自定义规则** | 不支持 | `defineCustomRule`,支持类型化的配置/输入/数据 |
### 安装
```
npm i @arcjet/guard
```
### 快速开始
```
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
async function handleToolCall(
userId: string,
userMessage: string,
tokenCount: number,
) {
const rl = limitRule({ key: userId, requested: tokenCount });
const decision = await arcjet.guard({
label: "tools.weather",
rules: [rl, piRule(userMessage)],
});
if (decision.conclusion === "DENY") {
throw new Error(`Blocked: ${decision.reason}`);
}
// safe to proceed
}
```
### 速率限制(Guard)
令牌桶、固定窗口和滑动窗口算法均可用。
配置规则一次,然后针对每次调用传入 `key`(以及可选的 `requested` 令牌数)。
#### 令牌桶(Guard)
```
import { launchArcjet, tokenBucket } from "@arcjet/guard";
const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });
const limitRule = tokenBucket({
refillRate: 2_000, // tokens added per interval
intervalSeconds: 3600, // seconds between refills
maxTokens: 5_000, // maximum bucket capacity
});
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");
}
```
#### 固定窗口(Guard)
```
import { launchArcjet, fixedWindow } from "@arcjet/guard";
const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });
const limitRule = fixedWindow({
maxRequests: 1000, // maximum requests per window
windowSeconds: 3600, // 1-hour window
});
const decision = await arcjet.guard({
label: "api.search",
rules: [limitRule({ key: teamId })],
});
```
#### 滑动窗口(Guard)
```
import { launchArcjet, slidingWindow } from "@arcjet/guard";
const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });
const limitRule = slidingWindow({
maxRequests: 500, // maximum requests per interval
intervalSeconds: 60, // 1-minute rolling window
});
const decision = await arcjet.guard({
label: "api.events",
rules: [limitRule({ key: userId })],
});
```
### 提示注入检测(Guard)
```
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");
}
```
### 敏感信息检测(Guard)
在本地检测 PII —— 原始文本不会离开 SDK。内置实体类型:`EMAIL`、`PHONE_NUMBER`、`IP_ADDRESS`、`CREDIT_CARD_NUMBER`。
```
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");
}
```
### 自定义规则(Guard)
使用任意键值数据定义本地评估逻辑。当提供 `evaluate` 时,SDK 在本地调用它,然后再发送请求。
函数接收 `(config, input, { signal })` 并必须返回 `{ 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 } })],
});
```
### 每个规则的结果(Guard)
对已配置的规则和绑定的输入均可访问类型化的结果访问器:
```
const limitRule = tokenBucket({
refillRate: 10,
intervalSeconds: 60,
maxTokens: 100,
});
const rl = limitRule({ key: userId, requested: 5 });
const decision = await arcjet.guard({ label: "tools.weather", rules: [rl] });
// From the bound input (matches exact invocation)
const r = rl.result(decision);
if (r) {
console.log(r.remainingTokens, r.maxTokens);
}
// From the configured rule (matches all invocations of this rule)
const ruleResult = limitRule.result(decision);
// Check only denied results
const denied = rl.deniedResult(decision);
if (denied) {
console.log(`Rate limited — resets at ${denied.resetAtUnixSeconds}`);
}
```
`RuleWithConfig` 和 `RuleWithInput` 上的方法:
| 方法 | `RuleWithConfig`(例如 `limitRule`) | `RuleWithInput`(例如 `rl`) |
|------|-------------------------------------|------------------------------|
| `results(decision)` | 该配置的所有结果 | 单元素或空数组 |
| `result(decision)` | 第一个结果(任意结论) | 本次提交的结果 |
| `deniedResult(decision)` | 第一个被拒绝的结果 | 被拒绝时的结果 |
### 决策 API(Guard)
```
const decision = await arcjet.guard({ label: "tools.weather", rules: [...] });
// Layer 1: conclusion and reason
decision.conclusion; // "ALLOW" or "DENY"
decision.reason; // "RATE_LIMIT", "PROMPT_INJECTION", "SENSITIVE_INFO", "CUSTOM", "ERROR", etc.
// Layer 2: error detection
decision.hasError(); // true if any rule errored or the server reported diagnostics
// Layer 3: per-rule results (see "Per-rule results" above)
for (const result of decision.results) {
console.log(result.type, result.conclusion);
}
```
### `guard()` 参数参考
| 参数 | 类型 | 描述 |
|------|------|------|
| `rules` | `RuleWithInput[]` | 绑定的规则输入(必需) |
| `label` | `string` | 标识本次 Guard 调用的标签(必需) |
| `metadata` | `Record
\| undefined` | 可选键值元数据 |
### DRY_RUN 模式
所有 Guard 规则均接受 `mode` 参数。使用 `"DRY_RUN"` 可在不阻断的情况下评估规则:
```
const limitRule = tokenBucket({
mode: "DRY_RUN",
refillRate: 10,
intervalSeconds: 60,
maxTokens: 100,
});
```
## 最佳实践
请参考[Arcjet 最佳实践][best-practices]获取详细说明。关键建议:
**创建单个客户端实例** 并在整个应用中使用 `withRule()` 附加路由特定规则。SDK 会缓存决策和配置,因此为每个请求创建新实例会浪费计算资源。
```
// lib/arcjet.ts — create once, import everywhere
import arcjet, { shield } from "@arcjet/next";
// Replace @arcjet/next with @arcjet/node, @arcjet/bun, etc. for your runtime
export default arcjet({
key: process.env.ARCJET_KEY!,
rules: [
shield({ mode: "LIVE" }), // base rules applied to every request
],
});
```
```
// app/api/chat/route.ts — extend per-route with withRule()
import aj from "@/lib/arcjet";
import { detectBot, tokenBucket } from "@arcjet/next";
const routeAj = aj.withRule(detectBot({ mode: "LIVE", allow: [] })).withRule(
tokenBucket({
mode: "LIVE",
refillRate: 2_000,
interval: "1h",
capacity: 5_000,
}),
);
export async function POST(req: Request) {
const decision = await routeAj.protect(req, { requested: 500 });
// ...
}
```
**其他建议:**
- **在路由处理程序中调用 `protect()`,而非中间件。** 中间件缺乏路由上下文,难以应用路由特定规则或自定义响应。
- **对每个请求只调用 `protect()` 一次。** 在中间件和处理程序中都调用会加倍计算并可能导致意外结果。
- **以 `DRY_RUN` 模式启动规则**,以便在切换至 `LIVE` 模式前观察行为。这允许你在不影响真实流量的情况下调整阈值。
- **如果应用运行在负载均衡器或反向代理后方,请配置代理**,使 Arcjet 能解析真实的客户端 IP:
arcjet({
key: process.env.ARCJET_KEY!,
rules: [],
proxies: ["100.100.100.100"],
});
- **显式处理错误。** `protect()` 永不抛出错误;出错时返回 `ERROR` 结果。故障开放策略:记录并允许请求继续:
if (decision.isErrored()) {
console.error("Arcjet error", decision.reason.message);
// 允许请求继续
}
## 软件包
我们在此仓库中提供多个软件包的源代码,可通过以下分类和描述查找特定软件包。
### SDK
- [`@arcjet/astro`](./arcjet-astro/README.md): Astro 的 SDK。
- [`@arcjet/bun`](./arcjet-bun/README.md): Bun 的 SDK。
- [`@arcjet/deno`](./arcjet-deno/README.md): Deno 的 SDK。
- [`@arcjet/fastify`](./arcjet-fastify/README.md): Fastify 的 SDK。
- [`@arcjet/guard`](./arcjet-guard/README.md): 用于 AI 代理工具调用和后台任务的 Guard SDK。
- [`@arcjet/nest`](./arcjet-nest/README.md): NestJS 的 SDK。
- [`@arcjet/next`](./arcjet-next/README.md): Next.js 的 SDK。
- [`@arcjet/node`](./arcjet-node/README.md): Node.js 的 SDK。
- [`@arcjet/nuxt`](./arcjet-nuxt/README.md): Nuxt 的 SDK。
- [`@arcjet/react-router`](./arcjet-react-router/README.md): React Router 的 SDK。
- [`@arcjet/remix`](./arcjet-remix/README.md): Remix 的 SDK。
- [`@arcjet/sveltekit`](./arcjet-sveltekit/README.md): SvelteKit 的 SDK。
### Nosecone
详见[文档][nosecone-docs]。
- [`@nosecone/next`](./nosecone-next/README.md): 使用安全标头保护 Next.js 应用。
- [`@nosecone/sveltekit`](./nosecone-sveltekit/README.md): 使用标头保护 SvelteKit 应用。
- [`nosecone`](./nosecone/README.md): 使用安全标头保护 `Response`。
### 工具
- [`@arcjet/analyze`](./analyze/README.md): 本地分析引擎。
- [`@arcjet/body`](./body/README.md): 从流中提取请求体。
- [`@arcjet/cache`](./cache/README.md): 基础缓存接口及实现。
- [`@arcjet/decorate`](./decorate/README.md): 为响应添加信息。
- [`@arcjet/duration`](./duration/README.md): 解析持续时间字符串。
- [`@arcjet/env`](./env/README.md): 环境检测。
- [`@arcjet/headers`](./headers/README.md): Headers 类的扩展。
- [`@arcjet/inspect`](./inspect/README.md): 检查 SDK 做出的决策。
- [`@arcjet/ip`](./ip/README.md): 查找请求的源 IP。
- [`@arcjet/logger`](./logger/README.md): 模拟 Pino 结构化日志接口的轻量级日志记录器。
- [`@arcjet/protocol`](./protocol/README.md): Arcjet 协议到 JS 的接口。
- [`@arcjet/redact`](./redact/README.md): 对字符串脱敏和去脱敏敏感信息。
- [`@arcjet/runtime`](./runtime/README.md): 运行时检测。
- [`@arcjet/sprintf`](./sprintf/README.md): 与平台无关的 `util.format` 替代实现。
- [`@arcjet/stable-hash`](./stable-hash/README.md): 稳定哈希。
- [`@arcjet/transport`](./transport/README.md): Arcjet 协议的数据传输机制。
- [`arcjet`](./arcjet/README.md): JS SDK 核心。
### 内部开发
- [`@arcjet/eslint-config`](./eslint-config/README.md): 我们项目的自定义 ESLint 配置。
- [`@arcjet/rollup-config`](./rollup-config/README.md): 我们项目的自定义 Rollup 配置。
## 支持
本仓库遵循 [Arcjet 支持策略][arcjet-support]。
## 安全性
本仓库遵循 [Arcjet 安全策略][arcjet-security]。
## 开发
这是一个由 [npm workspaces](https://npm.workspaces) 和 [Turborepo](https.turborepo.org) 管理的 monorepo。每个软件包都位于其自己的目录中(例如 `arcjet-next/`、`analyze/`)。
如果您想使用 Arcjet,则应安装适用于特定运行时的软件包(例如 `@arcjet/next` 适用于 Next.js)。如果您想为 SDK 开发做出贡献,请参阅 [CONTRIBUTING.md](./CONTRIBUTING.md)。
## 兼容性
本仓库维护的软件包与 LTS 版本的 Node.js 和 TypeScript 的当前次要版本兼容。
## 许可证
根据 [Apache License, Version 2.0][apache-license] 授权。