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) {
|
func TestInvariant_ProfileAdminEndpointsHideFromNonAdmins(t *testing.T) {
|
||||||
// These checks lock in the existence-hiding property: non-admins must
|
// These checks lock in the existence-hiding property: non-admins must
|
||||||
// see 404, never 403, so they can't probe which paths exist.
|
// see 404, never 403, so they can't probe which admin-only resources
|
||||||
t.Skip("requires the profile handler dispatcher entry point; skip until the refactor confirms ServeProfile signature")
|
// 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
|
// dump prints the rec body when t.Logf would help debugging — used in
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue