hub R-138: refuse a token whose one zone also holds another customer's domain (security review)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -1880,6 +1880,18 @@ func (s *Server) cfTokenReachMessage(ctx context.Context, customerID, domain, to
|
||||
s.logger.Printf("[WARN] cloudflare token check for %s: the token's one zone %q does not cover domain %q — save refused", customerID, names[0], domain)
|
||||
return fmt.Sprintf("The Cloudflare API token reaches zone %q, which is not this customer's domain %q. Nothing was saved.", names[0], domain)
|
||||
}
|
||||
// A zone ABOVE the customer's domain may also hold another customer's sibling domain (a.parent.hu and
|
||||
// b.parent.hu under zone parent.hu pass the R-415 guard, which compares the two domains only). So the zone
|
||||
// itself must not equal, contain or lie under any other customer's domain, nor under felhom.eu. Security
|
||||
// review 2026-10-08; pinned by TestR138_ParentZoneHoldingAnotherCustomerRefused.
|
||||
if other, err := s.store.DomainConflict(customerID, names[0]); err != nil || other != "" {
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERROR] cloudflare token check for %s: the zone could not be checked against the other customers: %v — save refused", customerID, err)
|
||||
return "The Cloudflare API token's zone could not be checked against the other customers — nothing was saved. Try again."
|
||||
}
|
||||
s.logger.Printf("[WARN] cloudflare token check for %s: the token's zone %q also covers customer %s's domain — save refused (R-138)", customerID, names[0], other)
|
||||
return fmt.Sprintf("The Cloudflare API token reaches zone %q, which also holds the domain of customer %q — make the customer's own domain a zone of its own. Nothing was saved.", names[0], other)
|
||||
}
|
||||
s.logger.Printf("[INFO] cloudflare token check for %s: the token sees 1 zone (%s), which covers the customer's domain — allowed", customerID, names[0])
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -21,11 +21,12 @@ import (
|
||||
// customer „b" with the account-wide token → FAILS.
|
||||
|
||||
const (
|
||||
tokOwn = "tok-own-zone-0123456789abcdef"
|
||||
tokWide = "tok-account-wide-0123456789abcdef"
|
||||
tokOther = "tok-other-zone-0123456789abcdef"
|
||||
tokNone = "tok-sees-nothing-0123456789abcdef"
|
||||
tokOwn2 = "tok-own-zone-second-0123456789abcdef"
|
||||
tokOwn = "tok-own-zone-0123456789abcdef"
|
||||
tokWide = "tok-account-wide-0123456789abcdef"
|
||||
tokOther = "tok-other-zone-0123456789abcdef"
|
||||
tokNone = "tok-sees-nothing-0123456789abcdef"
|
||||
tokOwn2 = "tok-own-zone-second-0123456789abcdef"
|
||||
tokParent = "tok-parent-zone-0123456789abcdef"
|
||||
)
|
||||
|
||||
type fakeCF struct {
|
||||
@@ -56,6 +57,8 @@ func newFakeCF(t *testing.T) *fakeCF {
|
||||
zones = []string{"neighbour.hu"}
|
||||
case tokNone:
|
||||
zones = nil
|
||||
case tokParent:
|
||||
zones = []string{"parent.hu"}
|
||||
default:
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
w.Write([]byte(`{"success":false,"errors":[{"message":"Invalid API Token"}]}`))
|
||||
@@ -233,3 +236,28 @@ func TestR138_TokenNeverInLogsOrPage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A token whose one zone sits ABOVE the customer's domain and also holds another customer's sibling domain is
|
||||
// refused (security review 2026-10-08: the R-415 guard compares the two domains only, and a.parent.hu and
|
||||
// b.parent.hu do not overlap). Control: the same parent-zone token is accepted when no other customer lives under it.
|
||||
// RED-PROOF: remove the DomainConflict(zone) block in cfTokenReachMessage → customer „a" is stored → FAILS.
|
||||
func TestR138_ParentZoneHoldingAnotherCustomerRefused(t *testing.T) {
|
||||
s, _, _ := r138Server(t)
|
||||
if rr := r138Create(s, "b", "b.parent.hu", ""); rr.Code != http.StatusSeeOther {
|
||||
t.Fatalf("setup: customer b without a token must be created; got %d %s", rr.Code, rr.Body.String())
|
||||
}
|
||||
rr := r138Create(s, "a", "a.parent.hu", tokParent)
|
||||
if rr.Code == http.StatusSeeOther {
|
||||
t.Fatalf("a token for zone parent.hu, which also holds customer b, was accepted")
|
||||
}
|
||||
if !strings.Contains(rr.Body.String(), "also holds the domain of customer") {
|
||||
t.Errorf("the refusal must name the shared zone; got %d", rr.Code)
|
||||
}
|
||||
if _, ok := r138StoredToken(t, s, "a"); ok {
|
||||
t.Errorf("a config was stored for a refused parent-zone token")
|
||||
}
|
||||
s2, _, _ := r138Server(t)
|
||||
if rr := r138Create(s2, "a", "a.parent.hu", tokParent); rr.Code != http.StatusSeeOther {
|
||||
t.Errorf("control: the parent-zone token with no other customer under it must be accepted; got %d %s", rr.Code, rr.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user