From d0d8423ac6dcb69fc5056c5ab368465b81ff0124 Mon Sep 17 00:00:00 2001 From: ZDDC Date: Thu, 21 May 2026 16:41:29 -0500 Subject: [PATCH] test(handler): un-skip the profile existence-hiding invariant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TestInvariant_ProfileAdminEndpointsHideFromNonAdmins was skipped pending the ServeProfile dispatcher refactor — which has since landed (ServeProfile in profilehandler.go is the entry point, with an adminOnly wrapper that denies with 404). Implement the test against it: non-admin, anonymous, and un-elevated-admin callers must get 404 (never 403/200) on every admin-gated sub-resource (/whoami, /config, /logs, /effective-policy, /reindex), so the namespace can't be enumerated; an elevated admin gets through (/whoami, /config positive control). Locks in the existence-hiding security property that was previously unverified. Co-Authored-By: Claude Opus 4.7 (1M context) --- zddc/internal/handler/auth_invariants_test.go | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/zddc/internal/handler/auth_invariants_test.go b/zddc/internal/handler/auth_invariants_test.go index 9d06c06..7197824 100644 --- a/zddc/internal/handler/auth_invariants_test.go +++ b/zddc/internal/handler/auth_invariants_test.go @@ -459,8 +459,53 @@ func TestInvariant_UnelevatedAdminNoSilentBypass(t *testing.T) { func TestInvariant_ProfileAdminEndpointsHideFromNonAdmins(t *testing.T) { // These checks lock in the existence-hiding property: non-admins must - // see 404, never 403, so they can't probe which paths exist. - t.Skip("requires the profile handler dispatcher entry point; skip until the refactor confirms ServeProfile signature") + // see 404, never 403, so they can't probe which admin-only resources + // exist. ServeProfile is the dispatcher (the refactor this test waited + // on); its adminOnly wrapper denies with 404 before the sub-handler + // runs, so a nil ring/index is safe for the non-admin paths. + cfg, _ := invariantsFixture(t) + + adminEndpoints := []string{"/whoami", "/config", "/logs", "/effective-policy", "/reindex"} + + profileGet := func(sub, email string, elevated bool) *httptest.ResponseRecorder { + req := httptest.NewRequest(http.MethodGet, ProfilePathPrefix+sub, nil) + ctx := context.WithValue(req.Context(), EmailKey, email) + ctx = context.WithValue(ctx, ElevatedKey, elevated) + req = req.WithContext(ctx) + rec := httptest.NewRecorder() + ServeProfile(cfg, nil, nil, rec, req) + return rec + } + + // Non-admin (eve, project_team only) and anonymous callers must get + // 404 on every admin endpoint — never 403, never 200. + for _, who := range []struct { + email string + elevated bool + label string + }{ + {"eve@example.com", false, "non-admin"}, + {"", false, "anonymous"}, + {"admin@example.com", false, "un-elevated admin"}, // sudo-style: no authority until elevated + } { + for _, sub := range adminEndpoints { + rec := profileGet(sub, who.email, who.elevated) + if rec.Code != http.StatusNotFound { + t.Errorf("%s GET /.profile%s = %d, want 404 (existence-hiding)", who.label, sub, rec.Code) + } + } + } + + // Positive control: an elevated root admin must NOT get 404 on the + // gated routes that need no ring/index — proving the 404s above are + // the admin gate, not a missing route. (/whoami and /config don't + // touch the log ring or archive index.) + for _, sub := range []string{"/whoami", "/config"} { + rec := profileGet(sub, "admin@example.com", true) + if rec.Code == http.StatusNotFound { + t.Errorf("elevated admin GET /.profile%s = 404; the gate should admit admins", sub) + } + } } // dump prints the rec body when t.Logf would help debugging — used in