mech.app

The mech.app newsletter

Agentic AI, minus the noise.

Get practical field notes on AI agents, automation, developer tools and security delivered to your inbox.

No spam. Unsubscribe anytime.

Financial

Chess-Inspired API Discovery: How Escape Maps Shadow Endpoints Before Agents Call Them

Game-tree search adapted from chess engines finds undocumented API endpoints. Critical for agent tool boundaries when LLMs probe production APIs.

Source: news.ycombinator.com
Chess-Inspired API Discovery: How Escape Maps Shadow Endpoints Before Agents Call Them

Escape (YC W23) borrowed adversarial search from chess engines to discover undocumented API endpoints. The approach matters because agents calling third-party APIs need to know what exists before they can enforce least-privilege boundaries. If your LLM tool layer can’t enumerate the attack surface, you can’t build safe guardrails.

Traditional API security assumes you have a schema. OpenAPI specs, Swagger docs, or at minimum a list of routes. In practice, large organizations ship hundreds of APIs across distributed teams. Many endpoints never make it into documentation. Shadow APIs appear when developers merge features, deprecate routes without cleanup, or expose internal tooling to the internet by accident.

Escape’s game-tree search treats API discovery like a chess position. Each HTTP request is a move. The engine evaluates responses (status codes, headers, body structure) to decide which variations to explore next. Instead of brute-forcing every possible path, it prunes branches that look unproductive and focuses on promising sequences.

How Game-Tree Search Differs from Fuzzing

Traditional fuzzers generate random or semi-random inputs and watch for crashes. Schema-based tools crawl documented endpoints and check for known vulnerabilities. Game-tree search sits between them.

Key differences:

  • State tracking: The engine maintains a tree of explored paths. Each node represents an API state (authenticated, role-switched, resource-scoped). Edges are HTTP requests.
  • Heuristic evaluation: After each probe, the system scores the response. A 200 with unexpected JSON structure scores higher than a 404. A 403 that changes to 200 after adding a header scores very high.
  • Pruning: If a branch returns consistent 404s or errors, the engine deprioritizes it. If a path reveals new parameters or nested resources, it explores deeper.
  • Adversarial mindset: Like a chess engine anticipating opponent moves, the discovery engine anticipates developer mistakes. It tests common misconfigurations (missing auth checks, verbose errors, CORS wildcards) before exotic attacks.
ApproachCoverageSpeedFalse PositivesState Awareness
Random fuzzingBroad, shallowFastHighNone
Schema crawlingNarrow, deepMediumLowPartial (from schema)
Game-tree searchAdaptiveMediumMediumFull (builds tree)
Manual pen-testingTargetedSlowVery lowExpert-driven

Architecture: Discovery Engine and State Management

Escape’s discovery engine runs as a scanning service. You point it at a base URL or network range. It starts with a seed set of known endpoints (from traffic logs, DNS records, or partial schemas) and expands outward.

Core components:

  1. Request generator: Builds HTTP requests based on tree position. Varies methods (GET, POST, PUT, DELETE, PATCH), headers (auth tokens, content types), and parameters (query strings, path segments, body fields).
  2. Response evaluator: Parses status codes, headers, and bodies. Extracts new resource identifiers (UUIDs, slugs, numeric IDs) and parameter hints (error messages that leak expected fields).
  3. Tree manager: Stores explored nodes and edges. Tracks which branches are exhausted and which need deeper exploration.
  4. Heuristic scorer: Ranks unexplored moves. Prioritizes requests likely to reveal new endpoints or security issues.
  5. Reporting layer: Outputs discovered endpoints, inferred schemas, and flagged vulnerabilities.

State between probes:

The engine persists the tree in a graph database (likely Neo4j or similar). Each node stores:

  • URL path and method
  • Request headers and body template
  • Response status, headers, and body schema
  • Authentication context (token, session, role)
  • Timestamp and exploration depth

When deciding the next probe, the engine queries the graph for high-scoring unexplored edges. It avoids redundant requests by checking if a similar path was already tested under the same auth context.

Agent Authorization Problem: Discovered vs. Documented APIs

When an LLM agent calls an API, you typically enforce boundaries using:

  • Allow-lists: Only call endpoints in the documented schema.
  • Rate limits: Throttle requests to prevent runaway loops.
  • Scoped tokens: Issue credentials with minimal permissions.

This breaks down when the API has undocumented endpoints. The agent’s tool definition might say “call /users/{id}” but the actual API also exposes /users/{id}/admin, /users/{id}/export, and /internal/users/bulk-delete. If the agent discovers these through error messages or link headers, it might call them without explicit permission.

Mitigation strategies:

  1. Pre-discovery scanning: Run Escape before deploying agents. Build a complete endpoint map. Configure the agent’s tool layer to block any path not in the map.
  2. Runtime monitoring: Log all agent API calls. Alert on requests to paths outside the documented schema. Treat these as potential security events.
  3. Least-privilege tokens: Even if the agent discovers a new endpoint, the token should lack permissions to execute it. This requires the API to enforce fine-grained authorization, not just authentication.
  4. Schema validation: Reject agent requests that don’t match the expected schema. If the agent tries to POST to /users/{id}/admin, the validation layer returns 403 before the request reaches the API.

Code example: Schema-based request filter

from typing import Dict, Set
import re

class APIBoundaryFilter:
    def __init__(self, allowed_patterns: Set[str]):
        # Compile regex patterns for allowed endpoints
        self.allowed = [re.compile(p) for p in allowed_patterns]
    
    def is_allowed(self, method: str, path: str) -> bool:
        """Check if agent request matches documented schema."""
        for pattern in self.allowed:
            if pattern.match(f"{method} {path}"):
                return True
        return False
    
    def filter_request(self, method: str, path: str, headers: Dict) -> Dict:
        """Block or log requests outside schema."""
        if not self.is_allowed(method, path):
            # Log to SIEM or alerting system
            log_unauthorized_attempt(method, path, headers)
            raise PermissionError(f"Endpoint {method} {path} not in agent schema")
        return {"allowed": True}

# Usage in agent tool layer
boundary = APIBoundaryFilter({
    r"GET /users/\d+",
    r"POST /users",
    r"GET /orders/\d+",
})

# Agent attempts to call discovered endpoint
try:
    boundary.filter_request("GET", "/users/123/admin", {})
except PermissionError as e:
    # Block and alert
    print(e)

Observability: Tracking Discovery and Agent Behavior

You need two observability layers:

Discovery observability:

  • Endpoint inventory: Real-time dashboard showing discovered paths, methods, and auth requirements.
  • Vulnerability feed: Alerts when the scanner finds missing auth checks, injection points, or sensitive data leaks.
  • Drift detection: Compare current scan results to baseline. Flag new endpoints that appeared since last scan.

Agent observability:

  • Request logs: Every API call the agent makes, including path, method, headers, and response status.
  • Schema violations: Count of blocked requests outside the documented schema.
  • Token usage: Track which endpoints the agent calls most frequently. Detect anomalies (sudden spike in calls to admin endpoints).

Escape likely integrates with SIEM platforms (Splunk, Datadog, Elastic) to feed discovery results into existing security workflows. You want alerts when a new endpoint appears in production and when an agent tries to call it before it’s been reviewed.

Deployment Shape and Failure Modes

Deployment options:

  1. Scheduled scans: Run discovery weekly or after each deployment. Treat it like a security audit.
  2. Continuous scanning: Deploy the engine as a sidecar in your staging environment. Scan every API change before it reaches production.
  3. On-demand: Trigger scans via CI/CD pipeline when API code changes are detected.

Failure modes:

  • Rate limiting: The discovery engine generates many requests. Target APIs might throttle or block it. Solution: Respect rate limits, use backoff, or run scans during low-traffic windows.
  • False negatives: The heuristic scorer might prune branches that actually lead to hidden endpoints. Solution: Periodically adjust scoring weights based on missed findings.
  • Auth context explosion: If the API has many roles and permissions, the state tree grows exponentially. Solution: Prioritize high-risk roles (admin, service accounts) and sample lower-privilege paths.
  • Dynamic endpoints: APIs that generate paths based on runtime state (user-specific UUIDs, session tokens) are hard to discover. Solution: Seed the scanner with sample identifiers from logs or test data.

When Agents Probe APIs: The Rogue Discovery Problem

An LLM agent with broad tool access might accidentally (or intentionally) perform its own API discovery. If the agent’s prompt says “find all user data” and the API returns a 404 with a helpful error message (“try /users/export instead”), the agent might follow that hint.

Risks:

  • Privilege escalation: Agent discovers admin endpoints and calls them with its service token.
  • Data exfiltration: Agent finds bulk export endpoints not intended for automated access.
  • Denial of service: Agent discovers expensive endpoints (report generation, bulk operations) and calls them in a loop.

Defenses:

  1. Strict tool definitions: Only expose specific endpoints to the agent. Don’t give it a generic “call any API” tool.
  2. Response filtering: Strip error messages and link headers from API responses before passing them to the agent. Prevent the agent from learning about undocumented paths.
  3. Behavioral analysis: Monitor agent request patterns. Flag sequences that look like discovery (many 404s followed by a 200, rapid path enumeration).

Technical Verdict

Use Escape-style game-tree discovery when:

  • You manage hundreds of APIs across distributed teams and lack a central schema registry.
  • You deploy agents that call third-party or internal APIs and need to enforce strict tool boundaries.
  • You need to find shadow endpoints before attackers (or rogue agents) do.
  • Your security team lacks time to manually audit every API change.

Avoid or supplement when:

  • You have comprehensive OpenAPI specs and strong schema governance. Traditional schema-based tools are faster and simpler.
  • Your APIs are highly dynamic (user-specific paths, ephemeral resources). Game-tree search struggles with infinite state spaces.
  • You need real-time protection. Discovery is a batch process. Pair it with runtime API gateways for live enforcement.
  • Your threat model focuses on code-level vulnerabilities (SQL injection, XSS) rather than endpoint discovery. Use SAST/DAST tools instead.

The chess analogy is apt. Both domains involve exploring a vast possibility space under time constraints. Both require pruning bad moves and focusing on promising lines. The difference is that in chess, all legal moves are known. In API security, half the board is hidden until you probe it.