Read Auth for Private Networks — COMPLETE
Shipped 2026-03-14. Per-path read token enforcement for private networks.
Design
Model: If any token in the store has "read" in its operations AND its path pattern matches the request path, that path requires read auth. If no such token exists, the path is public. No new config — the existing tokens TOML file drives everything.
Performance: Pre-compute readPaths []string at load time by collecting path patterns from tokens with "read" in operations. RequiresReadAuth calls matchesAnyPath(ts.readPaths, reqPath) — no iteration over tokens at request time.
Exempt paths: /health and /.well-known/agent-manifest.md bypass read auth so agents can discover capabilities before authenticating.
Content-addressed fetch: Hash lookups resolve to a real path first, then read auth checks that path — knowing a hash doesn't bypass access control.
What Shipped
server/internal/auth/auth.go
readPaths []stringfield onTokenStore, pre-computed at load timecollectReadPaths()— extracts path patterns from tokens with"read"opRequiresReadAuth(path)— checks if any read token covers the path- Directory path normalization — checks both
/privateand/private/against patterns so trailing-slash omission can't bypass/**globs
server/internal/handler/handler.go
authorizeRead(w, req)helper — checks token store, exempts well-known manifest path, callsRequiresReadAuth, thenAuthorizeif needed- Integrated into
handleFetch,handleList,handleVersions handleListandhandleVersionssignatures changed from(w, path string)to(w, req protocol.Request)to carry auth metadata- Hash-based fetch checks read auth on the resolved path after hash lookup
- Versioned path auth —
/doc.md/v2checks auth on the base path/doc.md
Tests
TestRequiresReadAuth— 9 table-driven cases covering read tokens, publish-only tokens, no tokens, glob patterns, directory paths with/without trailing slashTestReadAuth— 15 handler tests covering FETCH/LIST/VERSIONS with and without tokens, wrong tokens, public paths, well-known manifest, no token store, hash-based fetch, versioned paths
Edge Cases Caught During Review
- Versioned path bypass:
/doc.md/v2wouldn't match an exact-path token for/doc.md. Fixed by checking auth on the base path before dispatching tohandleFetchVersion. - Directory path bypass:
LIST /privatewithout trailing slash wouldn't match/private/**. Fixed by havingRequiresReadAuthcheck both the path as-is and with a trailing slash appended.
Backwards Compatibility
Fully backwards compatible:
- No read tokens configured = all reads public (same as before)
- No new config options needed
- No protocol changes
- Existing write auth unchanged
Related documents
- Content addressing: hash-based fetch resolves path before read auth check