URL allowlist footgun inspector
Paste an untrusted URL and see what the browser's own WHATWG parser makes of it: the host it will really connect to, credentials hidden before an @, punycode homographs, alternate IP notations it silently normalizes, and trailing-dot boundaries. Then give it an allowlist host and watch a naive includes() or endsWith() check wave the wrong origin straight through.
parsing…
What the browser parses
| component | value |
|---|
The host is whatever follows the last @
The single most exploited URL footgun is the userinfo field. In https://example.com@evil.example.net/ the part before the @ is a username, not a host. The browser connects to evil.example.net. Any check that reads the host by taking the text after :// up to the first slash, without stripping userinfo, sees example.com and is fooled. The fix is never to slice the string yourself: parse it and read url.hostname, which is what this tool shows you.
Backslashes, and the schemes that normalize them
For the special schemes (http, https, ws, wss, ftp, file) the parser treats \ as /. So https:/\evil.example.net and https://example.com\@evil.example.net resolve to a host most hand-written splitters never expect. A redirect validator written against forward slashes is blind to the backslash variant; the browser is not.
Alternate IP notations collapse to one address
The parser canonicalizes IPv4. http://0x7f.1, http://2130706433, http://0177.0.0.1, and http://127.1 all become 127.0.0.1. An SSRF allowlist that blocks the literal string 127.0.0.1 or localhost but not its octal, hex, decimal, or short forms is bypassed, because the socket connects to the same loopback address either way. Compare against the parsed host, and for SSRF resolve and check the actual IP.
Punycode and the trailing dot
An internationalized host is stored in its ASCII xn-- form, so hostname on a homograph like a Cyrillic а in example
reads as xn-- gibberish that is not the name you trust. And a fully-qualified host carries an optional trailing dot: example.com. resolves to the same site as example.com but is a different string, so an exact-match allowlist of example.com rejects the dotted form while the browser accepts it. Both are flagged above when present.
Why a substring or endsWith check is not enough
Two checks people reach for, and why they leak. url.includes("example.com") matches https://example.com.evil.net, https://evil.com/?next=example.com, and the userinfo trick, none of which are your site. hostname.endsWith("example.com") matches notexample.com because there is no dot boundary. The safe shape is exact host or a real subdomain: h === "example.com" || h.endsWith(".example.com"), evaluated on the parsed hostname, never on the raw string. When you give an allowlist host above, the tool runs all three and shows you where they disagree.
What this tool does not do
It parses one URL with the browser's resolver and reasons about its parts; it does not fetch the URL, follow redirects, resolve DNS, or check an IP against private ranges. It cannot tell you that a hostname resolves to an internal address, only that the host you typed is or is not the host you meant. Relative URLs are resolved against https://base.invalid/ so you can see how a path-only or scheme-relative input expands. For SSRF defense, treat the parsed host as the start, not the end: resolve it and check the address you would actually connect to.
Component reference
| component | meaning |
|---|---|
| protocol | The scheme with its colon, lowercased, e.g. https:. Anything outside http/https deserves scrutiny in a redirect or fetch sink. |
| username / password | Userinfo, the text before @ in the authority. Present means the URL carries credentials and the host is what follows the @. |
| hostname | The host with no port, lowercased, IDN as xn-- punycode, IPv4 canonicalized, IPv6 in brackets. This is the value to compare an allowlist against. |
| host | hostname plus :port when the port is non-default. The default port for the scheme is stripped. |
| origin | scheme + host, the security principal. null for opaque schemes such as data: and blob:. |
| pathname | The path, with . and .. segments already resolved and backslashes normalized for special schemes. |
Single static HTML file, no network. The parsing is the browser's own URL constructor; nothing you type leaves the page. Source: github.com/truffle-dev/tool-url. MIT.