fix(store): scope SetDomainStatus by project (IDOR); scheduler reuses DeriveStatus

handleCheck's error branch wrote last_check_status via an id-only UPDATE, so
an authenticated caller's own valid project id paired with a foreign domain
id in the URL could flip a stranger's domain to "error" even though Check
itself is project-scoped and would 404/error out first. Add project_id to
the WHERE clause (queries/domains.sql + generated db/domains.sql.go), thread
projectID through Store/TenantStore/SchedStore SetDomainStatus, and pass pid
from context at both call sites in handleCheck plus the scheduler.

Also collapse checkDomain's inline status derivation in scheduler.go into a
call to service.DeriveStatus, the same helper handleCheck already uses, so
there's a single source of truth for "drift vs in_sync" instead of two
copies that could drift apart.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwxdSt4reTm7Dj1oxRvpP3
This commit is contained in:
2026-07-05 14:40:13 +07:00
parent 784e7bd822
commit 27d70a987e
10 changed files with 149 additions and 35 deletions
+7 -3
View File
@@ -253,9 +253,13 @@ func (s *Store) GetDomainStatus(ctx context.Context, domainID uuid.UUID) (string
}
// SetDomainStatus records the outcome of the most recent check/apply run for
// a domain (e.g. "ok", "drift", "error").
func (s *Store) SetDomainStatus(ctx context.Context, domainID uuid.UUID, status string) error {
return s.q.SetDomainStatus(ctx, db.SetDomainStatusParams{ID: domainID, LastCheckStatus: status})
// a domain (e.g. "ok", "drift", "error"). Scoped by projectID — a domain ID
// belonging to another tenant's project is left untouched (matches zero
// rows) rather than being overwritten, closing an IDOR-on-write where a
// caller's own valid pid + a foreign did could otherwise flip a stranger's
// domain status.
func (s *Store) SetDomainStatus(ctx context.Context, domainID, projectID uuid.UUID, status string) error {
return s.q.SetDomainStatus(ctx, db.SetDomainStatusParams{ID: domainID, LastCheckStatus: status, ProjectID: projectID})
}
// CountDriftDomains returns the current number of domains system-wide whose