test(handler): un-skip the profile existence-hiding invariant
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) <noreply@anthropic.com>
This commit is contained in:
parent
4f021d8abc
commit
d0d8423ac6
1 changed files with 47 additions and 2 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue