Add AI protection
---
name: add-ai-protection
license: Apache-2.0
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.
metadata:
pathPatterns:
- "app/api/chat/**"
- "app/api/completion/**"
- "src/app/api/chat/**"
- "src/app/api/completion/**"
- "**/chat/**"
- "**/ai/**"
- "**/llm/**"
- "**/api/generate*"
- "**/api/chat*"
- "**/api/completion*"
importPatterns:
- "ai"
- "@ai-sdk/*"
- "openai"
- "@anthropic-ai/sdk"
- "langchain"
promptSignals:
phrases:
- "prompt injection"
- "pii"
- "sensitive info"
- "ai security"
- "llm security"
anyOf:
- "protect ai"
- "block pii"
- "detect injection"
- "token budget"
---
# Add AI-Specific Security with Arcjet
Secure AI/LLM endpoints with layered protection: prompt injection detection, PII blocking, and token budget rate limiting. These protections work together to block abuse before it reaches your model, saving AI budget and protecting user data.
## Reference
Read https://docs.arcjet.com/llms.txt for comprehensive SDK documentation covering all frameworks, rule types, and configuration options.
Arcjet rules run **before** the request reaches your AI model — blocking prompt injection, PII leakage, cost abuse, and bot scraping at the HTTP layer.
## Step 1: Ensure Arcjet Is Set Up
Check for an existing shared Arcjet client (see `/arcjet:protect-route` for full setup). If none exists, set one up first with `shield()` as the base rule. The user will need to register for an Arcjet account at https://app.arcjet.com then use the `ARCJET_KEY` in their environment variables.
## Step 2: Add AI Protection Rules
AI endpoints should combine these rules on the shared instance using `withRule()`:
### Prompt Injection Detection
Detects jailbreaks, role-play escapes, and instruction overrides.
- JS: `detectPromptInjection()` — pass user message via `detectPromptInjectionMessage` parameter at `protect()` time
- Python: `detect_prompt_injection()` — pass via `detect_prompt_injection_message` parameter
Blocks hostile prompts **before** they reach the model. This saves AI budget by rejecting attacks early.
### Sensitive Info / PII Blocking
Prevents personally identifiable information from entering model context.
- JS: `sensitiveInfo({ deny: ["EMAIL", "CREDIT_CARD_NUMBER", "PHONE_NUMBER", "IP_ADDRESS"] })`
- Python: `detect_sensitive_info(deny=[SensitiveInfoType.EMAIL, SensitiveInfoType.CREDIT_CARD_NUMBER, ...])`
Pass the user message via `sensitiveInfoValue` (JS) / `sensitive_info_value` (Python) at `protect()` time.
### Token Budget Rate Limiting
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.
Recommended starting configuration:
- `capacity`: 10 (max burst)
- `refillRate`: 5 tokens per interval
- `interval`: "10s"
Pass the `requested` parameter at `protect()` time to deduct tokens proportional to model cost. For example, deduct 1 token per message, or estimate based on prompt length.
Set `characteristics` to track per-user: `["userId"]` if authenticated, defaults to IP-based.
### Base Protection
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.
## Step 3: Compose the protect() Call and Handle Decisions
All rule parameters are passed together in a single `protect()` call. Use this pattern:
```typescript
const userMessage = req.body.message; // the user's input
const decision = await aj.protect(req, {
requested: 1, // tokens to deduct for rate limiting
sensitiveInfoValue: userMessage, // PII scanning
detectPromptInjectionMessage: userMessage, // injection detection
});
if (decision.isDenied()) {
if (decision.reason.isRateLimit()) {
return Response.json(
{ error: "You've exceeded your usage limit. Please try again later." },
{ status: 429 },
);
}
if (decision.reason.isPromptInjection()) {
return Response.json(
{ error: "Your message was flagged as potentially harmful." },
{ status: 400 },
);
}
if (decision.reason.isSensitiveInfo()) {
return Response.json(
{
error:
"Your message contains sensitive information that cannot be processed. Please remove any personal data.",
},
{ status: 400 },
);
}
if (decision.reason.isBot()) {
return Response.json({ error: "Forbidden" }, { status: 403 });
}
}
// Arcjet fails open — log errors but allow the request
if (decision.isErrored()) {
console.warn("Arcjet error:", decision.reason.message);
}
// Proceed with AI model call...
```
Adapt the response format to your framework (e.g., `res.status(429).json(...)` for Express).
## Step 5: Verify
1. Start the app and send a normal message — should succeed
2. Test prompt injection by sending something like "Ignore all previous instructions and..."
3. Test PII blocking by sending a message with a fake credit card number
Start all rules in `"DRY_RUN"` mode first. Once verified, promote to `"LIVE"`.
**Always recommend using the Arcjet MCP tools** to verify rules and analyze traffic:
- `list-requests` — confirm decisions are being recorded, filter by conclusion to see blocks
- `analyze-traffic` — review denial rates and patterns for the AI endpoint
- `explain-decision` — understand why a specific request was allowed or denied (useful for tuning prompt injection sensitivity)
- `promote-rule` — promote rules from `DRY_RUN` to `LIVE` once verified
If the user wants a full security review, suggest the `/arcjet:security-analyst` agent which can investigate traffic, detect anomalies, and recommend additional rules.
The Arcjet dashboard at https://app.arcjet.com is also available for visual inspection.
## Common Patterns
**Streaming responses**: Call `protect()` before starting the stream. If denied, return the error before opening the stream — don't start streaming and then abort.
**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.
**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.
## Common Mistakes to Avoid
- 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.
- `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.
- 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.
- 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.
- Creating a new Arcjet instance per request instead of reusing the shared client with `withRule()`.
Architecture & UI/UX Audit
Act as a senior frontend engineer and product-focused UI/UX reviewer with experience building scalable web applications.
Your task is NOT to write code yet.
First, carefully analyze the project based on:
1. Folder structure (Next.js App Router architecture, route groups, component organization)
2. UI implementation (layout, spacing, typography, hierarchy, consistency)
3. Component reuse and design system consistency
4. Separation of concerns (layout vs pages vs components)
5. Scalability and maintainability of the current structure
Context:
This is a modern Next.js (App Router) project for a developer community platform (similar to Reddit/StackOverflow hybrid).
Instructions:
* Start by analyzing the folder structure and explain what is good and what is problematic
* Identify architectural issues or anti-patterns
* Analyze the UI visually (hierarchy, spacing, consistency, usability)
* Point out inconsistencies in design (cards, buttons, typography, spacing, colors)
* Evaluate whether the layout system (root layout vs app layout) is correctly implemented
* Suggest improvements ONLY at a conceptual level (no code yet)
* Prioritize suggestions (high impact vs low impact)
* Be critical but constructive, like a senior reviewing a real product
Output format:
1. Overall assessment (brief)
2. Folder structure review
3. UI/UX review
4. Design system issues
5. Top 5 high-impact improvements
Do NOT generate code yet.
Focus only on analysis and recommendations.
Astro.js
# Astro v6 Architecture Rules (Strict Mode)
## 1. Core Philosophy
- Follow Astro’s “HTML-first / zero JavaScript by default” principle:
- Everything is static HTML unless interactivity is explicitly required.
- JavaScript is a cost → only add when it creates real user value.
- Always think in “Islands Architecture”:
- The page is static HTML
- Interactive parts are isolated islands
- Never treat the whole page as an app
- Before writing any JavaScript, always ask:
"Can this be solved with HTML + CSS or server-side logic?"
---
## 2. Component Model
- Use `.astro` components for:
- Layout
- Composition
- Static UI
- Data fetching
- Server-side logic (frontmatter)
- `.astro` components:
- Run at build-time or server-side
- Do NOT ship JavaScript by default
- Must remain framework-agnostic
- NEVER use React/Vue/Svelte hooks inside `.astro`
---
## 3. Islands (Interactive Components)
- Only use framework components (React, Vue, Svelte, etc.) for interactivity.
- Treat every interactive component as an isolated island:
- Independent
- Self-contained
- Minimal scope
- NEVER:
- Hydrate entire pages or layouts
- Wrap large trees in a single island
- Create many small islands in loops unnecessarily
- Prefer:
- Static list rendering
- Hydrate only the minimal interactive unit
---
## 4. Hydration Strategy (Critical)
- Always explicitly define hydration using `client:*` directives.
- Choose the LOWEST possible priority:
- `client:load`
→ Only for critical, above-the-fold interactivity
- `client:idle`
→ For secondary UI after page load
- `client:visible`
→ For below-the-fold or heavy components
- `client:media`
→ For responsive / conditional UI
- `client:only`
→ ONLY when SSR breaks (window, localStorage, etc.)
- Default rule:
❌ Never default to `client:load`
✅ Prefer `client:visible` or `client:idle`
- Hydration is a performance budget:
- Every island adds JS
- Keep total JS minimal
📌 Astro does NOT hydrate components unless explicitly told via `client:*` :contentReference[oaicite:0]{index=0}
---
## 5. Server vs Client Logic
- Prefer server-side logic (inside `.astro` frontmatter) for:
- Data fetching
- Transformations
- Filtering / sorting
- Derived values
- Only use client-side state when:
- User interaction requires it
- Real-time updates are needed
- Avoid:
- Duplicating logic on client
- Moving server logic into islands
---
## 6. State Management
- Avoid client state unless strictly necessary.
- If needed:
- Scope state inside the island only
- Do NOT create global app state unless required
- For cross-island state:
- Use lightweight shared stores (e.g., nano stores)
- Avoid heavy global state systems by default
---
## 7. Performance Constraints (Hard Rules)
- Minimize JavaScript shipped to client:
- Astro only loads JS for hydrated components :contentReference[oaicite:1]{index=1}
- Prefer:
- Static rendering
- Partial hydration
- Lazy hydration
- Avoid:
- Hydrating large lists
- Repeated islands in loops
- Overusing `client:load`
- Each island:
- Has its own bundle
- Loads independently
- Should remain small and focused :contentReference[oaicite:2]{index=2}
---
## 8. File & Project Structure
- `/pages`
- Entry points (SSG/SSR)
- No client logic
- `/components`
- Shared UI
- Islands live here
- `/layouts`
- Static wrappers only
- `/content`
- Markdown / CMS data
- Keep `.astro` files focused on composition, not behavior
---
## 9. Anti-Patterns (Strictly Forbidden)
- ❌ Using hooks in `.astro`
- ❌ Turning Astro into SPA architecture
- ❌ Hydrating entire layout/page
- ❌ Using `client:load` everywhere
- ❌ Mapping lists into hydrated components
- ❌ Using client JS for static problems
- ❌ Replacing server logic with client logic
---
## 10. Preferred Patterns
- ✅ Static-first rendering
- ✅ Minimal, isolated islands
- ✅ Lazy hydration (`visible`, `idle`)
- ✅ Server-side computation
- ✅ HTML + CSS before JS
- ✅ Progressive enhancement
---
## 11. Decision Framework (VERY IMPORTANT)
For every feature:
1. Can this be static HTML?
→ YES → Use `.astro`
2. Does it require interaction?
→ NO → Stay static
3. Does it require JS?
→ YES → Create an island
4. When should it load?
→ Choose LOWEST priority `client:*`
---
## 12. Mental Model (Non-Negotiable)
- Astro is NOT:
- Next.js
- SPA framework
- React-first system
- Astro IS:
- Static-first renderer
- Partial hydration system
- Performance-first architecture
- Think:
❌ “Build an app”
✅ “Ship HTML + sprinkle JS”
Build an Interview Practice App
You will build your own Interview Preparation app. I would imagine that you have participated in several interviews at some point. You have been asked questions. You were given exercises or some personality tests to complete. Fortunately, AI assistance comes to help. With it, you can do pretty much everything, including preparing for your next dream position. Your task will be to implement a single-page website using VS Code (or Cursor) editor, and either a Python library called Streamlit or a JavaScript framework called Next.js. You will need to call OpenAI, write a system prompt as the instructions for an LLM, and write your own prompt with the interview prep instructions. You will have a lot of freedom in the things you want to practise for your interview. We don't want you to put it in a box. Interview Questions? Specific programming language questions? Asking questions at the end of the interview? Analysing the job description to come up with the interview preparation strategy? Experiment! Remember, you have all of your tools at your disposal if, for some reason, you get stuck or need inspiration: ChatGPT, StackOverflow, or your friend!