fix(namecheap): lowercase HostName for setHosts API compat#39
Merged
Conversation
Namecheap's domains.dns.setHosts API validates HostName values case-sensitively and rejects uppercase letters with: 2050900: INVALID_NAME: Host name: '_<UPPERCASE_HEX>' is invalid. DNS itself is case-insensitive at resolution time, but Namecheap's input validator is not. Sectigo's CSR-hash CNAME response delivers the host portion as uppercase MD5 hex (e.g. `_442EBF5636AF...`). Without normalization, every Sectigo CSR-hash renewal blows up at the publish step. Confirmed against Namecheap's API directly with a manual setHosts call: identical request body with the uppercase hostname returns 2050900; lowercasing the host portion is the only difference and the call succeeds. The accepted CNAME already on oneiric.dev (from a prior provisioning) uses lowercase hex, matching this behavior. Fix: split_record_name normalizes subdomain + sld + tld to ASCII lowercase. SLD/TLD were already-lowercase by convention but cheap to defend against; the real workhorse is the subdomain lowercase. Sectigo's validator does a case-insensitive DNS lookup so the record still resolves correctly. Test pinning the case normalization against the exact Sectigo CSR-hash hostname shape (`_<UPPERCASE_HEX>.<MixedCase.Tld>`).
albedosehen
added a commit
that referenced
this pull request
May 9, 2026
rota's `get_info` was looking for `<CertificateReturned>` element
text and `<CACertificate>` element text. Neither matches Namecheap's
actual `ssl.getInfo&Returncertificate=true` response, which carries
`CertificateReturned` as an ATTRIBUTE on `<Certificates>` and packs
PEMs in nested `<Certificate>` elements:
<Certificates CertificateReturned="true" ReturnType="INDIVIDUAL">
<Certificate><![CDATA[LEAF_PEM]]></Certificate>
<CaCertificates>
<Certificate Type="INTERMEDIATE">
<Certificate><![CDATA[INTERMEDIATE_1_PEM]]></Certificate>
</Certificate>
...
</CaCertificates>
</Certificates>
Result: cert_pem and chain_pem both empty, `is_issued()` false,
polling never terminates even when status==active. So PR #40's
chain-follow lands on the right SSL ID but `await_issuance` still
hangs at the extraction step. Found by extracting the cert manually
out of band when `getInfo` returned status=active for oneiric.dev's
in-flight order: rota's parser yielded empty strings even though
the PEMs were sitting right there in the response.
Fix: new `ApiResponse::pem_blocks(label)` method scans the raw
response for `-----BEGIN <label>-----`...`-----END <label>-----`
armor and returns each block in document order. `get_info` calls
`pem_blocks("CERTIFICATE")`; first block is the leaf, rest are the
chain (concatenated with newlines). The CSR present in the same
response is safely skipped because its label is "CERTIFICATE
REQUEST" and `BEGIN CERTIFICATE-----` doesn't substring-match
`BEGIN CERTIFICATE REQUEST-----`.
This is the 6th and (hopefully) final layer in the rota+Namecheap
end-to-end renewal pipeline, after PRs #36 (reverted), #37 (CDATA
unwrap), #38 (DnsCname variant), #39 (lowercase HostName), #40
(ReplacedBy chain + Status XML path). Tests: 3 new in xml::tests
covering the leaf+chain extraction, the CSR-skip rule, and the
empty-input edge case. Total daemon test count 111 (was 108).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Namecheap's
domains.dns.setHostsvalidates HostName case-sensitively and rejects uppercase letters with2050900: INVALID_NAME. DNS itself is case-insensitive at resolution time, but Namecheap's input validator is not.Sectigo's CSR-hash CNAME response delivers the host portion as uppercase MD5 hex (e.g.
_442EBF5636AF...). Without normalization, every Sectigo CSR-hash renewal via PR #38 dies at the publish step.Verified directly
Manual
setHostscall against Namecheap, same payload, only difference being host case:2050900: INVALID_NAMEExisting accepted CNAME already on oneiric.dev (from prior provisioning):
_3150e77263e8e7664a9f0d59993747de— lowercase. Pattern confirmed.Fix
split_record_namelowercases subdomain, sld, and tld viato_ascii_lowercase. New unit test pins the exact Sectigo shape.Verified
cargo fmt --all --checkcleancargo clippy --workspace --all-targets -- -D warningscleancargo test --workspace --locked153 tests pass (108 daemon, +1 new)