From fed482de82ca11d5e679f21ca47da2d2c7a1225c Mon Sep 17 00:00:00 2001 From: Joe Amditis <6799804+jamditis@users.noreply.github.com> Date: Mon, 27 Jul 2026 07:26:50 -0400 Subject: [PATCH 1/3] Stop the HTTPS runbook from recommending a setting that would 526 the site Step 4 told the reader to keep Cloudflare's SSL mode at "Full (strict)" through a cert restore. The zone actually runs `full`, and strict would break the site rather than harden it: GitHub Pages answers SNI tools.amditis.tech with a cert for CN=*.github.io, which validates cleanly but does not cover the hostname, so strict rejects the name and visitors get HTTP 526. The mode is zone-wide, so the blast radius is wider than this repo: system and mooc share the same Pages origin and the same mismatch, and ccm and codiac pull their own third-party origins. Records the verification command next to the claim so the next reader can confirm the origin cert before touching the switch, matching what step 3 already does for the GitHub cert state. wake-20260727T0705-35b527 --- CLAUDE.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index c7150f8..4aa73c9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -113,7 +113,21 @@ Leave it as-is for a docs site. To restore a GitHub-issued cert if a clean GitHu 1. Gray-cloud the DNS record in Cloudflare (DNS only, no proxy) so GitHub's HTTP-01 ACME challenge reaches Pages directly instead of Cloudflare's edge. 2. In repo Settings > Pages, remove the custom domain, save, then re-enter `tools.amditis.tech` and save. This step is required, not optional: GitHub only starts cert provisioning when the custom domain is set or changed, and it already dropped the tracking record (`https_certificate: null`), so gray-clouding alone never restarts a job. Re-adding the domain is what re-fires ACME. 3. Wait about 10 minutes, then confirm issuance with `gh api repos/jamditis/tools/pages --jq .https_certificate.state` (it should move off `null` to `approved`). -4. Re-enable the Cloudflare proxy (orange cloud) to get the edge caching and DDoS protection back. Keep Cloudflare's SSL mode at "Full (strict)" throughout. +4. Re-enable the Cloudflare proxy (orange cloud) to get the edge caching and DDoS protection back. Leave the zone's SSL mode on "Full" — do not set "Full (strict)". See below. + +### Do not switch the zone to Full (strict) + +The `amditis.tech` zone runs SSL mode `Full`, which encrypts the Cloudflare-to-origin leg without checking that the origin cert matches the hostname. That last part is what makes it work here, so `Full (strict)` is not a drop-in upgrade: + +- Connecting to a Pages IP with SNI `tools.amditis.tech` returns a cert for `CN=*.github.io`. It chains cleanly (`Verify return code: 0`), but a `*.github.io` wildcard does not cover `tools.amditis.tech`, so strict rejects the name mismatch and every visitor gets HTTP 526 instead of the site. Check it before changing anything: + + ```bash + openssl s_client -connect 185.199.108.153:443 -servername tools.amditis.tech /dev/null | openssl x509 -noout -subject + ``` + +- The mode is a zone setting rather than a per-hostname one, so it is not tools' alone to change. Of the zone's 33 proxied records, 26 are cloudflared tunnel CNAMEs that pull no third-party origin. Of the remaining 7: `tools`, `system`, and `mooc` all CNAME to `jamditis.github.io` and share the mismatch above, so they break together; `ccm` (Firebase) and `codiac` (Cloudflare Pages) point at third-party origins and each need the same check; `codex` and `upload.social` are `AAAA` records on the `100::` discard prefix, so they have no origin to validate and strict does not affect them. + +Strict only becomes safe for this hostname *after* step 3 above confirms GitHub issued a real cert for `tools.amditis.tech`, and only once `ccm` and `codiac` have passed the same check. Until then `Full` is the correct setting. Background: issue #61. From 9c8733800930f42ce860a322f0df3937a73fde9a Mon Sep 17 00:00:00 2001 From: Joe Amditis <6799804+jamditis@users.noreply.github.com> Date: Mon, 27 Jul 2026 07:36:57 -0400 Subject: [PATCH 2/3] Make the strict-mode prerequisites complete and the check conclusive Two findings from the review, both cases of a claim reaching past its evidence. The prerequisite list named ccm and codiac but omitted system and mooc, even though the paragraph above says those two share tools' mismatch and break together. An operator who provisioned tools, checked ccm and codiac, and trusted that sentence would have flipped the switch and left system and mooc serving 526. All five now have to pass. The verification command printed only the subject, which cannot decide hostname coverage: a SAN can cover a name the CN does not, and s_client skips hostname verification unless asked. It now asks, via -verify_hostname, and reports the verdict openssl reaches -- today 62 (hostname mismatch). Records the cert's actual SANs, and a control run against jamditis.github.io that returns 0 (ok), so the reader can tell a real mismatch from a broken invocation. --- CLAUDE.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 4aa73c9..655bee4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -119,15 +119,18 @@ Leave it as-is for a docs site. To restore a GitHub-issued cert if a clean GitHu The `amditis.tech` zone runs SSL mode `Full`, which encrypts the Cloudflare-to-origin leg without checking that the origin cert matches the hostname. That last part is what makes it work here, so `Full (strict)` is not a drop-in upgrade: -- Connecting to a Pages IP with SNI `tools.amditis.tech` returns a cert for `CN=*.github.io`. It chains cleanly (`Verify return code: 0`), but a `*.github.io` wildcard does not cover `tools.amditis.tech`, so strict rejects the name mismatch and every visitor gets HTTP 526 instead of the site. Check it before changing anything: +- Connecting to a Pages IP with SNI `tools.amditis.tech` returns a cert for `CN=*.github.io`, carrying SANs for `*.github.com`, `*.github.io`, `*.githubusercontent.com` and those three apexes. None of them cover `tools.amditis.tech`, so strict rejects the name and every visitor gets HTTP 526 instead of the site. Let openssl make that call rather than eyeballing the subject — a SAN can cover a hostname the CN does not, and `s_client` skips hostname verification unless asked for it: ```bash - openssl s_client -connect 185.199.108.153:443 -servername tools.amditis.tech /dev/null | openssl x509 -noout -subject + openssl s_client -connect 185.199.108.153:443 -servername tools.amditis.tech \ + -verify_hostname tools.amditis.tech /dev/null | grep "Verify return code" ``` + Today that prints `62 (hostname mismatch)`, which is the 526 in advance. `0 (ok)` means strict would pass for this hostname. To confirm the command itself discriminates, run it with `jamditis.github.io` in both places — that prints `0 (ok)`. + - The mode is a zone setting rather than a per-hostname one, so it is not tools' alone to change. Of the zone's 33 proxied records, 26 are cloudflared tunnel CNAMEs that pull no third-party origin. Of the remaining 7: `tools`, `system`, and `mooc` all CNAME to `jamditis.github.io` and share the mismatch above, so they break together; `ccm` (Firebase) and `codiac` (Cloudflare Pages) point at third-party origins and each need the same check; `codex` and `upload.social` are `AAAA` records on the `100::` discard prefix, so they have no origin to validate and strict does not affect them. -Strict only becomes safe for this hostname *after* step 3 above confirms GitHub issued a real cert for `tools.amditis.tech`, and only once `ccm` and `codiac` have passed the same check. Until then `Full` is the correct setting. +Because the switch is zone-wide, strict only becomes safe once *every* proxied hostname with a third-party origin passes that check — not just this one. `tools`, `system`, and `mooc` fail it identically today, so each needs a real GitHub cert per step 3 above; `ccm` and `codiac` need the same verification against their own origins. Provisioning `tools` alone and flipping the switch would leave `system` and `mooc` serving 526. Until all five pass, `Full` is the correct setting. Background: issue #61. From 9f9c4b5e1a5000db162aa1b5224b42a78a9a224a Mon Sep 17 00:00:00 2001 From: Joe Amditis <6799804+jamditis@users.noreply.github.com> Date: Mon, 27 Jul 2026 11:36:03 -0400 Subject: [PATCH 3/3] Note that a Configuration Rule can scope an SSL exception The doc called the SSL mode a zone setting rather than a per-hostname one, which ruled out a staged migration that is actually available: a Configuration Rule can override the mode for matching hostnames, so the github.io hosts could be held at Full while the default moves to strict. Records it as an option with the entitlement caveat and the check, and keeps the zone-wide route as the recommendation, since an override is a rule that stops protecting the moment someone edits it on a leg nobody watches. --- CLAUDE.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 655bee4..a92bb16 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -128,9 +128,13 @@ The `amditis.tech` zone runs SSL mode `Full`, which encrypts the Cloudflare-to-o Today that prints `62 (hostname mismatch)`, which is the 526 in advance. `0 (ok)` means strict would pass for this hostname. To confirm the command itself discriminates, run it with `jamditis.github.io` in both places — that prints `0 (ok)`. -- The mode is a zone setting rather than a per-hostname one, so it is not tools' alone to change. Of the zone's 33 proxied records, 26 are cloudflared tunnel CNAMEs that pull no third-party origin. Of the remaining 7: `tools`, `system`, and `mooc` all CNAME to `jamditis.github.io` and share the mismatch above, so they break together; `ccm` (Firebase) and `codiac` (Cloudflare Pages) point at third-party origins and each need the same check; `codex` and `upload.social` are `AAAA` records on the `100::` discard prefix, so they have no origin to validate and strict does not affect them. +- The mode is the zone's default rather than a per-hostname setting, so it is not tools' alone to change. Of the zone's 33 proxied records, 26 are cloudflared tunnel CNAMEs that pull no third-party origin. Of the remaining 7: `tools`, `system`, and `mooc` all CNAME to `jamditis.github.io` and share the mismatch above, so they break together; `ccm` (Firebase) and `codiac` (Cloudflare Pages) point at third-party origins and each need the same check; `codex` and `upload.social` are `AAAA` records on the `100::` discard prefix, so they have no origin to validate and strict does not affect them. -Because the switch is zone-wide, strict only becomes safe once *every* proxied hostname with a third-party origin passes that check — not just this one. `tools`, `system`, and `mooc` fail it identically today, so each needs a real GitHub cert per step 3 above; `ccm` and `codiac` need the same verification against their own origins. Provisioning `tools` alone and flipping the switch would leave `system` and `mooc` serving 526. Until all five pass, `Full` is the correct setting. +Changing that default is therefore only safe once *every* proxied hostname with a third-party origin passes the check — not just this one. `tools`, `system`, and `mooc` fail it identically today, so each needs a real GitHub cert per step 3 above; `ccm` and `codiac` need the same verification against their own origins. Provisioning `tools` alone and flipping the default would leave `system` and `mooc` serving 526. Until all five pass, `Full` is the correct default. + +A staged migration is possible, because a Configuration Rule can override the SSL mode for matching hostnames and so scope an exception to the default. That would mean setting the default to `Full (strict)` and adding a rule that holds the `jamditis.github.io` hosts at `Full` until they have real certs, rather than waiting on all five. Confirm the SSL setting is actually offered before planning around it — `amditis.tech` is on the Free plan, where the Configuration Rules allowance is small, and the personal API token cannot read the ruleset phase (`GET /zones//rulesets/phases/http_config_settings/entrypoint` returns `request is not authorized`), so check in the dashboard under Rules > Configuration Rules or with a token scoped for it. + +The zone-wide route is still the recommendation here: it has nothing to maintain, while the override adds a rule that silently stops protecting the moment someone edits or reorders it, on the leg between Cloudflare and the origin where nobody is watching. Background: issue #61.