fix(target-url): treat trailing-dot hostnames as loopback#37
Open
lxcario wants to merge 1 commit into
Open
Conversation
assertNotLocal lowercased the hostname but did not strip a trailing dot, so http://localhost. (the FQDN form of localhost, RFC 6761) and http://localhost%2e bypassed the host === 'localhost' loopback check. IP literals are already dot-normalized by the WHATWG URL parser, so only named hosts were affected. Strips one trailing dot before the comparison. Adds 4 regression tests (3 blocked variants + 1 public-FQDN no-false-positive).
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
assertNotLocal(the--target-urlpre-flight guard) rejectshttp://localhostbut acceptshttp://localhost.— the trailing-dot (fully-qualified) form of the same loopback name.Why this matters
localhost.resolves to the loopback address just likelocalhost, sotest create --target-url http://localhost./test run --target-url …passes the guard when the equivalenthttp://localhostis correctly rejected. The guard'slocalhostspecial-case is simply incomplete for the FQDN form.Reproduction
Reproduced directly against
assertNotLocal. Thehttp://127.0.0.1.control shows the numeric-IP path is already fine; only the named-host trailing-dot form slips through.Root cause
src/lib/target-url.ts,assertNotLocal:The WHATWG URL parser dot-normalizes IP literals but keeps the trailing dot on named hosts, so
localhost.never equalslocalhost.Fix
Strip a single trailing dot from the hostname before the comparison:
IP literals are unaffected (already dot-normalized by
new URL()); only named hosts change.Tests
Added 4 tests to
src/lib/target-url.spec.ts: 3 trailing-dot loopback variants (localhost.,localhost.:8080,localhost%2e) now blocked, plus 1 no-false-positive check that a legitimate public FQDN with a trailing dot (https://example.com.) is still allowed. The 3 "blocked" tests fail onmainbefore this fix and pass after.main):Tests 16 failed | 1359 passed | 72 skippedTests 16 failed | 1363 passed | 72 skipped(+4 new tests)The 16 failures are pre-existing and environment-specific (Windows path/line-ending), unrelated to this change — see #4.
Verification
npm test: same 16 pre-existing (environment-specific) failures as baseline, zero newnpm run typecheck: passnpm run lint: passNote on scope
assertNotLocalis documented as a best-effort client-side pre-flight check, not the security boundary (the backend enforces that). This PR doesn't change that posture — it just closes a gap in the guard's own existing special-case forlocalhost, sincelocalhost.(trailing dot, RFC 952 / 1123 FQDN form) resolves to the same loopback address but wasn't being normalized before the comparison. Happy to close this if the maintainers consider trailing-dot normalization out of scope for this guard.