+21
-14
| { | ||
| "name": "arcjet", | ||
| "version": "1.3.0", | ||
| "description": "Arcjet TypeScript and JavaScript SDK core", | ||
| "version": "1.3.1", | ||
| "description": "Arcjet runtime security SDK — bot protection, rate limiting, prompt injection detection, PII blocking, and WAF for JavaScript and TypeScript apps", | ||
| "keywords": [ | ||
| "ai", | ||
| "analyze", | ||
| "arcjet", | ||
| "attack", | ||
| "bot-detection", | ||
| "limit", | ||
| "llm", | ||
| "pii", | ||
| "prompt-injection", | ||
| "protect", | ||
| "rate-limiting", | ||
| "secure", | ||
| "security", | ||
| "verify" | ||
| "verify", | ||
| "waf" | ||
| ], | ||
@@ -50,16 +57,16 @@ "license": "Apache-2.0", | ||
| "dependencies": { | ||
| "@arcjet/analyze": "1.3.0", | ||
| "@arcjet/cache": "1.3.0", | ||
| "@arcjet/duration": "1.3.0", | ||
| "@arcjet/headers": "1.3.0", | ||
| "@arcjet/protocol": "1.3.0", | ||
| "@arcjet/runtime": "1.3.0", | ||
| "@arcjet/stable-hash": "1.3.0" | ||
| "@arcjet/analyze": "1.3.1", | ||
| "@arcjet/cache": "1.3.1", | ||
| "@arcjet/duration": "1.3.1", | ||
| "@arcjet/headers": "1.3.1", | ||
| "@arcjet/protocol": "1.3.1", | ||
| "@arcjet/runtime": "1.3.1", | ||
| "@arcjet/stable-hash": "1.3.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@arcjet/eslint-config": "1.3.0", | ||
| "@arcjet/rollup-config": "1.3.0", | ||
| "@rollup/wasm-node": "4.57.1", | ||
| "@arcjet/eslint-config": "1.3.1", | ||
| "@arcjet/rollup-config": "1.3.1", | ||
| "@rollup/wasm-node": "4.59.0", | ||
| "@types/node": "24.11.0", | ||
| "eslint": "9.39.2", | ||
| "eslint": "9.39.3", | ||
| "typescript": "5.9.3" | ||
@@ -66,0 +73,0 @@ }, |
+450
-34
@@ -19,41 +19,405 @@ <a href="https://arcjet.com" target="_arcjet-home"> | ||
| [Arcjet][arcjet] helps developers protect their apps in just a few lines of | ||
| code. Implement rate limiting, bot protection, email verification, and defense | ||
| against common attacks. | ||
| [Arcjet][arcjet] is the runtime security platform that ships with your AI code. Stop bots and automated attacks from burning your AI budget, leaking data, or misusing tools with Arcjet's AI security building blocks. | ||
| This is the [Arcjet][arcjet] TypeScript and JavaScript SDK core. | ||
| This is the [Arcjet][arcjet] TypeScript and JavaScript SDK core. **Most users | ||
| should install a framework SDK instead** (`@arcjet/next`, `@arcjet/node`, | ||
| `@arcjet/bun`, etc.) — see the [framework SDKs][github-arcjet-sdks]. Use this | ||
| package directly only if you are building a custom adapter for a runtime not | ||
| yet supported. Every feature works with any JavaScript application. | ||
| - [npm package (`arcjet`)](https://www.npmjs.com/package/arcjet) | ||
| - [GitHub source code (`arcjet/` in `arcjet/arcjet-js`)](https://github.com/arcjet/arcjet-js/tree/main/arcjet) | ||
| [npm package](https://www.npmjs.com/package/arcjet) | | ||
| [GitHub source](https://github.com/arcjet/arcjet-js/tree/main/arcjet) | | ||
| [Full docs][ts-sdk-docs] | ||
| ## Getting started | ||
| ## Rules | ||
| Visit [docs.arcjet.com](https://docs.arcjet.com) to get started. | ||
| The `arcjet` core exports the following protection rules. Each rule is passed | ||
| in the `rules` array when configuring the client. | ||
| ## What is this? | ||
| ### `shield(options)` | ||
| This is our core package. | ||
| It exposes the functionality for the many types of protection that Arcjet | ||
| provides which can be configured and combined by users. | ||
| The functionality here is exposed from our [SDKs][github-arcjet-sdks] | ||
| (such as `@arcjet/next`) that each integrate with a particular framework. | ||
| Protects your application against common web attacks, including the OWASP | ||
| Top 10. | ||
| ## When should I use this? | ||
| ```ts | ||
| import arcjet, { shield } from "arcjet"; | ||
| We recommend using one of our runtime or framework specific packages rather | ||
| than this one. | ||
| See our [_Get started_ guide][arcjet-get-started] for more info. | ||
| const aj = arcjet({ | ||
| // ... | ||
| rules: [ | ||
| shield({ | ||
| mode: "LIVE", // "LIVE" blocks requests | "DRY_RUN" logs only | ||
| }), | ||
| ], | ||
| }); | ||
| ``` | ||
| ## Install | ||
| ### `detectBot(options)` | ||
| This package is ESM only. | ||
| Install with npm in Node.js: | ||
| Detects and blocks automated clients and bots. Configure `allow` to permit | ||
| specific bot categories or named bots; all others are denied. | ||
| ```sh | ||
| npm install arcjet | ||
| Available categories: `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`. | ||
| ```ts | ||
| import arcjet, { detectBot } from "arcjet"; | ||
| const aj = arcjet({ | ||
| // ... | ||
| rules: [ | ||
| detectBot({ | ||
| mode: "LIVE", | ||
| allow: [ | ||
| "CATEGORY:SEARCH_ENGINE", // Google, Bing, etc | ||
| // "CATEGORY:MONITOR", // Uptime monitoring services | ||
| // "CATEGORY:PREVIEW", // Link previews e.g. Slack, Discord | ||
| // "OPENAI_CRAWLER_SEARCH", | ||
| // See the full list at https://arcjet.com/bot-list | ||
| ], | ||
| }), | ||
| ], | ||
| }); | ||
| ``` | ||
| ## Use | ||
| ### `tokenBucket(options)` | ||
| Token bucket rate limit. Ideal for controlling AI token budgets. Set `capacity` | ||
| to the max tokens a user can spend, `refillRate` to how many tokens are | ||
| restored per `interval`, and deduct tokens per request via `requested` in | ||
| `protect()`. The `interval` accepts strings (`"1s"`, `"1m"`, `"1h"`, `"1d"`) | ||
| or seconds as a number. | ||
| ```ts | ||
| import arcjet, { tokenBucket } from "arcjet"; | ||
| const aj = arcjet({ | ||
| // ... | ||
| characteristics: ["userId"], // Track per user (or "ip.src" for IP-based) | ||
| rules: [ | ||
| tokenBucket({ | ||
| mode: "LIVE", | ||
| refillRate: 2_000, // Tokens added per interval | ||
| interval: "1h", // Refill interval (supports "s", "m", "h", "d" or seconds) | ||
| capacity: 5_000, // Maximum bucket size | ||
| }), | ||
| ], | ||
| }); | ||
| // Deduct tokens at request time: | ||
| const decision = await aj.protect(context, { | ||
| userId: "user-123", | ||
| requested: 500, // Tokens to deduct | ||
| }); | ||
| ``` | ||
| ### `slidingWindow(options)` | ||
| Sliding window rate limit. Smoothly limits request rates over a rolling time | ||
| window. | ||
| ```ts | ||
| import arcjet, { slidingWindow } from "arcjet"; | ||
| const aj = arcjet({ | ||
| // ... | ||
| rules: [ | ||
| slidingWindow({ | ||
| mode: "LIVE", | ||
| interval: 60, // Window size in seconds | ||
| max: 100, // Maximum requests per window | ||
| }), | ||
| ], | ||
| }); | ||
| ``` | ||
| ### `fixedWindow(options)` | ||
| Fixed window rate limit. Resets the counter at the start of each window. | ||
| ```ts | ||
| import arcjet, { fixedWindow } from "arcjet"; | ||
| const aj = arcjet({ | ||
| // ... | ||
| rules: [ | ||
| fixedWindow({ | ||
| mode: "LIVE", | ||
| window: "1m", // Window duration | ||
| max: 100, // Maximum requests per window | ||
| }), | ||
| ], | ||
| }); | ||
| ``` | ||
| ### `detectPromptInjection(options)` | ||
| Detects prompt injection attacks — attempts to override your AI model's | ||
| instructions via user input. Pass the user's message via | ||
| `detectPromptInjectionMessage` on each `protect()` call. | ||
| ```ts | ||
| import arcjet, { detectPromptInjection } from "arcjet"; | ||
| const aj = arcjet({ | ||
| // ... | ||
| rules: [ | ||
| detectPromptInjection({ | ||
| mode: "LIVE", // "LIVE" blocks | "DRY_RUN" logs only | ||
| threshold: 0.5, // Score above which requests are blocked (default: 0.5) | ||
| // Must be in range (0.0, 1.0) exclusive | ||
| }), | ||
| ], | ||
| }); | ||
| const decision = await aj.protect(context, { | ||
| detectPromptInjectionMessage: userMessage, | ||
| }); | ||
| if (decision.isDenied() && decision.reason.isPromptInjection()) { | ||
| // Block the request | ||
| } | ||
| ``` | ||
| ### `sensitiveInfo(options)` | ||
| Detects and blocks sensitive information (PII) in request content. Pass the | ||
| content to scan via `sensitiveInfoValue` on each `protect()` call. Built-in | ||
| entity types: `CREDIT_CARD_NUMBER`, `EMAIL`, `PHONE_NUMBER`, `IP_ADDRESS`. | ||
| You can also provide a custom `detect` callback for additional patterns. | ||
| ```ts | ||
| import arcjet, { sensitiveInfo } from "arcjet"; | ||
| const aj = arcjet({ | ||
| // ... | ||
| rules: [ | ||
| sensitiveInfo({ | ||
| mode: "LIVE", | ||
| deny: [ | ||
| "CREDIT_CARD_NUMBER", | ||
| "EMAIL", | ||
| "PHONE_NUMBER", | ||
| // See https://docs.arcjet.com/sensitive-info/reference for all types | ||
| ], | ||
| }), | ||
| ], | ||
| }); | ||
| const decision = await aj.protect(context, { | ||
| sensitiveInfoValue: userMessage, | ||
| }); | ||
| ``` | ||
| ### `validateEmail(options)` | ||
| Validates and verifies email addresses. Deny types: `DISPOSABLE`, `FREE`, | ||
| `NO_MX_RECORDS`, `NO_GRAVATAR`, `INVALID`. | ||
| ```ts | ||
| import arcjet, { validateEmail } from "arcjet"; | ||
| const aj = arcjet({ | ||
| // ... | ||
| rules: [ | ||
| validateEmail({ | ||
| mode: "LIVE", | ||
| deny: ["DISPOSABLE", "INVALID", "NO_MX_RECORDS"], | ||
| }), | ||
| ], | ||
| }); | ||
| const decision = await aj.protect(context, { | ||
| email: "user@example.com", | ||
| }); | ||
| ``` | ||
| ### `filter(options)` | ||
| Filters requests using expression-based rules against request properties (IP, | ||
| headers, path, method, etc.). | ||
| ```ts | ||
| import arcjet, { filter } from "arcjet"; | ||
| const aj = arcjet({ | ||
| // ... | ||
| rules: [ | ||
| filter({ | ||
| mode: "LIVE", | ||
| deny: [ | ||
| 'ip.src == "1.2.3.4"', | ||
| 'http.request.uri.path contains "/admin"', | ||
| // See https://docs.arcjet.com/filters/reference#expression-language | ||
| ], | ||
| }), | ||
| ], | ||
| }); | ||
| ``` | ||
| #### Block by country | ||
| Restrict access to specific countries — useful for licensing, compliance, or | ||
| regional rollouts. The `allow` list denies all countries not listed: | ||
| ```ts | ||
| filter({ | ||
| mode: "LIVE", | ||
| // Allow only US traffic — all other countries are denied | ||
| allow: ['ip.src.country == "US"'], | ||
| }); | ||
| ``` | ||
| #### Block VPN and proxy traffic | ||
| Prevent anonymized traffic from accessing sensitive endpoints — useful for | ||
| fraud prevention, enforcing geo-restrictions, and reducing abuse: | ||
| ```ts | ||
| filter({ | ||
| mode: "LIVE", | ||
| deny: [ | ||
| "ip.src.vpn", // VPN services | ||
| "ip.src.proxy", // Open proxies | ||
| "ip.src.tor", // Tor exit nodes | ||
| ], | ||
| }); | ||
| ``` | ||
| For more nuanced handling, use `decision.ip` helpers after calling `protect()`: | ||
| ```ts | ||
| const decision = await aj.protect(context, {}); | ||
| if (decision.ip.isVpn() || decision.ip.isTor()) { | ||
| // Block VPN traffic | ||
| } | ||
| ``` | ||
| See the [filter expression reference][filter-reference] for the full list of | ||
| supported fields and operators. | ||
| ### `protectSignup(options)` | ||
| Combines bot protection, email validation, and rate limiting in a single rule, | ||
| optimized for protecting signup and lead capture forms. | ||
| ```ts | ||
| import arcjet, { protectSignup } from "arcjet"; | ||
| const aj = arcjet({ | ||
| // ... | ||
| rules: [ | ||
| protectSignup({ | ||
| rateLimit: { | ||
| mode: "LIVE", | ||
| interval: "10m", | ||
| max: 5, | ||
| }, | ||
| bots: { | ||
| mode: "LIVE", | ||
| }, | ||
| email: { | ||
| mode: "LIVE", | ||
| deny: ["DISPOSABLE", "INVALID", "NO_MX_RECORDS"], | ||
| }, | ||
| }), | ||
| ], | ||
| }); | ||
| const decision = await aj.protect(context, { | ||
| email: "user@example.com", | ||
| }); | ||
| ``` | ||
| ## Inspecting decisions | ||
| The `decision` object returned by `aj.protect()` provides information about | ||
| why a request was allowed or denied. | ||
| ```ts | ||
| const decision = await aj.protect(context, props); | ||
| // Top-level verdict | ||
| decision.isAllowed(); // true if the request should proceed | ||
| decision.isDenied(); // true if the request should be blocked | ||
| decision.isErrored(); // true if an error occurred evaluating rules | ||
| // Reason for the decision | ||
| decision.reason.isBot(); // detectBot rule triggered | ||
| decision.reason.isRateLimit(); // rate limit rule triggered | ||
| decision.reason.isShield(); // shield rule triggered | ||
| decision.reason.isSensitiveInfo(); // sensitiveInfo rule triggered | ||
| decision.reason.isPromptInjection(); // detectPromptInjection rule triggered | ||
| decision.reason.isEmail(); // validateEmail rule triggered | ||
| // IP intelligence | ||
| decision.ip.isHosting(); // Cloud/hosting provider IP | ||
| decision.ip.isVpn(); // VPN IP | ||
| decision.ip.isProxy(); // Proxy IP | ||
| decision.ip.isTor(); // Tor exit node IP | ||
| decision.ip.country; // ISO 3166-1 alpha-2 country code | ||
| decision.ip.city; // City name | ||
| decision.ip.asn; // Autonomous system number | ||
| // Per-rule results (array, one entry per rule) | ||
| decision.results; // ArcjetRuleResult[] | ||
| ``` | ||
| ### Bot verification details | ||
| ```ts | ||
| import { isSpoofedBot } from "@arcjet/inspect"; | ||
| // Check if any result is from a bot claiming to be a known crawler but failing | ||
| // IP verification: | ||
| if (decision.results.some(isSpoofedBot)) { | ||
| // Block spoofed bot | ||
| } | ||
| // Inspect individual bot rule results: | ||
| for (const result of decision.results) { | ||
| if (result.reason.isBot()) { | ||
| console.log("Bot type:", result.reason.botType); | ||
| console.log("Bot name:", result.reason.botName); | ||
| console.log("Verified:", result.reason.verified); | ||
| console.log("Spoofed:", result.reason.spoofed); | ||
| } | ||
| } | ||
| ``` | ||
| ## IP analysis | ||
| Arcjet enriches every request with IP metadata. Use these helpers to make | ||
| policy decisions based on network signals: | ||
| ```ts | ||
| const decision = await aj.protect(context, {}); | ||
| if (decision.ip.isHosting()) { | ||
| // Requests from cloud/hosting providers are often automated. | ||
| // https://docs.arcjet.com/blueprints/vpn-proxy-detection | ||
| } | ||
| 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); | ||
| ``` | ||
| ## Custom adapter example | ||
| The following shows the minimal adapter interface required to build a custom | ||
| integration. Framework adapters take care of this automatically; this example | ||
| illustrates the low-level API. | ||
| ```ts | ||
| import http from "node:http"; | ||
@@ -63,4 +427,2 @@ import { readBody } from "@arcjet/body"; | ||
| // Get your Arcjet key at <https://app.arcjet.com>. | ||
| // Set it as an environment variable instead of hard coding it. | ||
| const arcjetKey = process.env.ARCJET_KEY; | ||
@@ -86,7 +448,3 @@ | ||
| log: console, | ||
| rules: [ | ||
| // Shield protects your app from common attacks. | ||
| // Use `DRY_RUN` instead of `LIVE` to only log. | ||
| shield({ mode: "LIVE" }), | ||
| ], | ||
| rules: [shield({ mode: "LIVE" })], | ||
| }); | ||
@@ -112,4 +470,2 @@ | ||
| console.log(decision); | ||
| if (decision.isDenied()) { | ||
@@ -128,2 +484,61 @@ response.writeHead(403, { "Content-Type": "application/json" }); | ||
| ## Best practices | ||
| See the [Arcjet best practices][best-practices] for detailed guidance. Key | ||
| recommendations: | ||
| **Create a single client instance** and reuse it with `withRule()` for | ||
| route-specific rules. The SDK caches decisions and configuration, so creating a | ||
| new instance per request wastes that work. | ||
| ```ts | ||
| // lib/arcjet.ts — create once, import everywhere | ||
| import arcjet, { shield } from "@arcjet/node"; // or @arcjet/next, @arcjet/bun, etc. | ||
| export default arcjet({ | ||
| key: process.env.ARCJET_KEY!, | ||
| rules: [ | ||
| shield({ mode: "LIVE" }), // base rules applied to every request | ||
| ], | ||
| }); | ||
| ``` | ||
| ```ts | ||
| // routes/chat.ts — extend per-route with withRule() | ||
| import aj from "./lib/arcjet.js"; | ||
| import { detectBot, tokenBucket } from "@arcjet/node"; | ||
| const routeAj = aj.withRule(detectBot({ mode: "LIVE", allow: [] })).withRule( | ||
| tokenBucket({ | ||
| mode: "LIVE", | ||
| refillRate: 2_000, | ||
| interval: "1h", | ||
| capacity: 5_000, | ||
| }), | ||
| ); | ||
| ``` | ||
| **Other recommendations:** | ||
| - **Call `protect()` once per request** in the handler where you need it. | ||
| - **Start rules in `DRY_RUN` mode** to observe behavior before switching to | ||
| `LIVE`. This lets you tune thresholds without affecting real traffic. | ||
| - **Configure proxies** if your app runs behind a load balancer or reverse proxy | ||
| so Arcjet resolves the real client IP: | ||
| ```ts | ||
| arcjet({ | ||
| key: process.env.ARCJET_KEY!, | ||
| rules: [], | ||
| proxies: ["100.100.100.100"], | ||
| }); | ||
| ``` | ||
| - **Handle errors explicitly.** `protect()` never throws — on error it returns | ||
| an `ERROR` result. Fail open by logging and allowing the request: | ||
| ```ts | ||
| if (decision.isErrored()) { | ||
| console.error("Arcjet error", decision.reason.message); | ||
| // allow the request to proceed | ||
| } | ||
| ``` | ||
| ## API | ||
@@ -140,3 +555,4 @@ | ||
| [apache-license]: http://www.apache.org/licenses/LICENSE-2.0 | ||
| [arcjet-get-started]: https://docs.arcjet.com/get-started | ||
| [github-arcjet-sdks]: https://github.com/arcjet/arcjet-js#sdks | ||
| [best-practices]: https://docs.arcjet.com/best-practices | ||
| [filter-reference]: https://docs.arcjet.com/filters/reference#expression-language |
148252
8.14%553
303.65%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated
Updated
Updated
Updated