Skip to content

Commit cccab80

Browse files
committed
chore: optimize skills
1 parent 52928e0 commit cccab80

3 files changed

Lines changed: 114 additions & 55 deletions

File tree

CONTRIBUTING.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,14 @@ Both must pass before merging.
126126
- Test skills by running them in an AI coding agent against a real project e.g. `claude --plugin-dir ./arcjet-plugin`
127127
- Start new protection rules in `DRY_RUN` mode guidance — never suggest `LIVE` as a default
128128

129+
## Skill Writing References
130+
131+
When creating or improving skills, follow the guidance at [agentskills.io](https://agentskills.io):
132+
133+
- [Best practices](https://agentskills.io/skill-creation/best-practices) — scoping, context efficiency, gotchas sections, code templates, and calibrating control
134+
- [Optimizing descriptions](https://agentskills.io/skill-creation/optimizing-descriptions) — writing descriptions that trigger reliably on relevant prompts
135+
- [Evaluating skills](https://agentskills.io/skill-creation/evaluating-skills) — test cases, grading, and iterating on output quality
136+
129137
## License
130138

131139
By contributing, you agree that your contributions will be licensed under the [Apache License 2.0](LICENSE).

skills/add-ai-protection/SKILL.md

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
name: add-ai-protection
33
license: Apache-2.0
4-
description: Add AI-specific security to LLM/chat endpoints prompt injection detection, PII/sensitive info blocking, and token budget rate limiting. Use when building AI chat interfaces, completion APIs, or any endpoint that processes user prompts.
4+
description: Protect AI chat and completion endpoints from abuse — detect prompt injection and jailbreak attempts, block PII and sensitive info from leaking in responses, and enforce token budget rate limits to control costs. Use this skill when the user is building or securing any endpoint that processes user prompts with an LLM, even if they describe it as "preventing jailbreaks," "stopping prompt attacks," "blocking sensitive data," or "controlling AI API costs" rather than naming specific protections.
55
metadata:
66
pathPatterns:
77
- "app/api/chat/**"
@@ -42,16 +42,7 @@ Secure AI/LLM endpoints with layered protection: prompt injection detection, PII
4242

4343
Read https://docs.arcjet.com/llms.txt for comprehensive SDK documentation covering all frameworks, rule types, and configuration options.
4444

45-
## Why AI Endpoints Need Special Protection
46-
47-
AI endpoints are high-value targets:
48-
49-
- **Prompt injection** — attackers try to override system prompts, extract training data, or bypass safety rails
50-
- **PII leakage** — users may paste sensitive data (credit cards, emails, phone numbers) that gets stored in model context or logs
51-
- **Cost abuse** — each request consumes expensive model tokens; without rate limiting, a single user can exhaust your budget
52-
- **Automated scraping** — bots can scrape your AI endpoints for data or to find vulnerabilities.
53-
54-
Arcjet addresses all of these with rules that run **before** the request reaches your AI model.
45+
Arcjet rules run **before** the request reaches your AI model — blocking prompt injection, PII leakage, cost abuse, and bot scraping at the HTTP layer.
5546

5647
## Step 1: Ensure Arcjet Is Set Up
5748

@@ -77,13 +68,11 @@ Prevents personally identifiable information from entering model context.
7768
- JS: `sensitiveInfo({ deny: ["EMAIL", "CREDIT_CARD_NUMBER", "PHONE_NUMBER", "IP_ADDRESS"] })`
7869
- Python: `detect_sensitive_info(deny=[SensitiveInfoType.EMAIL, SensitiveInfoType.CREDIT_CARD_NUMBER, ...])`
7970

80-
Detection runs **locally in WASM** — no user data is sent to external services. Only available in route handlers (not pages or server actions in Next.js).
81-
8271
Pass the user message via `sensitiveInfoValue` (JS) / `sensitive_info_value` (Python) at `protect()` time.
8372

8473
### Token Budget Rate Limiting
8574

86-
Use `tokenBucket()` / `token_bucket()` for AI endpoints — it allows short bursts while enforcing an average rate, which matches how users interact with chat interfaces.
75+
Use `tokenBucket()` / `token_bucket()` for AI endpoints — the `requested` parameter can be set proportional to actual model token usage, directly linking rate limiting to cost. It also allows short bursts while enforcing an average rate, which matches how users interact with chat interfaces.
8776

8877
Recommended starting configuration:
8978

@@ -99,24 +88,55 @@ Set `characteristics` to track per-user: `["userId"]` if authenticated, defaults
9988

10089
Always include `shield()` (WAF) and `detectBot()` as base layers. Bots scraping AI endpoints are a common abuse vector. For endpoints accessed via browsers (e.g. chat interfaces), consider adding Arcjet advanced signals for client-side bot detection that catches sophisticated headless browsers. See https://docs.arcjet.com/bot-protection/advanced-signals for setup.
10190

102-
## Step 3: Compose the protect() Call
103-
104-
All rule parameters are passed together in a single `protect()` call. The key parameters for AI:
105-
106-
- `requested` — tokens to deduct for rate limiting
107-
- `sensitiveInfoValue` / `sensitive_info_value` — the user's message text for PII scanning
108-
- `detectPromptInjectionMessage` / `detect_prompt_injection_message` — the user's message text for injection detection
109-
110-
Typically `sensitiveInfoValue` and `detectPromptInjectionMessage` are set to the same value: the user's input message.
111-
112-
## Step 4: Handle Decisions
113-
114-
For AI endpoints, provide meaningful error responses:
115-
116-
- **Rate limited** (`reason.isRateLimit()`) → 429 with message like "You've exceeded your usage limit. Please try again later."
117-
- **Prompt injection** (`reason.isPromptInjection()`) → 400 with "Your message was flagged as potentially harmful."
118-
- **Sensitive info** (`reason.isSensitiveInfo()`) → 400 with "Your message contains sensitive information that cannot be processed. Please remove any personal data."
119-
- **Bot detected** (`reason.isBot()`) → 403
91+
## Step 3: Compose the protect() Call and Handle Decisions
92+
93+
All rule parameters are passed together in a single `protect()` call. Use this pattern:
94+
95+
```typescript
96+
const userMessage = req.body.message; // the user's input
97+
98+
const decision = await aj.protect(req, {
99+
requested: 1, // tokens to deduct for rate limiting
100+
sensitiveInfoValue: userMessage, // PII scanning
101+
detectPromptInjectionMessage: userMessage, // injection detection
102+
});
103+
104+
if (decision.isDenied()) {
105+
if (decision.reason.isRateLimit()) {
106+
return Response.json(
107+
{ error: "You've exceeded your usage limit. Please try again later." },
108+
{ status: 429 },
109+
);
110+
}
111+
if (decision.reason.isPromptInjection()) {
112+
return Response.json(
113+
{ error: "Your message was flagged as potentially harmful." },
114+
{ status: 400 },
115+
);
116+
}
117+
if (decision.reason.isSensitiveInfo()) {
118+
return Response.json(
119+
{
120+
error:
121+
"Your message contains sensitive information that cannot be processed. Please remove any personal data.",
122+
},
123+
{ status: 400 },
124+
);
125+
}
126+
if (decision.reason.isBot()) {
127+
return Response.json({ error: "Forbidden" }, { status: 403 });
128+
}
129+
}
130+
131+
// Arcjet fails open — log errors but allow the request
132+
if (decision.isErrored()) {
133+
console.warn("Arcjet error:", decision.reason.message);
134+
}
135+
136+
// Proceed with AI model call...
137+
```
138+
139+
Adapt the response format to your framework (e.g., `res.status(429).json(...)` for Express).
120140

121141
## Step 5: Verify
122142

@@ -144,3 +164,11 @@ The Arcjet dashboard at https://app.arcjet.com is also available for visual insp
144164
**Multiple models / providers**: Use the same Arcjet instance regardless of which AI provider you use. Arcjet operates at the HTTP layer, independent of the model provider.
145165

146166
**Vercel AI SDK**: Arcjet works alongside the Vercel AI SDK. Call `protect()` before `streamText()` / `generateText()`. If denied, return a plain error response instead of calling the AI SDK.
167+
168+
## Common Mistakes to Avoid
169+
170+
- Sensitive info detection runs **locally in WASM** — no user data is sent to external services. It is only available in route handlers, not in Next.js pages or server actions.
171+
- `sensitiveInfoValue` and `detectPromptInjectionMessage` (JS) / `sensitive_info_value` and `detect_prompt_injection_message` (Python) must both be passed at `protect()` time — forgetting either silently skips that check.
172+
- Starting a stream before calling `protect()` — if the request is denied mid-stream, the client gets a broken response. Always call `protect()` first and return an error before opening the stream.
173+
- Using `fixedWindow()` or `slidingWindow()` instead of `tokenBucket()` for AI endpoints — token bucket lets you deduct tokens proportional to model cost and matches the bursty interaction pattern of chat interfaces.
174+
- Creating a new Arcjet instance per request instead of reusing the shared client with `withRule()`.

skills/protect-route/SKILL.md

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
name: protect-route
33
license: Apache-2.0
4-
description: Add Arcjet security protection to a route handler. Detects framework, imports the shared client, applies appropriate rules, and handles decisions. Use when adding security to API routes, form handlers, or any server-side endpoint.
4+
description: Add security protection to a server-side route or endpoint — rate limiting, bot detection, email validation, and abuse prevention. Works across frameworks including Next.js, Express, Fastify, SvelteKit, Remix, Bun, Deno, NestJS, and Python (Django/Flask). Use this skill when the user wants to protect an API route, form handler, auth endpoint, or webhook from abuse, even if they describe it as "add rate limiting," "block bots," "prevent brute force," or "secure my endpoint" without mentioning Arcjet specifically.
55
metadata:
66
pathPatterns:
77
- "app/**/route.ts"
@@ -85,35 +85,58 @@ Search the project for an existing shared Arcjet client file (commonly `lib/arcj
8585

8686
Select rules based on the route's purpose. If the user specified what they want (via `$ARGUMENTS` or in their prompt), use that. Otherwise, infer from context:
8787

88-
| Route type | Recommended rules |
89-
| ----------------------- | ---------------------------------------------------------------------- |
90-
| Public API endpoint | `shield()` + `detectBot()` + `fixedWindow()` or `slidingWindow()` |
91-
| Form handler / signup | `shield()` + `validateEmail()` + `slidingWindow()` |
92-
| Authentication endpoint | `shield()` + `slidingWindow()` (strict, low limits) |
93-
| AI / LLM endpoint | Use `/arcjet:add-ai-protection` instead — it handles the full AI stack |
94-
| Webhook receiver | `shield()` + filter rules for allowed IPs |
95-
| General server route | `shield()` + `detectBot()` |
88+
| Route type | Recommended rules |
89+
| ----------------------- | ------------------------------------------------------------------------------------------------------------ |
90+
| Public API endpoint | `shield()` + `detectBot()` + `slidingWindow()` (use `fixedWindow()` only if hard per-window caps are needed) |
91+
| Form handler / signup | `shield()` + `validateEmail()` + `slidingWindow()` |
92+
| Authentication endpoint | `shield()` + `slidingWindow()` (strict, low limits) |
93+
| AI / LLM endpoint | Use `/arcjet:add-ai-protection` instead — it handles the full AI stack |
94+
| Webhook receiver | `shield()` + filter rules for allowed IPs |
95+
| General server route | `shield()` + `detectBot()` |
9696

9797
For routes that need to detect sophisticated bots (headless browsers, advanced scrapers) — especially form submissions, login/signup pages, and other abuse-prone endpoints — recommend adding Arcjet advanced signals. This is a browser-based detection system using client-side telemetry that complements server-side `detectBot()` rules. See https://docs.arcjet.com/bot-protection/advanced-signals for setup instructions.
9898

9999
Apply route-specific rules using `withRule()` on the shared instance — do not modify the shared instance directly.
100100

101101
## Step 4: Add Protection to the Handler
102102

103-
**Key patterns:**
104-
105-
- Call `protect()` **inside** the route handler, not in middleware.
106-
- Call `protect()` only **once** per request.
107-
- Pass the framework's request object directly.
108-
- For Next.js pages/server components: use `import { request } from "@arcjet/next"` then `const req = await request()`.
109-
110-
**Handle the decision:**
111-
112-
- `isDenied()` (JS) / `is_denied()` (Python) — return the appropriate error response:
113-
- `429` if `reason.isRateLimit()`
114-
- `403` if `reason.isBot()`, `reason.isShield()`, or `reason.isFilterRule()`
115-
- `400` if `reason.isSensitiveInfo()`
116-
- `isErrored()` / `is_error()` — Arcjet fails open. Log the error and allow the request to proceed.
103+
Call `protect()` **inside** the route handler (not in middleware), only **once** per request, passing the framework's request object directly. For Next.js pages/server components: use `import { request } from "@arcjet/next"` then `const req = await request()`.
104+
105+
Use this pattern:
106+
107+
```typescript
108+
const decision = await aj.protect(req);
109+
110+
if (decision.isDenied()) {
111+
if (decision.reason.isRateLimit()) {
112+
return Response.json(
113+
{ error: "Too many requests" },
114+
{ status: 429 },
115+
);
116+
}
117+
if (decision.reason.isBot() || decision.reason.isShield() || decision.reason.isFilterRule()) {
118+
return Response.json(
119+
{ error: "Forbidden" },
120+
{ status: 403 },
121+
);
122+
}
123+
if (decision.reason.isSensitiveInfo()) {
124+
return Response.json(
125+
{ error: "Bad request" },
126+
{ status: 400 },
127+
);
128+
}
129+
}
130+
131+
// Arcjet fails open — log errors but allow the request
132+
if (decision.isErrored()) {
133+
console.warn("Arcjet error:", decision.reason.message);
134+
}
135+
136+
// Proceed with route handler logic...
137+
```
138+
139+
Adapt the response format to your framework (e.g., `res.status(429).json(...)` for Express, `JsonResponse` for Django).
117140

118141
## Step 5: Verify
119142

0 commit comments

Comments
 (0)