diff --git a/README.md b/README.md index 6e37cee..d2c06c3 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,7 @@ boundary-run -- curl https://example.com ```bash boundary-run --allow "domain=github.com" -- git pull boundary-run --allow "domain=*.github.com" -- npm install # GitHub subdomains +boundary-run --allow "domain=github.com" --allow "domain=*.github.com" -- git pull # Both base domain and subdomains boundary-run --allow "method=GET,HEAD domain=api.github.com" -- curl https://api.github.com boundary-run --allow "method=POST domain=api.example.com path=/users,/posts" -- ./app # Multiple paths boundary-run --allow "path=/api/v1/*,/api/v2/*" -- curl https://api.example.com/api/v1/users diff --git a/rulesengine/engine.go b/rulesengine/engine.go index 4b068eb..4bf6ae4 100644 --- a/rulesengine/engine.go +++ b/rulesengine/engine.go @@ -77,9 +77,10 @@ func (re *Engine) matches(r Rule, method, url string) bool { if r.HostPattern != nil { // For a host pattern to match, every label has to match or be an `*`. - // Subdomains also match automatically, meaning if the pattern is "example.com" - // and the real is "api.example.com", it should match. We check this by comparing - // from the end of the actual hostname with the pattern (which is in normal order). + // Host matching is strict: + // - "github.com" matches ONLY "github.com" (exact match, no subdomains) + // - "*.github.com" matches ONLY subdomains like "api.github.com" (not the base domain) + // - To allow both, specify: "github.com, *.github.com" labels := strings.Split(parsedUrl.Hostname(), ".") @@ -98,6 +99,10 @@ func (re *Engine) matches(r Rule, method, url string) bool { return false } } + + if len(labels) > len(r.HostPattern) && r.HostPattern[0] != "*" { + return false + } } if r.PathPattern != nil { diff --git a/rulesengine/engine_test.go b/rulesengine/engine_test.go index 72f4311..5a98dea 100644 --- a/rulesengine/engine_test.go +++ b/rulesengine/engine_test.go @@ -74,14 +74,32 @@ func TestEngineMatches(t *testing.T) { expected: false, }, { - name: "subdomain matches", + name: "subdomain does not match exact domain pattern", rule: Rule{ HostPattern: []string{"example", "com"}, }, method: "GET", url: "https://api.example.com/users", + expected: false, + }, + { + name: "wildcard subdomain pattern matches subdomain", + rule: Rule{ + HostPattern: []string{"*", "example", "com"}, + }, + method: "GET", + url: "https://api.example.com/users", expected: true, }, + { + name: "wildcard subdomain pattern does not match base domain", + rule: Rule{ + HostPattern: []string{"*", "example", "com"}, + }, + method: "GET", + url: "https://example.com/users", + expected: false, + }, { name: "host pattern too long", rule: Rule{ diff --git a/rulesengine/parse_and_match_test.go b/rulesengine/parse_and_match_test.go index 02efdfc..c994800 100644 --- a/rulesengine/parse_and_match_test.go +++ b/rulesengine/parse_and_match_test.go @@ -98,13 +98,37 @@ func TestRoundTrip(t *testing.T) { expectMatch: true, }, { - name: "domain wildcard segment matches", + name: "domain wildcard segment matches subdomain", rules: []string{"domain=*.github.com"}, url: "https://api.github.com/repos", method: "GET", expectParse: true, expectMatch: true, }, + { + name: "domain wildcard does not match base domain", + rules: []string{"domain=*.github.com"}, + url: "https://github.com/repos", + method: "GET", + expectParse: true, + expectMatch: false, + }, + { + name: "exact domain does not match subdomain", + rules: []string{"domain=github.com"}, + url: "https://api.github.com/repos", + method: "GET", + expectParse: true, + expectMatch: false, + }, + { + name: "exact domain matches only exact domain", + rules: []string{"domain=github.com"}, + url: "https://github.com/repos", + method: "GET", + expectParse: true, + expectMatch: true, + }, { name: "domain cannot end with asterisk", rules: []string{"domain=github.*"}, @@ -281,12 +305,12 @@ func TestRoundTripExtraRules(t *testing.T) { expectMatch: false, }, { - name: "includes all subdomains by default", + name: "exact domain does not match subdomains", rules: []string{"domain=github.com"}, url: "https://x.users.api.github.com", method: "GET", expectParse: true, - expectMatch: true, + expectMatch: false, }, { name: "domain wildcard in the middle matches exactly one label", diff --git a/rulesengine/rules.go b/rulesengine/rules.go index a8c9f5d..25e6131 100644 --- a/rulesengine/rules.go +++ b/rulesengine/rules.go @@ -20,7 +20,8 @@ type Rule struct { // The labels of the host, i.e. ["google", "com"]. // - nil means all hosts allowed // - A label of `*` acts as a wild card. - // - subdomains automatically match + // - Exact domain patterns (e.g., "github.com") match ONLY the exact domain (no subdomains) + // - Wildcard patterns starting with "*" (e.g., "*.github.com") match ONLY subdomains (not the base domain) HostPattern []string // The allowed http methods. diff --git a/rulesengine/rules_test.go b/rulesengine/rules_test.go index 543e2d7..03d71cb 100644 --- a/rulesengine/rules_test.go +++ b/rulesengine/rules_test.go @@ -1103,7 +1103,7 @@ func TestReadmeExamples(t *testing.T) { }{ {"GET", "https://github.com", true}, {"POST", "https://github.com/user/repo", true}, - {"GET", "https://api.github.com", true}, // subdomain match + {"GET", "https://api.github.com", false}, // subdomain does not match exact domain {"GET", "https://example.com", false}, }, },