Repository navigation
fix(rulesengine): match domains case-insensitively and ignore a trailing dot - #217
Open
a-palamarchuk wants to merge 1 commit into
Open
a-palamarchuk wants to merge 1 commit into
a-palamarchuk wants to merge 1 commit into
Conversation
…ing dot Domain rules compared host labels byte for byte, so `domain=github.com` denied requests for `GitHub.com` or `github.com.`, and a rule written as `domain=GitHub.com` never matched lowercase traffic. All of these are the same DNS name. A `*` label also matched an empty label, so `domain=*.github.com` matched `.github.com` and `a..github.com`. Before matching, remove one trailing dot from the request host and lowercase ASCII letters on both sides. Folding is ASCII-only (RFC 4343): Unicode folding would map U+212A KELVIN SIGN to "k" and let a rule match a host that resolves to a different name. Hosts with an empty label no longer match. Paths and methods stay case-sensitive, and the rule grammar is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
Problem
domain=rules compare host labels byte for byte (rulesengine/engine.go,matches). DNS names are case-insensitive (RFC 4343), and a trailing dot only marks a name as fully qualified, so these all name the same host but are treated differently:maindomain=github.comGitHub.comdomain=github.comgithub.com.domain=GitHub.comgithub.comdomain=*.github.comAPI.github.com*matches any case while literal labels don'tThis fails closed, so it's a usability problem, not a bypass. But users see 403s for hosts they did allow, and a rule typed with capitals silently does nothing.
Since #201, inject targets use the same matcher, so
--session-id-inject-targetalso became case-sensitive. #201's table lists this as a side effect of unifying the two matchers.A related edge case in the same code:
*matches an empty label, sodomain=*.github.commatches.github.comanda..github.com. Those names don't resolve (Go's resolver rejects them), so this isn't exploitable, but a wildcard shouldn't match an invalid name.Change
matchesnormalizes the request host before splitting it into labels: it removes one trailing dot and lowercases ASCII letters.domain=github.com.is still rejected at parse time.domain=*no longer matches a request with an empty host; onmainit matched, and the forward then failed.toLowerASCIIhelper.strings.ToLowerandstrings.EqualFolduse Unicode folding, which maps U+212A KELVIN SIGN tok.domain=kubernetes.iowould then matchKubernetes.io(written with the Kelvin sign), a name that resolves differently or not at all.docs/architecture.mdpolicy model: one new bullet.Paths and methods stay case-sensitive (RFC 3986 and RFC 9110). Inject targets become case-insensitive again, as they were before #201.
Testing
New cases in
TestRoundTripExtraRules, which parse a real rule string and evaluate a URL:*.github.com, two trailing dots, empty leading and inner labels, a Kelvin-sign lookalike, and a path that differs only in case.The four match cases and the two empty-label cases fail on
main. The other four no-match cases guard existing behavior and pass both before and after.End to end with landjail and
--allow 'domain=example.com'. The same script ran against amainbuild and a build of this branch; the outputs are placed side by side:The last row passes on
mainonly because curl drops the trailing dot fromHostfor HTTPS. Other clients may not.make unit-test: all packages pass, except the two live-network proxy tests that also fail onmain(dev.coder.comnow redirects). test(proxy): serve proxy test upstreams locally #213 fixes those.make fmt-check: passes.AI disclosure
I used Claude Code to investigate the matcher and draft the fix and tests. I reviewed the diff and ran the tests and the reproduction above myself.