Default numeric hosts to http, named hosts to https; add a README - #4
Merged
Merged
Conversation
A target with no scheme now gets one chosen by what the host looks like: a numeric host is a device on the local network, which rarely has a certificate, while a named host should be https. example.com -> https://example.com example.com:8080 -> https://example.com:8080 192.168.1.50:8123 -> http://192.168.1.50:8123 Applied in the three places that defaulted a scheme, which previously disagreed: index.html used http, forward.js and metadata.js used https. Two latent bugs fall out of the same change. index.html treated any colon as a scheme, so a host and port such as 192.168.1.50:8123 was left without one entirely. metadata.js tested startsWith("http"), so a hostname beginning with those letters, like httpbin.org, was treated as already carrying a scheme. Both now share hasScheme(), which distinguishes "example.com:8080" from "tel:5551234" by whether the part before the colon is dotted and the part after is purely a number. The README covers the path grammar, the routes, the fallback probing, the Cast setup including the published app id, and the routing-order constraints in netlify.toml that are easy to break. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014nP4CW7aH6ei3eHbBb1oYd
Numeric hosts already defaulted to http. localhost, *.localhost and *.local names are this machine or a device on the local network and are in the same position: they rarely have certificates, so https just fails to connect. The host test is now case-insensitive. Two bugs found while testing this. localhost:3000 was getting no scheme at all. hasScheme only recognised a host and port when the name was dotted, so "localhost:" read as a scheme, the same trap "example.com:8080" fell into. The rule now also accepts localhost as a host name, while tel:5551234 stays a scheme. A short single-label hostname could be decoded as base64 and mangled: "mylocal" decoded to binary and produced https://)hq. A bare token is now only accepted as base64 when it decodes to printable text containing a dot or a colon, which every real encoded URL does. This predates the scheme work; it was only visible once bare hostnames were being tested. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014nP4CW7aH6ei3eHbBb1oYd
alcor
marked this pull request as ready for review
September 19, 2026 14:06
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.
Scheme defaulting
A target with no scheme now gets one chosen by what the host looks like. Numeric hosts,
localhostand.localnames are this machine or a device on the local network, which rarely have certificates. Everything else gets https.example.comhttps://example.comexample.com:8080https://example.com:8080192.168.1.50:8123http://192.168.1.50:8123localhost:3000http://localhost:3000nas1.localhttp://nas1.localApplied in the three places that defaulted a scheme and previously disagreed with each other:
index.htmlused http,forward.jsandmetadata.jsused https.Telling a host and port from a scheme takes care, since
example.comis made of characters that are legal in a scheme. It reads as host-and-port only when the name is dotted or islocalhostand what follows the colon is purely a number — solocalhost:3000is a host andtel:5551234is still a scheme.Four bugs fixed
Two were the reason for the change, two were found by testing it.
index.htmltreated any colon as a scheme.if (url.indexOf(":") < 0)meant192.168.1.50:8123was left with no scheme at all — exactly the input this rule is for.metadata.jstestedstartsWith("http"). A hostname beginning with those letters, likehttpbin.org, was emitted bare intoog:url. Live in production, not hypothetical — before/after below.localhost:3000got no scheme. My own first cut: the host-and-port rule only recognised dotted names, solocalhost:read as a scheme. Same trap as Make og:image work for SVG: pasted markup, svg: payloads, and UTF-8 #1, one layer down.mylocalis accidentally valid base64 and decoded to binary, producinghttps://)hq©. A bare token is now only accepted as base64 when it decodes to printable text containing a dot or colon, which every real encoded URL does. This predates the PR; it only became visible once bare hostnames were being tested.Testing
normalizeTarget— 32 assertions: the local-host set (localhost, with port, with path, uppercase,*.localhost,.local, uppercase.LOCAL), numeric hosts with and without port and path, loopback, named hosts including near-misses (notlocalhost.com,local.example.com), accidental-base64 tokens (mylocal,test,intranet,wiki,abcd), real base64 that must still decode, and schemes that must survive (http,https,tel,sms,mailto,hass://, the:hostshorthand).netlify dev:/view/loadslocalhost:9001and127.0.0.1:9001over http;example.com:8080resolves to https;index.html'sprompt()storeshttp://192.168.1.50:8123for a numeric answer andhttps://example.com:8080for a named one./view/and sender checks, 10 receiver dispatch checks including DashCast namespace semantics.Edge function, verified on the deploy preview
metadata.jscan't run locally (the sandbox can't fetch Deno's bootstrap), so it was checked against the preview with aTwitterbotuser-agent, with production alongside for before/after:…/u/<x>/httpbin.orgog:url="httpbin.org"— no schemehttps://httpbin.org192.168.1.50:8123https://192.168.1.50:8123http://192.168.1.50:8123localhost:3000http://localhost:3000nas1.localhttp://nas1.localexample.com:8080https://example.com:8080intranethttps://intranetaHR0cHM6…https://example.com/a?b=cREADME
Replaces the single-line file. Covers the path grammar and its three encodings, the route table, fallback probing, the Cast setup including the published app id
CCAB7FD4and the DashCast namespace compatibility, the repo layout, and local development.It also writes down two routing constraints that are easy to break silently: rules in
netlify.tomlmust stay above the/*catch-all with/cast/receiverabove/cast/*, and the metadata edge function must keep skipping/view/and/cast/or it will treat them as link previews and redirect bots to their targets.🤖 Generated with Claude Code
https://claude.ai/code/session_014nP4CW7aH6ei3eHbBb1oYd