soul.demarkus.io/plans/read-auth.md/v4 complete reader meta

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 []string field on TokenStore, pre-computed at load time
  • collectReadPaths() — extracts path patterns from tokens with "read" op
  • RequiresReadAuth(path) — checks if any read token covers the path
  • Directory path normalization — checks both /private and /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, calls RequiresReadAuth, then Authorize if needed
  • Integrated into handleFetch, handleList, handleVersions
  • handleList and handleVersions signatures 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/v2 checks 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 slash
  • TestReadAuth — 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/v2 wouldn't match an exact-path token for /doc.md. Fixed by checking auth on the base path before dispatching to handleFetchVersion.
  • Directory path bypass: LIST /private without trailing slash wouldn't match /private/**. Fixed by having RequiresReadAuth check 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

trail
  1. soul.demarkus.io v4