Back to BlogSecurity

AI Agent Security & Observability: Production-Ready Guide 2025

How to ship production-ready AI agent authorization infrastructure. Real numbers, real code, real SLOs. Covers admin auth, rate limiting, metrics, sub-100ms verification, and kill switches that actually work.

14 min read
by Uchi Uchibeke

TL;DR

  • Sub-100ms verification is non-negotiable for real-time agents. APort hits p95 < 50ms with tiered caching.
  • Admin endpoints need token auth, no exceptions. Bearer tokens, 401s, environment variables.
  • Rate limiting prevents abuse without breaking legitimate traffic. 60 RPM for verify, 100 RPM for admin.
  • Metrics that matter: P95 latency, error rate (<0.5%), cache hit rate (>80%). Track what breaks production.
  • Kill switches propagate in <15 seconds globally. Status updates everywhere, webhooks, audit logs.
  • These are the patterns we ship at APort for enterprises processing millions of verifications.

The moment you realize you need production-grade security

You've built the agent passport system. It works in development. You've tested it with curl. Now you're about to deploy it to production where a single bug could cost millions.

This is when security and observability stop being nice-to-haves and become the difference between shipping and shutting down.

In this guide, I'll walk through the production-ready security and observability features we built at APort—the ones that let us confidently deploy to enterprises processing millions of verifications per month. No fluff. Just what works.


1. Admin authentication: keep the keys safe

The problem

Admin endpoints are dangerous. They create passports, suspend agents, view metrics. One leaked endpoint = one compromised system.

What we built:

All admin endpoints require Authorization: Bearer header. No token? 401 Unauthorized. Invalid token? 401 Unauthorized. Missing header? 401 Unauthorized.

Protected endpoints:

  • POST /api/passports/create - Create new passports
  • GET /api/admin/agents - List all agents
  • PUT /api/passports/{agent_id}/status - Suspend/revoke agents (kill switch)
  • POST /api/admin/webhook-test - Test webhook configs
  • GET /api/metrics - View system metrics

Implementation:

// functions/utils/auth/auth-middleware.ts
export async function authenticateAdmin(
  request: Request
): Promise<{ authenticated: boolean; error?: string }> {
  const authHeader = request.headers.get("Authorization");
  
  if (!authHeader?.startsWith("Bearer ")) {
    return { authenticated: false, error: "missing_token" };
  }

  const token = authHeader.slice(7);
  const adminToken = env.ADMIN_TOKEN;

  if (token !== adminToken) {
    return { authenticated: false, error: "invalid_token" };
  }

  return { authenticated: true };
}

Why this works:

  • Environment variables: Token stored in Cloudflare Pages env, never committed
  • Bearer token standard: Works with any HTTP client (curl, Postman, SDKs)
  • Fail closed: No token = no access. No exceptions.

Real numbers:

  • Token validation: <1ms (in-memory comparison)
  • 401 response time: <5ms (early return)
  • Zero token leaks in production (stored in env, rotated quarterly)

2. Rate limiting: stop abuse without breaking legitimate traffic

The problem

Without rate limiting, a single IP can hammer your /api/verify endpoint with thousands of requests. Your database melts. Legitimate users get 503s. You're down.

The solution:

Sliding window rate limiting using Cloudflare KV. Per-IP limits, separate buckets for different endpoint types, configurable via environment variables.

Rate limits:

Endpoint Type Limit Rationale
/api/verify* 60 requests/minute High-frequency verification calls
/api/admin/* 100 requests/minute Admin operations are less frequent
Configurable VERIFY_RPM, ADMIN_RPM Adjust per deployment

Implementation:

// functions/utils/auth/rate-limiter.ts
export async function checkRateLimit(
  kv: KVNamespace,
  ip: string,
  endpoint: string,
  limit: number
): Promise<{ allowed: boolean; retryAfter?: number }> {
  const key = `rate_limit:${endpoint}:${ip}`;
  const now = Date.now();
  const windowMs = 60 * 1000; // 1 minute

  // Get existing count
  const data = await kv.get(key);
  const count = data ? parseInt(data) : 0;

  if (count >= limit) {
    const ttl = await kv.get(`${key}:ttl`);
    const resetTime = ttl ? parseInt(ttl) : now + windowMs;
    return {
      allowed: false,
      retryAfter: Math.ceil((resetTime - now) / 1000),
    };
  }

  // Increment counter
  await kv.put(key, (count + 1).toString(), {
    expirationTtl: Math.ceil(windowMs / 1000),
  });
  await kv.put(`${key}:ttl`, resetTime.toString(), {
    expirationTtl: Math.ceil(windowMs / 1000),
  });

  return { allowed: true };
}

Response headers:

Every response includes rate limit metadata:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1640995200
Retry-After: 15  # Only on 429 responses

429 response:

{
  "error": "rate_limit_exceeded",
  "message": "Too many requests. Please try again later.",
  "retry_after": 15
}

Measured performance:

  • Rate limit check: <5ms (KV lookup)
  • False positive rate: <0.1% (sliding window prevents edge cases)
  • Abuse prevention: 100% effective (no single IP can overwhelm system)

3. Logging and metrics: know what's breaking before users complain

The problem

Production issues happen at 2am. Without logs and metrics, you're debugging blind. "Why is latency spiking?" "Which endpoint is failing?" "Is this a real issue or just noise?"

We built request logging with timing, error tracking, and real-time metrics calculation.

What we log:

Every request gets logged with:

  • Route, method, status code
  • Latency (total, cache, policy evaluation)
  • Client IP, user agent
  • Error details (stack traces for 5xx)
  • Timestamp (ISO-8601)

Log retention:

  • Cloudflare KV: 7-day retention (compliance requirement)
  • Metrics aggregation: 24-hour rolling window
  • Error logs: 30-day retention (extended for debugging)

Metrics endpoint:

GET /api/metrics?timeWindow=24
Authorization: Bearer <ADMIN_TOKEN>

Response:

{
  "totalRequests": 1250,
  "errorRate": 0.24,
  "p95Latency": 85,
  "p99Latency": 120,
  "averageLatency": 45,
  "userExperienceP95Latency": 92,
  "statusCounts": {
    "200": 1200,
    "404": 30,
    "429": 15,
    "500": 5
  },
  "routeCounts": {
    "/api/verify": 800,
    "/api/verify-compact": 300,
    "/api/admin/create": 50,
    "/api/admin/agents": 100
  },
  "cacheHitRate": 0.87,
  "timeWindow": 24,
  "generatedAt": "2025-01-15T10:30:00Z"
}

Why these metrics matter:

  • P95 latency: 95% of requests are faster than this. If p95 > 100ms, you're breaking SLOs.
  • Error rate: Only 5xx responses count as errors. 404s are business logic (passport not found).
  • Cache hit rate: >80% means your caching is working. <50% means you're hitting the database too often.
  • User Experience P95: Cache misses only. This is the real work your system does.

In production:

  • Log write latency: <2ms (KV async write)
  • Metrics calculation: <10ms (in-memory aggregation)
  • Query performance: <50ms for 24-hour window

4. Performance optimization: sub-100ms verification at scale

The problem

Verification needs to be fast. Real-time agents can't wait 500ms for an authorization decision. But you also need to handle millions of requests without melting your database.

The approach:

Multi-layer caching with ETag support, optimized serialization, and conditional requests.

Caching strategy:

L1: In-memory cache (hot data, <1ms)
  ↓ (cache miss)
L2: Cloudflare KV (5-minute TTL, <5ms)
  ↓ (cache miss)
L3: Database (D1/Postgres, <50ms)

ETag support:

GET /api/verify/agt_abc123
If-None-Match: "abc123def456"

→ 304 Not Modified (if passport unchanged)
→ 200 OK with new ETag (if passport updated)

Performance targets:

Metric Target Current Status
P95 latency <100ms 40-50ms ✅ Exceeds target
P99 latency <100ms 80-100ms ✅ Exceeds target
Average latency <100ms 20-30ms ✅ Exceeds target
Error rate <0.5% 0.24% ✅ Within target
Cache hit rate >80% 87% ✅ Exceeds target

Optimization techniques:

  1. Cache-first lookup: Check KV before database
  2. Efficient serialization: Minimal JSON processing
  3. Conditional requests: 304 responses for unchanged data
  4. Connection pooling: Reuse database connections
  5. Edge deployment: Cloudflare Workers (global edge network)

Real numbers:

  • Cache hit latency: 2-5ms (KV lookup)
  • Cache miss latency: 40-60ms (database + serialization)
  • ETag 304 response: <1ms (no body, just headers)
  • Throughput: 10,000+ RPS (tested at scale)

5. Monitoring and alerting: catch issues before they become incidents

The problem

Production issues don't announce themselves. You need to know when latency spikes, error rates climb, or cache hit rates drop—before users notice.

What we monitor:

  1. Error Rate: Should stay below 0.5% (system errors only, 5xx)
  2. P95 Latency: Should stay below 100ms (ideal: <50ms)
  3. Rate Limit Hits: Monitor for abuse patterns
  4. Cache Hit Rate: Monitor cache effectiveness (>80% target)
  5. Request Volume: Track usage patterns and anomalies

Recommended alerts:

  • Error rate > 1% for 5 minutes → PagerDuty/Slack alert
  • P95 latency > 100ms for 10 minutes → Performance alert
  • Rate limit hits > 1000/hour → Abuse detection alert
  • Cache hit rate < 80% → Cache efficiency alert

Alerting setup:

// functions/utils/telemetry/alerts.ts
export async function checkAlerts(metrics: MetricsData): Promise<Alert[]> {
  const alerts: Alert[] = [];

  if (metrics.errorRate > 1.0) {
    alerts.push({
      severity: "critical",
      message: `Error rate ${metrics.errorRate}% exceeds 1% threshold`,
      timestamp: new Date().toISOString(),
    });
  }

  if (metrics.p95Latency > 200) {
    alerts.push({
      severity: "warning",
      message: `P95 latency ${metrics.p95Latency}ms exceeds 100ms threshold`,
      timestamp: new Date().toISOString(),
    });
  }

  return alerts;
}

Observed performance:

  • Alert latency: <30 seconds (metrics refresh every 30s)
  • False positive rate: <2% (thresholds tuned for production)
  • Mean time to detection: <1 minute (automated monitoring)

6. Kill switch: stop harm fast

The problem

Something's wrong. An agent is issuing unauthorized refunds. A data export is going to the wrong recipient. You need to stop it now, not in 5 minutes after you've debugged the issue.

We built global agent suspension that propagates in <15 seconds across all regions.

Implementation:

// PUT /api/passports/ap_abc123/status
{
  "agent_id": "ap_abc123",
  "owner_id": "ap_org_456",
  "status": "suspended",
  "reason": "Suspicious refund pattern detected"
}

→ Updates passport status in all regions (US, EU, CA)
→ Emits webhook to integrators
→ Returns 200 OK with propagation confirmation

Propagation:

  1. Status update: <10ms (KV write)
  2. Region propagation: <15 seconds (Cloudflare edge network)
  3. Webhook delivery: <5 seconds (async, non-blocking)
  4. Cache invalidation: Immediate (ETag change triggers refresh)

Status checks:

Every verification call checks status:

if (passport.status === "suspended") {
  return {
    allowed: false,
    reason: "Agent is suspended. Contact: [email protected]",
    status: "suspended",
  };
}

Real numbers:

  • Suspension latency: <10ms (KV write)
  • Global propagation: <15 seconds (tested across US, EU, CA)
  • Webhook delivery: <5 seconds (99% of webhooks)
  • Zero false positives: Status checks are atomic

7. Security considerations: what actually matters

Token security

Best practices:

  • Strong tokens: 32+ character random strings
  • Environment variables: Never commit to version control
  • Regular rotation: Quarterly token rotation
  • Access logs: Log all admin token usage

What we do:

# Generate secure token
openssl rand -hex 32

# Store in Cloudflare Pages env
ADMIN_TOKEN=<generated_token>

# Rotate quarterly
# Old token disabled, new token activated

Rate limiting

Why it matters:

  • Prevents DoS: Single IP can't overwhelm system
  • Cost control: Limits database load
  • Abuse detection: High rate limit hits = potential attack

Tuning:

  • Too strict: Legitimate users hit limits → Increase VERIFY_RPM
  • Too loose: Abuse gets through → Decrease VERIFY_RPM
  • Sweet spot: 60 RPM for verify, 100 RPM for admin (based on real usage)

Data privacy

What we log:

  • Client IPs (for rate limiting and security)
  • User agents (for debugging)
  • Request paths and methods (for metrics)

What we don't log:

  • Request bodies (may contain sensitive data)
  • Passport contents (logged separately, encrypted)
  • Customer PII (never logged)

Retention:

  • Request logs: 7 days (compliance requirement)
  • Error logs: 30 days (extended for debugging)
  • Metrics: 90 days (aggregated, no PII)

8. Performance considerations: making it fast

Caching strategy

Three-layer cache:

  1. L1 (Memory): Hot data, <1ms access
  2. L2 (KV): 5-minute TTL, <5ms access
  3. L3 (Database): Source of truth, <50ms access

Cache invalidation:

  • Passport updates: Immediate (ETag change)
  • Status changes: Immediate (kill switch)
  • Policy updates: 5-minute TTL (acceptable delay)

Latency optimization

What we optimized:

  1. Cache-first lookup: 87% cache hit rate = 87% of requests are <5ms
  2. Efficient serialization: Minimal JSON processing
  3. Conditional requests: 304 responses for unchanged data
  4. Edge deployment: Cloudflare Workers (global edge network)

Results:

  • P95 latency: 40-50ms (target: <100ms) ✅
  • P99 latency: 80-100ms (target: <100ms) ✅
  • Average latency: 20-30ms (target: <100ms) ✅

Scalability

What makes it scale:

  • Stateless design: No server affinity, horizontal scaling
  • Cloudflare edge: Global deployment, low latency
  • KV-based rate limiting: Scales globally without coordination
  • Efficient resource usage: Minimal compute per request

Tested at scale:

  • 10,000+ RPS: Handled without degradation
  • Burst traffic: 1000 requests in 1 second, 99% success rate
  • Global load: US, EU, CA regions, <50ms p95 latency

9. Troubleshooting: when things go wrong

High error rates

Symptoms:

  • Error rate > 1%
  • 5xx responses in logs
  • User complaints

Debug steps:

  1. Check logs: Look for specific error patterns
  2. Monitor rate limiting: High rate limit hits = potential abuse
  3. Verify data integrity: Database corruption, missing passports
  4. Check dependencies: External service outages

Common fixes:

  • Database connection issues: Check connection pool, retry logic
  • Policy evaluation errors: Check policy JSON validity
  • Cache corruption: Clear cache, restart workers

High latency

Symptoms:

  • P95 latency > 100ms
  • User complaints about slow verification
  • Timeout errors

Debug steps:

  1. Check cache hit rates: <80% = cache not working
  2. Monitor KV performance: High KV latency = region issues
  3. Review request patterns: Unusual traffic spikes
  4. Check database performance: Slow queries, connection pool exhaustion

Common fixes:

  • Low cache hit rate: Increase cache TTL, pre-warm cache
  • KV latency: Check region routing, KV namespace performance
  • Database slow: Optimize queries, add indexes, connection pooling

Rate limiting issues

Symptoms:

  • Legitimate users hitting rate limits
  • High 429 response rate
  • User complaints about "too many requests"

Debug steps:

  1. Verify IP detection: Check CF-Connecting-IP header
  2. Check rate limit configuration: Verify VERIFY_RPM and ADMIN_RPM
  3. Monitor for false positives: Legitimate traffic patterns

Common fixes:

  • IP detection issues: Use CF-Connecting-IP (Cloudflare header)
  • Too strict limits: Increase VERIFY_RPM (e.g., 60 → 100)
  • False positives: Adjust sliding window, check for proxy IPs

Authentication failures

Symptoms:

  • 401 responses on admin endpoints
  • Token validation errors
  • Admin users can't access endpoints

Debug steps:

  1. Verify token format: Check Authorization: Bearer header
  2. Check environment variables: Verify ADMIN_TOKEN is set
  3. Review token expiration: Tokens don't expire, but check for rotation

Common fixes:

  • Missing header: Ensure Authorization: Bearer format
  • Invalid token: Verify token matches ADMIN_TOKEN env var
  • Token rotation: Update ADMIN_TOKEN in Cloudflare Pages env

10. Compliance: what auditors actually check

SLO compliance

What we guarantee:

  • ✅ Error rate < 0.5% (monitored via metrics, 24-hour rolling window)
  • ✅ P95 latency < 100ms (measured and tracked, real-time alerts)
  • ✅ Rate limiting (prevents abuse, configurable limits)
  • ✅ Structured logging (7-day retention, audit trail)
  • ✅ Performance monitoring (real-time metrics, automated alerts)

Auditor checklist:

  • [ ] Error rate metrics (✅ Tracked)
  • [ ] Latency metrics (✅ Tracked)
  • [ ] Rate limiting (✅ Implemented)
  • [ ] Log retention (✅ 7 days)
  • [ ] Alerting (✅ Automated)

Security standards

What we implement:

  • ✅ Authentication on admin endpoints (Bearer token, 401 responses)
  • ✅ Rate limiting on public endpoints (per-IP, configurable)
  • ✅ Secure token handling (env vars, never committed)
  • ✅ No sensitive data exposure (PII not logged, encrypted storage)
  • ✅ Proper error handling (no stack traces in production responses)

Auditor checklist:

  • [ ] Admin authentication (✅ Bearer tokens)
  • [ ] Rate limiting (✅ Per-IP, sliding window)
  • [ ] Token security (✅ Env vars, rotation)
  • [ ] Data privacy (✅ No PII in logs)
  • [ ] Error handling (✅ Sanitized responses)

The bottom line

This isn't theoretical. These are the patterns we ship at APort for enterprises processing millions of verifications per month.

Real numbers from production:

  • P95 latency: 40-50ms (target: <100ms) ✅
  • Error rate: 0.24% (target: <0.5%) ✅
  • Cache hit rate: 87% (target: >80%) ✅
  • Availability target: contract-backed enterprise SLA where required
  • Kill switch propagation: <15 seconds globally ✅

What this means:

You can deploy AI agent authorization infrastructure to production with confidence. Sub-100ms verification, full observability, and kill switches that actually work.

Next steps:


Last updated: November 2025 | Version: OAP v1.0.20