Skip to content

[Audit] LOW: No rate limiting on API endpoints #11

@NickCalabs

Description

@NickCalabs

Severity: LOW

Description

The HTTP server in src/server.ts has no rate limiting on any endpoints. While it's bound to localhost by default, this could still be problematic in some scenarios.

Vulnerable Endpoints

POST /agents/:name/run - No rate limit

app.post("/agents/:name/run", async (c) => {
  // Fire-and-forget agent run
  // No check for duplicate runs or rate limiting
});

POST /tools/call - No rate limit

app.post("/tools/call", async (c) => {
  // Direct tool invocation
  // Could be expensive (shell commands, file operations)
});

POST /agents - No rate limit

app.post("/agents", async (c) => {
  // Agent creation/registration
});

Risk Scenarios

  1. Localhost Compromise: If localhost is compromised (malicious browser extension, local malware), attacker can spam requests
  2. Accidental DoS: Buggy client code in a loop could spawn thousands of agent runs
  3. Resource Exhaustion: Each agent run spawns MCP servers, runs LLM calls, etc.
  4. Cost Explosion: Anthropic API calls cost money, unlimited runs = unlimited cost

Current Mitigation

  • Server binds to 127.0.0.1 only (not 0.0.0.0)
  • No network exposure by default

Impact

  • Runaway costs from accidental infinite loops
  • System resource exhaustion (memory, CPU, file descriptors)
  • Database growth (run logs)
  • Difficult to kill runaway processes

Recommendation

Add basic rate limiting with a simple in-memory store:

import { Hono } from "hono";

interface RateLimitEntry {
  count: number;
  resetAt: number;
}

const rateLimits = new Map<string, RateLimitEntry>();

function checkRateLimit(
  key: string, 
  maxRequests: number, 
  windowMs: number
): { allowed: boolean; retryAfter?: number } {
  const now = Date.now();
  const entry = rateLimits.get(key);
  
  if (!entry || now > entry.resetAt) {
    rateLimits.set(key, { count: 1, resetAt: now + windowMs });
    return { allowed: true };
  }
  
  if (entry.count >= maxRequests) {
    return { allowed: false, retryAfter: Math.ceil((entry.resetAt - now) / 1000) };
  }
  
  entry.count++;
  return { allowed: true };
}

// Apply to endpoints:
app.post("/agents/:name/run", async (c) => {
  const limit = checkRateLimit(`run:${c.req.param("name")}`, 10, 60_000); // 10 per minute
  if (!limit.allowed) {
    c.header("Retry-After", String(limit.retryAfter));
    return c.json({ error: "Rate limit exceeded" }, 429);
  }
  // ... rest of handler
});

Or use existing middleware like hono-rate-limiter.

Configuration

Make rate limits configurable in ~/.agentd/config.yaml:

rate_limits:
  agent_runs_per_minute: 10
  tool_calls_per_minute: 100
  agent_creation_per_hour: 5

Created by security audit

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions