Skip to content

fix(api): reclaim stale domain claims with neutral conflicts - #446

Merged
duyetbot merged 1 commit into
mainfrom
fix/security-domain-uniqueness
Sep 16, 2026
Merged

duyetbot merged 1 commit into
mainfrom
fix/security-domain-uniqueness

Conversation

@duyetbot

@duyetbot duyetbot commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Finish [audit] P3: global domain uniqueness leaks cross-tenant claims; readJsonPath accepts $.constructor #436 Part 1 while keeping database-enforced global one-domain/one-project ownership.
  • Return neutral DOMAIN_UNAVAILABLE for conflicts outside the requesting project; retain DOMAIN_EXISTS only within that project.
  • Atomically reclaim never-verified pending/failed claims after 7 days from creation, with fresh ID/token/timestamps and reset verification/SSL. Verification retries do not extend the deadline; verified claims remain protected.
  • Document the one-owner rule and lifecycle. Neutral errors reduce explicit disclosure but do not make success and failure indistinguishable.
  • Part 2 is already on main via fix(api): restrict JSON paths to safe own properties #441 (99aa710): verified ancestry, unchanged files, and passing JSON-path regression tests. No redo.

Validation

  • API Biome check: passed
  • API TypeScript check: passed
  • Full API Vitest suite: 32 files, 513 tests passed
  • Domain coverage includes cross-project neutral errors, pending/failed reclaim, exact 7-day boundary, protected verified claims, rotated identity/token, old-owner access, and concurrent claims.
  • TypeScript SDK typecheck/build/tests: passed
  • Local SDK examples could not complete because Python httpx is missing; PR CI installs Python SDK dev dependencies and will be awaited before handoff.

Closes #436

Do not merge this PR as part of this task. Release-please #442 is untouched.

Co-Authored-By: Duyet Le me@duyet.net
Co-Authored-By: duyetbot bot@duyet.net

🤖 Generated with Claude Code

Summary by Sourcery

Harden custom-domain claims by neutralizing cross-project conflicts and safely reclaiming stale unverified ownership after seven days.

Bug Fixes:

  • Prevent custom-domain ownership conflicts from exposing information about domains owned by other projects by returning neutral DOMAIN_UNAVAILABLE errors.
  • Allow pending or failed custom-domain claims that have never been verified to be atomically reclaimed after seven days without extending the deadline through retries.
  • Preserve global one-domain/one-project ownership and protect verified or historically verified claims from reclamation.

Enhancements:

  • Document custom-domain ownership, conflict behavior, verification lifecycle, and unverified-claim reclamation rules.

Documentation:

  • Add custom-domain lifecycle and ownership guidance to the API reference and documentation index.

Tests:

  • Add regression coverage for neutral cross-project conflicts, stale-claim reclamation, exact expiry boundaries, protected verified claims, claim identity rotation, ownership access, normalized domains, and concurrent claims.

Chores:

  • Record completion of the targeted security follow-up in the maintenance plan.

Preserve global one-owner uniqueness with an atomic conditional upsert. Reclaim never-verified pending/failed claims after seven days, rotate claim identity, and avoid disclosing other projects in conflict errors. Document the lifecycle and cover tenant isolation, deadlines, and concurrent adds. JSON-path hardening from #441 remains unchanged.

Co-Authored-By: Duyet Le <me@duyet.net>
Co-Authored-By: duyetbot <bot@duyet.net>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @duyetbot, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 5 days and 21 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@coderabbitai

coderabbitai Bot commented Sep 16, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: a5bba8d7-61d4-497b-95ca-48c34d3d800d


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Sep 16, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR preserves database-enforced one-domain/one-project ownership while moving domain creation to an atomic guarded upsert. It returns neutral errors for claims owned by other projects, reclaims never-verified pending or failed claims after seven days using rotated identity and verification credentials, adds extensive concurrency and lifecycle regression coverage, and documents the resulting API and ownership rules.

Sequence diagram for atomic custom-domain claiming

sequenceDiagram
    participant Client
    participant API
    participant DomainsService
    participant Database

    Client->>API: POST /api/v1/projects/:projectId/domains
    API->>DomainsService: createDomain(db, projectId, domain)
    DomainsService->>Database: insert(...).onConflictDoUpdate(...).returning(...)
    alt New domain or reclaimable unverified claim
        Database-->>DomainsService: claimed id
        DomainsService-->>API: verification instructions
        API-->>Client: 201 Created
    else Active claim in requesting project
        DomainsService->>Database: select own claim
        Database-->>DomainsService: matching claim
        DomainsService-->>API: DOMAIN_EXISTS
        API-->>Client: 409 DOMAIN_EXISTS
    else Claim owned by another project or protected claim
        DomainsService->>Database: select own claim
        Database-->>DomainsService: no matching claim
        DomainsService-->>API: DOMAIN_UNAVAILABLE
        API-->>Client: 409 DOMAIN_UNAVAILABLE
    end
Loading

State diagram for custom-domain claim lifecycle

stateDiagram-v2
    [*] --> pending: createDomain
    pending --> verified: verification succeeds
    pending --> failed: verification fails
    failed --> verified: verification succeeds
    pending --> pending: retry verification
    failed --> failed: retry verification
    pending --> pending: reclaim after 7 days
    failed --> pending: reclaim after 7 days
    verified --> [*]: owner removes domain
    pending --> pending: replacement gets new ID, token, timestamps
    failed --> pending: SSL disabled and credentials rotated
Loading

File-Level Changes

Change Details Files
Implements atomic global domain claiming with neutral cross-project conflicts and seven-day reclamation of eligible unverified claims.
  • Replaces pre-check plus insert with database upsert guarded by pending/failed status, null verification, and creation-time TTL.
  • Rotates claim ID, token, and timestamps on reclamation while resetting verification and SSL state.
  • Preserves verified and historically verified claims and maps ownership conflicts to project-scoped or neutral errors.
  • Handles concurrent new claims and stale takeovers without duplicate owners or unexpected failures.
packages/api/src/services/domains.ts
packages/api/src/routes/domains.ts
packages/api/test/domains.test.ts
Adds regression coverage for domain ownership privacy and claim lifecycle behavior.
  • Covers cross-project neutral responses without owner or claim metadata disclosure.
  • Tests pending/failed reclamation, exact seven-day boundary, retry deadline preservation, normalization, old-owner access loss, and concurrent claims.
packages/api/test/domains.test.ts
Documents custom-domain ownership, conflict semantics, verification, and reclamation lifecycle.
  • Adds a Custom Domains API reference section with endpoint behavior and neutral error examples.
  • Links custom-domain documentation from the documentation index.
  • Records completion of the security follow-up and the no-op verification of Part 2 in the maintenance plan.
docs/api-reference.md
docs/INDEX.md
PLAN.md

Assessment against linked issues

Issue Objective Addressed Explanation
#436 Prevent cross-tenant domain enumeration while preserving global one-domain/one-project uniqueness, by returning a neutral conflict response for domains owned by another project.
#436 Provide reclamation for never-verified pending or failed domain claims after seven days, without reclaiming verified or historically verified claims, and document the one-owner rule and claim lifecycle.
#436 Harden readJsonPath so reserved prototype-related segments such as $.constructor, $.prototype, and $.proto are rejected, including through runtime defense-in-depth checks.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@duyetbot
duyetbot merged commit bd35c47 into main Sep 16, 2026
6 checks passed
@duyetbot
duyetbot deleted the fix/security-domain-uniqueness branch September 16, 2026 22:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[audit] P3: global domain uniqueness leaks cross-tenant claims; readJsonPath accepts $.constructor

1 participant