domains: sidecar names are reserved to everyone (#209 review)
Astra round 1, P1: a deny entry naming the sidecar suffix made its names platform names, and the API alias branch never checked the suffix, so --platform could claim one. domainHostPolicy now reserves the suffix outright, which also closes the alias branch for members. The service-token test mints as the ops owner, since a service principal carries its minter's address. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Human
Prompt
Can we address carlos/platform #209 ?
From this commit's own message. The prompt ledger has no entries for it — which is what a squashed, rebased or amended commit looks like, and what every commit looks like in a repo without a ledger.
Summary
Reserve .sidecar.internal names in domainHostPolicy; tighten the service-token test.
2 files changed,
+29
−2
internal/console/domainplatform_test.go+20 −2internal/console/domainpolicy.go+9 −0
CI passed — run details
diff --git a/internal/console/domainplatform_test.go b/internal/console/domainplatform_test.go| index a56f7cb73..9a31576cc 100644 |
| --- a/internal/console/domainplatform_test.go |
| +++ b/internal/console/domainplatform_test.go |
| @@ -59,6 +59,7 @@ func TestDomainHostPolicyMatrix(t *testing.T) { |
| {"edge.apps.test", hostReserved}, // the steering host |
| {"apps.test", hostReserved}, |
| {"site.bab.apps.test", hostReserved}, // a tenant's address |
| + {"web.sidecar.internal", hostReserved}, |
| } |
| for _, c := range cases { |
| if got := s.domainHostPolicy(c.host, "bab"); got != c.want { |
| @@ -108,10 +109,11 @@ func TestPlatformAttachRefusesMembers(t *testing.T) { |
| } |
| // TestPlatformAttachRefusesServices: an app token is never an operator, flag |
| -// or no flag. |
| +// or no flag, even one the ops owner minted (a service principal carries its |
| +// minter's address). |
| func TestPlatformAttachRefusesServices(t *testing.T) { |
| fx, _, _ := platformFixture(t, "code-plat-svc") |
| - tok, err := fx.st.NewAppToken(OpsAccountID, "titogo", "alice@keymail.dev", "signup", time.Now()) |
| + tok, err := fx.st.NewAppToken(OpsAccountID, "titogo", "paul@keymail.dev", "signup", time.Now()) |
| if err != nil { |
| t.Fatal(err) |
| } |
| @@ -192,12 +194,16 @@ func TestPlatformAttachBySystemOperator(t *testing.T) { |
| func TestPlatformAttachReservedStaysRefused(t *testing.T) { |
| fx, _, paul := platformFixture(t, "code-plat-reserved") |
| fx.srv.cfg.Origin = "https://console.carloku.test" |
| + // A deny entry naming the sidecar suffix must not make it a platform |
| + // name the door lifts. |
| + fx.srv.cfg.DenyDomains = append(fx.srv.cfg.DenyDomains, "sidecar.internal") |
| for _, host := range []string{ |
| "console.carloku.test", |
| "hub.carloku.test", |
| "edge.oncarlos.test", |
| "oncarlos.test", |
| "titogo.bab.oncarlos.test", |
| + "web.sidecar.internal", |
| } { |
| code, _, out := rawReq(t, http.MethodPost, domainsURL(fx, "bab", "titogo"), paul, |
| platformAttachBody(host, "go.tito.io")) |
| @@ -293,3 +299,15 @@ func TestPlatformBrowserDoors(t *testing.T) { |
| } |
| claimAbsent(t, fx, "carloku.test") |
| } |
| + |
| +// TestAPIAttachRefusesSidecarNames: the API door's alias branch refuses a |
| +// sidecar name to a member too, with or without a deny entry covering it. |
| +func TestAPIAttachRefusesSidecarNames(t *testing.T) { |
| + fx, alice, _ := platformFixture(t, "code-plat-sidecar") |
| + code, _, out := rawReq(t, http.MethodPost, domainsURL(fx, "bab", "titogo"), alice, |
| + attachBody("web.sidecar.internal", "go.tito.io")) |
| + if code != http.StatusBadRequest { |
| + t.Errorf("member alias attach of a sidecar name = %d %s, want 400", code, out) |
| + } |
| + claimAbsent(t, fx, "web.sidecar.internal") |
| +} |
diff --git a/internal/console/domainpolicy.go b/internal/console/domainpolicy.go| index a5f7e2aa7..052a5c9c0 100644 |
| --- a/internal/console/domainpolicy.go |
| +++ b/internal/console/domainpolicy.go |
| @@ -6,6 +6,7 @@ import ( |
| "strings" |
| "github.com/carlosframework/platform/internal/domains" |
| + "github.com/carlosframework/platform/internal/provision" |
| ) |
| // directAppsDomainHost reports whether host is one label under the apps |
| @@ -65,6 +66,14 @@ func (s *Server) domainHostPolicy(host, account string) hostPolicy { |
| if slices.Contains(s.consoleHosts(), host) { |
| return hostReserved |
| } |
| + // provision.SidecarSuffix names a sidecar process, never a hostname |
| + // anyone serves. attachDomain refuses it with its own message; this |
| + // covers the API door's alias branch, which does not reach that check, |
| + // and keeps a deny entry naming the suffix from turning it into a |
| + // platform name the operator's door would lift (Astra review, #209). |
| + if strings.HasSuffix(host, provision.SidecarSuffix) { |
| + return hostReserved |
| + } |
| allowed := domains.ValidSqid(account) && slices.Contains(s.cfg.AppsDomainAliasAccounts, account) && s.directAppsDomainHost(host) |
| if allowed { |
| domain := strings.ToLower(strings.TrimSpace(s.cfg.AppsDomain)) |