Skip to content

fix(security): decide daemon auth locality from the accepting socket, not a spoofable REMOTE_ADDR header (1.1.23)#226

Merged
click0 merged 1 commit into
mainfrom
claude/analyze-test-coverage-nCOJW
Jul 7, 2026
Merged

fix(security): decide daemon auth locality from the accepting socket, not a spoofable REMOTE_ADDR header (1.1.23)#226
click0 merged 1 commit into
mainfrom
claude/analyze-test-coverage-nCOJW

Conversation

@click0

@click0 click0 commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Summary

The highest-severity item from the second-pass audit. crated grants full trust — bypassing both the bearer token and the pool ACL — to "Unix-socket peers", and detected them with:

req.get_header_value("REMOTE_ADDR").empty()

cpp-httplib stores request headers in a multimap and get_header_value returns the first match, so a remote client on the TCP listener that sends its own empty REMOTE_ADDR: header could shadow the server-set value and be mistaken for a local socket peer — a potential complete bypass of the token layer (including POST /api/v1/privops/:verb, uid=0) whenever listen.tcp_port is enabled. The same header also keyed the rate-limit bucket, letting a client rotate its own key.

Fix

The daemon already runs two separate httplib::Server instances — one on TCP, one on the Unix socket. So trust locality is knowable from which instance accepted the connection, with no dependency on any client-influenced value:

  • registerRoutes(srv, config, isUnixListener) now installs a pre-routing handler that, on every request, erases any client-supplied X-Crated-Listener header and stamps the authoritative value (unix / tcp) from the accepting server instance.
  • isUnixSocketPeer reads that marker instead of REMOTE_ADDR.
  • getClientId keys the rate limiter on the marker plus req.remote_addr (the address httplib fills from the accepted socket — not a header).
  • server.cpp passes false for the TCP registration, true for the UDS one.
  • A missing marker (pre-routing handler somehow skipped) reads as untrusted TCP → token required → fail-closed.

Because the pre-routing handler erases client copies before stamping, a TCP client sending X-Crated-Listener: unix is overwritten to tcp — the marker cannot be forged.

Testing / caveats

This is a structural change on the httplib-coupled auth path — there is no pure unit surface to assert against (the token logic in auth_pure is already covered separately). It is compile-gated by the FreeBSD build (which links crated), and I recommend a runtime check against a live TCP+UDS daemon to confirm the pre-routing handler fires on every path before relying on it in an internet-exposed deployment. Normal UDS-only deployments are behaviorally unchanged (UDS → unix → trusted).

Version

Bumps to 1.1.23; CHANGELOG.md + docs/trust-model.{md,uk.md} updated; the corresponding TODO item is marked fixed (the lease-race and socket_proxy items from the same audit remain deferred).

🤖 Generated with Claude Code


Generated by Claude Code

… not a spoofable REMOTE_ADDR header (1.1.23)

crated grants full trust (bypassing bearer token AND pool ACL) to
"Unix-socket peers" and detected them via
req.get_header_value("REMOTE_ADDR").empty(). cpp-httplib stores request
headers in a multimap and get_header_value returns the first match, so a
remote TCP-listener client sending its own empty REMOTE_ADDR: header
could shadow the server value and be mistaken for a local socket peer —
a potential complete token-layer bypass (incl. privops uid=0) when the
TCP listener is enabled. The same header keyed the rate-limit bucket.

The daemon already runs two separate httplib::Server instances (TCP and
UDS). registerRoutes now takes an isUnixListener flag and installs a
pre-routing handler that erases any client-supplied X-Crated-Listener
header and stamps the authoritative value (unix/tcp) from the accepting
server instance. isUnixSocketPeer reads that marker instead of
REMOTE_ADDR; getClientId keys on the marker plus req.remote_addr (filled
by httplib from the accepted socket, not a header). A missing marker
reads as untrusted TCP — fail-closed.

Structural change on the httplib-coupled auth path (no pure unit
surface); compile-gated by the FreeBSD build, warrants a runtime check
against a live TCP+UDS daemon. Bumps to 1.1.23; CHANGELOG + trust-model
(en/uk) updated; TODO item marked fixed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X6t6tzVypHye5bDGLxzmZK
@click0
click0 merged commit 4cb931c into main Jul 7, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants