Prompt and context
A Go HTTP service upgraded from 1.25 to 1.26 and found that historical values http://localhost:80:80/ and http://::1/ now fail, while http://[::1]/ still succeeds. Explain the net/url.Parse change, its effect on proxies and SSRF defenses, and a migration that does not depend on a permanent compatibility switch.
Go 1.26 defaults urlstrictcolons to 1, rejecting extra colons in a host subcomponent that cannot be interpreted as host:port; the behavior was backported to Go 1.25.2 and 1.24.8. RFC 3986 defines host as an IP-literal, IPv4 address, or registered name, with the port separated by one colon, so textual IPv6 belongs inside brackets.
What the interviewer is testing
- Can you explain from URI syntax why extra colons are ambiguous or invalid?
- Can you distinguish unbracketed IPv6, valid
[IPv6]:port, hostnames, and invalid ports? - Can you handle configuration migration, third-party URLs, proxy rewriting, and logs instead of disabling validation?
- Can you explain that
GODEBUG=urlstrictcolons=0is a temporary compatibility tool, not an input-validation policy? - Can you prove parsing, connection, redirect, and SSRF behavior with regression tests after the upgrade?
Questions to clarify first
- Do failing URLs come from human configuration, a database, user input, or a third-party callback? Their trust levels differ.
- Does the application combine
url.Parse,url.Hostname, andurl.Port? Different parsing APIs change behavior. - Must internal IPv6 be supported, is a port required, and can a proxy rewrite Host?
- Can old values be fixed online, or can they be validated and rejected in a pre-release batch?
- Does the service use URL parsing for access control, tenant routing, or SSRF defenses?
30-second answer
"I would classify failures by source and host shape. Go 1.26 enables urlstrictcolons by default, so unbracketed IPv6 and strings with multiple host colons are rejected; the explicit form is [::1], with a port written as [::1]:8080. I would repair and reject bad values at configuration boundaries, add parsing and network regression tests, and watch the failure rate in a canary. GODEBUG=urlstrictcolons=0 is only a short-lived rollback and cleanup tool, never a permanent policy for untrusted input."
Step-by-step deep dive
- Establish a baseline. Collect failing samples on the old version and Go 1.26, recording parse errors, raw values, sources, and final use. "Parses" does not mean "safe to connect"; also inspect scheme, hostname, port, and redirects.
- Classify by URI syntax.
http://[::1]/is a bracketed IP-literal;http://[::1]:8080/puts the port after the brackets.http://::1/uses colons both as host data and a port delimiter, whilehttp://localhost:80:80/contains multiple port delimiters; both are values to repair.
- Fix data boundaries first. Add validation to configuration files, database migrations, and admin APIs: normalize IPv6 with brackets, parse ports as integers within range, and reject uninterpretable hosts. During a batch cleanup, preserve the original, corrected value, and owning source; isolate records that cannot be decided automatically.
- Review the security path. Access control should use parsed
Hostname(), port, and IP results, with explicit boundaries for DNS, redirects, and proxy rewriting. Never classify internal versus public destinations from a string prefix or oneParseresult. Re-run SSRF, proxy, and redirect cases after the upgrade.
- Design a temporary rollback.
GODEBUG=urlstrictcolons=0can restore old behavior in a controlled environment, but it needs an expiry, metrics, and alerts, and must not let new user input bypass validation. A safer option is enabling it only for an inventoried legacy configuration set in an isolated process, then disabling it after cleanup.
- Canary and verify. Run shadow traffic and a small instance set, comparing parse errors, connection errors, proxy matches, and redirect outcomes. Gates should include valid IPv4, bracketed IPv6, bad IPv6, extra colons, empty ports, and malicious redirects. Expand only after legacy cleanup is complete.
func validateEndpoint(raw string) (*url.URL, error) {
u, err := url.Parse(raw)
if err != nil {
return nil, err
}
if u.Scheme != "http" && u.Scheme != "https" {
return nil, fmt.Errorf("unsupported scheme")
}
host := u.Hostname()
if host == "" {
return nil, fmt.Errorf("missing host")
}
if p := u.Port(); p != "" {
n, err := strconv.Atoi(p)
if err != nil || n < 1 || n > 65535 {
return nil, fmt.Errorf("invalid port")
}
}
return u, nil
}Model answer
I would first identify the source of each bad value and separate syntax errors from connection failures. Go 1.26 defaults urlstrictcolons=1, rejecting http://::1/ and http://localhost:80:80/ because the colons cannot be unambiguously interpreted as valid IPv6 or one port; http://[::1]:8080/ is explicit.
At configuration and admin boundaries, I would reject malformed values, automatically repair records proven to be IPv6, and isolate ambiguous records for owners. Every path using URLs for routing, proxying, or SSRF defense must retest hostname, port, DNS, redirects, and proxy rewrites. GODEBUG=urlstrictcolons=0 is a time-bounded compatibility rollback with metrics and alerts, not a way to let untrusted input bypass the new check. The canary compares parse errors, connection errors, and security cases before the rollback is removed.
Common mistakes
- Symptom: Treat every unbracketed IPv6 string as valid → Why it fails: Host and port boundaries are ambiguous → Fix: Use
[IPv6]and place the port after]. - Symptom: Set
GODEBUG=urlstrictcolons=0permanently → Why it fails: Bad input and legacy ambiguity remain → Fix: Set an expiry and repair data and boundaries first. - Symptom: Test only whether
url.Parsereturns an error → Why it fails: Hostname, port, DNS, redirects, and proxies can change the security result → Fix: Test the complete network path. - Symptom: Classify private addresses from raw strings → Why it fails: Encoding, parsing, DNS, and redirects can bypass string rules → Fix: Parse consistently, resolve IPs, and reapply policy after every redirect.
Follow-up questions and responses
Legacy configuration must recover today. How do you control risk?
Enable urlstrictcolons=0 only for an isolated process or an explicitly inventoried legacy set, with an expiry, source and target restrictions, and a metric for every hit. New values still use strict validation; close the rollback after cleanup.
Why is [::1] valid while ::1 is discouraged?
URI authority uses a colon to separate host and port. Brackets make the boundary between the IPv6 IP-literal and port explicit. Placing ::1 directly in hostport is ambiguous, so Go 1.26 rejects it.
Where must a proxy revalidate?
Parsing at ingress is insufficient. After rewriting Host, following a redirect, or resolving DNS again, reapply scheme, hostname, IP, port, and network policy to the new destination; do not carry the initial URL's decision across a hop.
How do you show that the upgrade did not widen rejection?
Keep a fixed corpus of valid IPv4, bracketed IPv6, URLs with ports, malformed extra-colon hosts, empty hosts, invalid ports, and redirects. Run it on Go 1.24.8, 1.25.2, and 1.26, then compare real configuration failure rates during the canary.
References
- Go 1.26 Release Notes
- Go, Backwards Compatibility, and GODEBUG
- RFC 3986 Uniform Resource Identifier: Generic Syntax
- Go
net/urlsource
Interview checklist
Explain host, IPv6 brackets, and the port boundary first. Then cover data cleanup, strict boundary validation, SSRF-path retesting, and an expiring GODEBUG rollback. Keep parsing compatibility separate from access security.
One-sentence takeaway
Go 1.26 requires services to turn ambiguous URLs into explicit URIs and use tests, a canary, and a time-limited rollback for a safe migration.