Skip to content

fix(certificates): normalize domain case instead of rejecting it - #11

Merged
loks0n merged 1 commit into
mainfrom
fix/normalize-domain-case
Aug 29, 2026
Merged

loks0n merged 1 commit into
mainfrom
fix/normalize-domain-case

Conversation

@loks0n

@loks0n loks0n commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Domain::validate() rejected any domain that was not already lowercase. DNS labels are case-insensitive (RFC 4343), so case carries no meaning — folding it is the correct handling. Rejecting turns a cosmetic difference into a hard failure on data that already exists.

Proxy compounded it. Every public method called select(), which validated into a local variable and threw the result away, so the raw domain still reached the provider:

public function deleteCertificate(string $domain, ?string $domainType = null): void
{
    foreach ($this->select($domain, $domainType) as $provider) {
        $provider->deleteCertificate($domain, $domainType);   // original, not normalized
    }
}

Normalization now happens once at each public entry point, so the canonical value reaches both the appDomain comparison in select() and the provider call.

Incident

Appwrite Cloud 1.45.22, production fra1, 2026-08-28. Deleting a proxy rule whose stored domain had mixed case threw InvalidArgumentException out of Domain::validate(). The throw aborted the delete job before the certificate row was removed, orphaning it.

Verification

  • DomainTest: case folding, plus rejection of empty / scheme / port / path / trailing-slash / underscore
  • ProxyTest: asserts the provider receives the canonical domain, and that a mixed-case app domain still routes to the app provider — which only holds if folding happens before the appDomain comparison
  • Both hunks seen red independently: reverting the Domain fold and reverting the Proxy entry-point normalization each fail the new tests
  • Full suite 77 tests / 180 assertions, Pint, PHPStan — all clean

🤖 Generated with Claude Code

`Domain::validate()` rejected any domain that was not already lowercase.
DNS labels are case-insensitive (RFC 4343), so case carries no meaning and
folding it is the correct handling — rejecting it turns a cosmetic
difference into a hard failure on data that already exists.

This surfaced on Appwrite Cloud's deletes worker, where deleting a rule
whose stored domain had mixed case threw `InvalidArgumentException` and
aborted the job, orphaning the certificate row it was about to remove.

`Proxy` compounded it. Every public method called `select()`, which
validated into a *local* variable and discarded the result, so the raw
domain still reached the provider. Normalization now happens once at each
entry point and the canonical value flows to both the `appDomain`
comparison and the provider call.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Aug 28, 2026

Copy link
Copy Markdown

Greptile Summary

This PR changes domain validation from rejecting uppercase hostnames to returning their lowercase canonical form. It also normalizes domains at every proxy lifecycle entry point so routing comparisons and delegated provider calls consistently receive the canonical hostname.

  • Canonicalizes domain case while retaining malformed-hostname validation.
  • Normalizes all five proxy certificate operations before routing and delegation.
  • Adds focused tests for mixed-case routing, provider forwarding, and malformed domains.

Confidence Score: 5/5

The PR appears safe to merge with no actionable correctness, security, or quality issues identified.

The changed validation preserves hostname checks while canonicalizing case, and every reachable proxy routing and delegation path now consistently uses the returned canonical domain.

Important Files Changed

Filename Overview
src/Cdn/Domain.php Canonicalizes hostname case before validation while preserving rejection of malformed hostname forms.
src/Cdn/Certificates/Provider/Proxy.php Moves normalization to each public lifecycle entry point so both provider selection and forwarding use the canonical domain.
tests/Cdn/DomainTest.php Covers lowercase canonicalization and representative malformed-domain inputs.
tests/Cdn/Certificates/Provider/ProxyTest.php Verifies mixed-case application-domain routing and normalized custom-provider delegation.

Reviews (1): Last reviewed commit: "fix(certificates): normalize domain case..." | Re-trigger Greptile

@loks0n
loks0n merged commit 320ef34 into main Aug 29, 2026
4 checks passed
loks0n added a commit to appwrite/appwrite that referenced this pull request Aug 29, 2026
Picks up the domain-case normalization fix (utopia-php/cdn#11). Without
this the `^0.0.7` constraint here also pins downstream consumers, so
Appwrite Cloud cannot move to 0.0.8 while server-ce holds 0.0.7.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant