github Arthur-Ficial/apfel v0.6.23
v0.6.23 - Localhost CSRF Protection

latest releases: v1.8.4, v1.8.3, v1.8.2...
3 months ago

Localhost CSRF Protection

apfel's HTTP server now rejects cross-origin requests by default, protecting against the same localhost CSRF vulnerability class that hit Ollama (same port 11434!), Jupyter, and 175,000+ exposed LLM instances.

The problem: Any website you visit can silently fetch() to 127.0.0.1:11434. Without protection, a malicious page could abuse your on-device model for compute or exfiltrate responses (with --cors).

The fix: One middleware, zero breaking changes. curl, Python SDKs, and scripts keep working exactly as before - they don't send Origin headers.

What changed

Default behavior (apfel --serve):

  • Requests without an Origin header: allowed (curl, SDKs, scripts - unchanged)
  • Requests with localhost Origin (127.0.0.1, localhost, [::1], any port): allowed
  • Requests with foreign Origin (http://evil.com): rejected with 403
  • Subdomain attacks (http://localhost.evil.com): rejected - safe prefix matching

New flags:

Flag Description
--allowed-origins <origins> Add comma-separated origins to localhost defaults
--no-origin-check Disable origin checking (allow all origins)
--token <secret> Require Bearer token authentication
--token-auto Generate and print a random Bearer token
--footgun Disable all protections (--no-origin-check + --cors)

New env var: APFEL_TOKEN - set Bearer token via environment.

Security details

  • Origin check: Validates the Origin HTTP header (only sent by browsers on cross-origin requests). No header = no check = backward compatible.
  • Token auth: Optional Bearer token for endpoints (except /health which stays open for monitoring). Explicit secrets from --token and APFEL_TOKEN are never echoed in the startup banner.
  • CORS consolidation: Previously scattered across 3 code locations, now centralized in a single SecurityMiddleware. CORS headers are properly gated behind --cors (previously the OPTIONS handler always returned them).
  • Proper CORS: When --cors is enabled with specific origins, responds with the actual request origin (not wildcard *) and includes Vary: Origin.
  • 401 responses include WWW-Authenticate: Bearer header per RFC 7235.
  • Severity: Low - Apple's on-device model cannot access filesystem or network. Worst case is compute abuse or response exfiltration with --cors.

Architecture

  • Sources/Core/OriginValidator.swift - Pure validation logic in ApfelCore (unit-testable, no Hummingbird dependency)
  • Sources/SecurityMiddleware.swift - Hummingbird RouterMiddleware handling origin check, token auth, and CORS
  • Middleware runs before all routes - catches every endpoint including future ones

Examples

# Default - safe, works like before
apfel --serve

# Local web app on port 3000
apfel --serve --cors --allowed-origins "http://localhost:3000"

# Token auth for shared machines
apfel --serve --token-auto

# Old behavior (wide open)
apfel --serve --footgun

Tests

  • 99 unit tests (32 new for OriginValidator) - all pass
  • 78 integration tests (18 new for security) - all pass
  • Security tests include spawned server scenarios for --token, --token-auto, --allowed-origins, and CORS interaction

Documentation

  • Server Security Guide - detailed docs for all security flags, matching matrix, common scenarios, and prior art

Prior art

Credit

This issue was originally reported via Hacker News discussion: https://news.ycombinator.com/item?id=47625563


Full Changelog: v0.6.14...v0.6.23

Don't miss a new apfel release

NewReleases is sending notifications on new releases.