EX

exa-policy-guardrails

Add domain filtering, content moderation, and usage guardrails to Exa search integrations.

Install

mkdir -p .claude/skills/exa-policy-guardrails && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/4822" && unzip -o skill.zip -d .claude/skills/exa-policy-guardrails && rm skill.zip

Installs to .claude/skills/exa-policy-guardrails

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Implement content policy enforcement, domain filtering, and usage guardrails
76 charsno explicit “when” trigger
Advanced

Key capabilities

  • Enforce domain allowlists and blocklists
  • Validate and sanitize search queries
  • Implement freshness policies based on publication date
  • Enforce per-user search quotas
  • Apply built-in content moderation

How it works

It wraps search requests with validation logic that checks domain filters, query patterns, and user quotas before executing the API call.

Inputs & outputs

You give it
Raw user query and user context
You get back
Sanitized and policy-compliant search results

When to use exa-policy-guardrails

  • Define trusted source domain lists
  • Implement search result content filters
  • Enforce query and usage policies
  • Setup content moderation workflows

About this skill

Exa Policy Guardrails

Overview

Policy enforcement for Exa neural search integrations. Exa searches the open web, so results may include unreliable sources, competitor content, or inappropriate material. This skill covers domain allowlists/blocklists (via Exa's includeDomains/excludeDomains), content moderation, query sanitization, freshness policies, and per-user budget enforcement.

Prerequisites

  • exa-js installed and configured
  • Content policy requirements defined
  • Redis for per-user quota tracking (optional)

Instructions

Step 1: Domain Filtering (Built-in Exa Feature)

import Exa from "exa-js";

const exa = new Exa(process.env.EXA_API_KEY);

// Exa supports up to 1200 domains in includeDomains/excludeDomains
const TRUSTED_SOURCES = {
  medical: [
    "pubmed.ncbi.nlm.nih.gov", "who.int", "cdc.gov",
    "nejm.org", "nature.com", "thelancet.com",
  ],
  technical: [
    "github.com", "stackoverflow.com", "developer.mozilla.org",
    "docs.python.org", "nodejs.org", "arxiv.org",
  ],
  news: [
    "reuters.com", "apnews.com", "bbc.com",
    "techcrunch.com", "arstechnica.com",
  ],
};

const BLOCKED_DOMAINS = [
  "competitor1.com", "competitor2.io",
  "spam-farm.com", "content-mill.net",
];

async function policySearch(
  query: string,
  category: keyof typeof TRUSTED_SOURCES | "general"
) {
  const opts: any = {
    type: "auto",
    numResults: 10,
    text: { maxCharacters: 1000 },
    moderation: true,  // Exa's built-in content moderation
  };

  if (category !== "general" && TRUSTED_SOURCES[category]) {
    opts.includeDomains = TRUSTED_SOURCES[category];
  } else {
    opts.excludeDomains = BLOCKED_DOMAINS;
  }

  return exa.searchAndContents(query, opts);
}

Step 2: Query Content Policy

const BLOCKED_PATTERNS = [
  /how to (hack|exploit|attack|ddos)/i,
  /(buy|purchase|order)\s+(drugs|weapons|firearms)/i,
  /personal.*(address|phone|ssn|social security)/i,
  /generate.*(malware|ransomware|virus)/i,
];

function validateQuery(input: string): string {
  for (const pattern of BLOCKED_PATTERNS) {
    if (pattern.test(input)) {
      throw new PolicyViolation("Query blocked by content policy");
    }
  }

  // Sanitize
  return input
    .replace(/[<>{}]/g, "")     // strip HTML/template chars
    .replace(/\0/g, "")         // remove null bytes
    .trim()
    .substring(0, 500);         // cap query length
}

class PolicyViolation extends Error {
  constructor(message: string) {
    super(message);
    this.name = "PolicyViolation";
  }
}

Step 3: Freshness Policy

// Enforce minimum recency for time-sensitive use cases
function applyFreshnessPolicy(
  opts: any,
  maxAgeDays: number
): any {
  const cutoff = new Date(Date.now() - maxAgeDays * 24 * 60 * 60 * 1000);
  return {
    ...opts,
    startPublishedDate: cutoff.toISOString(),
  };
}

// Usage: only return results from the last 90 days
const results = await exa.searchAndContents("AI regulation updates",
  applyFreshnessPolicy(
    { type: "neural", numResults: 10, text: true },
    90  // max 90 days old
  )
);

Step 4: Per-User Budget Enforcement

class ExaUsagePolicy {
  private usage = new Map<string, { count: number; resetAt: number }>();
  private limits: Record<string, number>;

  constructor(limits: Record<string, number> = {
    "free": 10,
    "pro": 100,
    "enterprise": 1000,
  }) {
    this.limits = limits;
  }

  checkQuota(userId: string, tier: string): void {
    const limit = this.limits[tier] || this.limits["free"] || 10;
    const now = Date.now();
    const hourKey = `${userId}:${new Date().toISOString().substring(0, 13)}`;

    let entry = this.usage.get(hourKey);
    if (!entry || entry.resetAt < now) {
      entry = { count: 0, resetAt: now + 3600 * 1000 };
    }

    if (entry.count >= limit) {
      throw new PolicyViolation(
        `Hourly search quota exceeded: ${entry.count}/${limit}`
      );
    }

    entry.count++;
    this.usage.set(hourKey, entry);
  }
}

const usagePolicy = new ExaUsagePolicy();

Step 5: Combined Policy Enforcement

async function enforcedSearch(
  userId: string,
  userTier: string,
  rawQuery: string,
  category: keyof typeof TRUSTED_SOURCES | "general" = "general",
  maxAgeDays?: number
) {
  // 1. Check quota
  usagePolicy.checkQuota(userId, userTier);

  // 2. Validate and sanitize query
  const query = validateQuery(rawQuery);

  // 3. Build options with domain policy
  let opts: any = {
    type: "auto",
    numResults: 10,
    text: { maxCharacters: 1000 },
    moderation: true,
  };

  if (category !== "general" && TRUSTED_SOURCES[category]) {
    opts.includeDomains = TRUSTED_SOURCES[category];
  } else {
    opts.excludeDomains = BLOCKED_DOMAINS;
  }

  // 4. Apply freshness policy
  if (maxAgeDays) {
    opts = applyFreshnessPolicy(opts, maxAgeDays);
  }

  // 5. Execute search
  return exa.searchAndContents(query, opts);
}

Error Handling

IssueCauseSolution
Competitor content in resultsNo domain filteringApply excludeDomains blocklist
Harmful query acceptedNo content policyValidate queries against blocked patterns
Stale results displayedNo freshness checkApply startPublishedDate filter
API cost overrunNo usage limitsImplement per-user/tier quotas
Blocked policy queryFalse positiveReview and adjust BLOCKED_PATTERNS

Resources

Next Steps

For architecture decisions, see exa-architecture-variants. For cost control, see exa-cost-tuning.

When not to use it

  • When queries require unrestricted access to all domains
  • When quota tracking is not required

Prerequisites

exa-js packageRedis for quota tracking

Limitations

  • Exa supports up to 1200 domains in include/exclude lists
  • Query sanitization caps length at 500 characters

How it compares

It provides a centralized policy enforcement layer for Exa integrations, rather than relying on raw API calls without content safety.

Compared to similar skills

exa-policy-guardrails side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
exa-policy-guardrails (this skill)126dReviewAdvanced
file-uploads46moNo flagsAdvanced
graphql66moNo flagsAdvanced
vercel-webhooks-events326dCautionIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

More by jeremylongshore

View all by jeremylongshore

analyzing-logs

jeremylongshore

Analyze application logs to detect performance issues, identify error patterns, and improve stability by extracting key insights.

14123

ollama-setup

jeremylongshore

Configure auto-configure Ollama when user needs local LLM deployment, free AI alternatives, or wants to eliminate hosted API costs. Trigger phrases: "install ollama", "local AI", "free LLM", "self-hosted AI", "replace OpenAI", "no API costs". Use when appropriate context detected. Trigger with relevant phrases based on skill purpose.

1167

backtesting-trading-strategies

jeremylongshore

Backtest crypto and traditional trading strategies against historical data. Calculates performance metrics (Sharpe, Sortino, max drawdown), generates equity curves, and optimizes strategy parameters. Use when user wants to test a trading strategy, validate signals, or compare approaches. Trigger with phrases like "backtest strategy", "test trading strategy", "historical performance", "simulate trades", "optimize parameters", or "validate signals".

1071

generating-database-seed-data

jeremylongshore

Process this skill enables AI assistant to generate realistic test data and database seed scripts for development and testing environments. it uses faker libraries to create realistic data, maintains relational integrity, and allows configurable data volumes. u... Use when working with databases or data models. Trigger with phrases like 'database', 'query', or 'schema'.

1033

cursor-codebase-indexing

jeremylongshore

Execute set up and optimize Cursor codebase indexing. Triggers on "cursor index setup", "codebase indexing", "index codebase", "cursor semantic search". Use when working with cursor codebase indexing functionality. Trigger with phrases like "cursor codebase indexing", "cursor indexing", "cursor".

885

testing-mobile-apps

jeremylongshore

Execute mobile app testing on iOS and Android devices/simulators. Use when performing specialized testing. Trigger with phrases like "test mobile app", "run iOS tests", or "validate Android functionality".

810

You might also like

file-uploads

davila7

Expert at handling file uploads and cloud storage. Covers S3, Cloudflare R2, presigned URLs, multipart uploads, and image optimization. Knows how to handle large files without blocking. Use when: file upload, S3, R2, presigned URL, multipart.

438

graphql

davila7

GraphQL gives clients exactly the data they need - no more, no less. One endpoint, typed schema, introspection. But the flexibility that makes it powerful also makes it dangerous. Without proper controls, clients can craft queries that bring down your server. This skill covers schema design, resolvers, DataLoader for N+1 prevention, federation for microservices, and client integration with Apollo/urql. Key insight: GraphQL is a contract. The schema is the API documentation. Design it carefully.

624

vercel-webhooks-events

jeremylongshore

Implement Vercel webhook signature validation and event handling. Use when setting up webhook endpoints, implementing signature verification, or handling Vercel event notifications securely. Trigger with phrases like "vercel webhook", "vercel events", "vercel webhook signature", "handle vercel events", "vercel notifications".

325

identify-vault-protocol

tradingstrategy-ai

Identify an unknown vault protocol based on its smart contract address

15

openevidence-multi-env-setup

jeremylongshore

Configure OpenEvidence across development, staging, and production environments. Use when setting up multiple environments, managing environment-specific configurations, or implementing environment promotion strategies for clinical AI applications. Trigger with phrases like "openevidence environments", "openevidence staging", "openevidence dev setup", "multi-environment openevidence".

11

documenso-security-basics

jeremylongshore

Implement security best practices for Documenso document signing integrations. Use when securing API keys, configuring webhooks securely, or implementing document security measures. Trigger with phrases like "documenso security", "secure documenso", "documenso API key security", "documenso webhook security".

10

Search skills

Search the agent skills registry