# Read Auth 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. ## Changes ### 1. `server/internal/auth/auth.go` Add `readPaths []string` field to `TokenStore`. Populate in `LoadTokens` and `NewTokenStore` by collecting paths from tokens with `"read"` in operations. ```go type TokenStore struct { tokens map[string]Token readPaths []string // pre-computed from tokens with "read" op now func() time.Time } func (ts *TokenStore) RequiresReadAuth(reqPath string) bool { return matchesAnyPath(ts.readPaths, reqPath) } ``` ### 2. `server/internal/auth/auth_test.go` Table-driven tests for `RequiresReadAuth`: - Path covered by a read token → true - Path NOT covered by any read token → false - Path covered by a publish-only token → false - No tokens at all → false - Glob patterns work (`/private/**` covers `/private/doc.md`) ### 3. `server/internal/handler/handler.go` Add helper: ```go func (h *Handler) authorizeRead(w io.Writer, req protocol.Request) bool ``` Logic: 1. Get token store. If nil → return true (public). 2. If `req.Path` is `protocol.WellKnownManifestPath` → return true (always public). 3. `ts.RequiresReadAuth(req.Path)` — if false → return true (public path). 4. `ts.Authorize(req.Metadata["auth"], req.Path, "read")` — if error → write error, return false. 5. Return true. Call at the top of `handleFetch`, `handleList`, `handleVersions`. For `handleFetchByHash`: check read auth on the resolved path after hash lookup, before serving. ### 4. `server/internal/handler/handler_test.go` Table-driven tests: - FETCH protected path without token → unauthorized - FETCH protected path with valid read token → ok - FETCH protected path with wrong token → unauthorized - FETCH public path without token → ok - LIST protected path without token → unauthorized - LIST protected path with valid token → ok - VERSIONS protected path without token → unauthorized - VERSIONS protected path with valid token → ok - FETCH by hash where resolved path is protected → unauthorized without token - FETCH by hash where resolved path is protected → ok with token - FETCH `/.well-known/agent-manifest.md` → ok without token on protected server - No token store configured → all reads public ## Order of Work 1. `readPaths` field + `RequiresReadAuth` + tests in auth package 2. `authorizeRead` helper + integration into handler 3. Handler tests 4. `bash pre-commit.sh`