Back to Articles

Kiterunner: Context-Aware API Discovery Beyond Directory Brute-Forcing

[ View on GitHub ]

Kiterunner: Context-Aware API Discovery Beyond Directory Brute-Forcing

Hook

Your directory brute-forcer reports zero results, but the application is clearly making API calls. The problem isn’t your wordlist—it’s that you’re knocking on doors when you should be speaking the application’s language.

Context

Traditional content discovery tools like DirBuster and gobuster operate on a simple premise: throw thousands of path names at a web server and see what returns a 200 status code. This works brilliantly for discovering forgotten admin panels, backup files, and hidden directories on conventional web servers. But modern applications don’t work this way anymore.

Today’s web applications are built on frameworks like Flask, Django, Express, and Rails that use explicit routing with specific HTTP methods, expected parameters, and required headers. A REST API endpoint like /api/v2/users/{id} won’t respond to a GET request for /api/v2/users/ the same way. It expects a specific structure. Send a POST to an endpoint that requires PUT, omit required parameters, or forget authentication headers, and you’ll get a 404 or 405—indistinguishable from a non-existent route. Assetnote built Kiterunner to solve this exact problem by transforming content discovery from blind path enumeration into contextual API conversation.

Technical Insight

Runtime Scanning

Compilation

kb compile

generates

loaded by

input to

orchestrates

constructs method + params

sends requests

responses

wildcard detection

filters & dedupes

JSON/Swagger Wordlists

Compiler

.kite Binary Format

Scanner Engine

Target URLs

Request Builder

HTTP Client Pool

Target API

Response Analyzer

Results Output

System architecture — auto-generated

Kiterunner’s core innovation is its use of compiled Swagger and OpenAPI specifications to inform how it probes applications. Rather than just testing paths, it sends properly structured requests based on real-world API definitions collected from internet-wide scans, GitHub repositories, and APIs.guru. These specifications are compiled into a custom binary .kite format that efficiently stores not just paths, but HTTP methods, required parameters, headers, and example values.

The tool’s binary format is crucial to its performance. The routes-large.json wordlist is 2.6GB of decompressed JSON containing API route definitions. Kiterunner’s compiler transforms this into a 40MB .kite file that can be rapidly queried during scans. You compile wordlists once using the kr kb compile command:

# Download and compile Assetnote's curated routes
kr kb download routes-large -o routes-large.kite

# Or compile your own JSON wordlist
kr kb compile custom-routes.json -o custom.kite

During scanning, Kiterunner reads these compiled specifications and constructs contextually appropriate requests. For an API route like POST /api/v1/users that expects a JSON body with username and email fields, Kiterunner will send exactly that—not just a GET request to /api/v1/users. Here’s a basic scan:

# Scan with method-aware requests
kr scan https://api.example.com -w routes-large.kite

# Scan with custom headers and request rate limiting
kr scan https://api.example.com -w routes-large.kite \
  -H "Authorization: Bearer token" \
  --delay 100 \
  --max-parallel-hosts 5

The architecture implements sophisticated concurrency control with both host-level and request-level parallelism. You can scan multiple hosts simultaneously while controlling the request rate per host to avoid triggering rate limiters or overwhelming targets. The --max-parallel-hosts flag controls how many targets are scanned concurrently, while --delay introduces millisecond delays between requests to the same host.

One of Kiterunner’s most critical features is its wildcard detection and quarantine system. Many modern applications use catch-all routes that return 200 status codes for any path (think client-side routing in single-page applications). Without mitigation, these would flood results with false positives. Kiterunner sends preflight requests to detect these patterns:

# Enable aggressive wildcard detection
kr scan https://example.com -w routes.kite \
  --preflight-depth 2 \
  --blacklist-domain example.com/catchall

The tool tests random non-existent paths at various directory depths before running the full scan. If it detects consistent responses (same status codes, content lengths, or response bodies) for paths that shouldn’t exist, it quarantines those signatures and filters them from results. This is configurable through --ignore-length and custom response blacklisting.

Kiterunner also supports “depth scanning” to optimize enumeration efficiency. Rather than testing every possible path immediately, it can verify that intermediate directories exist before attempting deeper routes. If /api/v2/ returns 404, there’s no point testing /api/v2/users/profile. This dramatically reduces request volume on applications with sparse API surfaces:

# Test directory levels before full enumeration
kr scan https://example.com -w routes.kite --preflight-depth 3

The tool outputs results in multiple formats (text, JSON, and formatted tables) and integrates cleanly into automated pipelines. The JSON output is particularly valuable for feeding discoveries into other tools or storing in databases for diff-based monitoring of API surfaces over time.

Gotcha

Kiterunner’s greatest strength—its reliance on pre-compiled API specifications—is also its primary limitation. You cannot simply point it at a text file of paths like you would with ffuf or gobuster. Everything must be converted to the .kite format first, and for custom wordlists, you’ll need your data structured as JSON with proper method, parameter, and header definitions. The documentation on creating these custom specifications from your organization’s internal Swagger docs or extending the dataset with proprietary API patterns is minimal. You’re essentially reverse-engineering the expected JSON schema from examples.

The wordlist download and compilation process has significant upfront cost. The large routes dataset is over 2GB compressed, requiring substantial bandwidth and storage before your first scan. For penetration testers working from coffee shop WiFi or resource-constrained environments, this initial setup can take hours. Once compiled, the .kite files are portable, but you’ll want to maintain a library of them rather than compiling on-demand. There’s also no built-in wordlist update mechanism—you manually track when Assetnote releases updated route collections and re-download. For a tool designed for professional security work, the lack of wordlist versioning and automated updates feels like a missed opportunity.

Verdict

Use if: You’re pentesting modern API-driven applications, microservices architectures, or SPA backends where traditional directory busters consistently come up empty. Kiterunner excels when your targets are built on explicit routing frameworks (Flask, Django, Rails, Express) and you need to discover undocumented endpoints by speaking the application’s expected protocol. It’s particularly valuable for large-scope engagements where the upfront wordlist compilation cost is amortized across multiple targets, and when thorough API enumeration is worth the investment. Skip if: You’re primarily testing legacy web applications with simple file structures, need immediate results without setup overhead, or want real-time wordlist customization without pre-compilation. For quick assessments where standard directory brute-forcing is sufficient, ffuf or gobuster will get you started faster. Also skip if you’re uncomfortable with the proprietary .kite format lock-in and prefer tools with transparent, text-based wordlist ecosystems.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/ai-dev-tools/assetnote-kiterunner.svg)](https://starlog.is/api/badge-click/ai-dev-tools/assetnote-kiterunner)