From 0db044076f6ed69ce1343f9b9c8f3c09474f6f60 Mon Sep 17 00:00:00 2001 From: YEVHENII SHCHERBINA Date: Thu, 15 Jan 2026 14:25:06 +0000 Subject: [PATCH 1/2] feat: make rulesengine stricter --- rulesengine/engine.go | 11 ++++++++--- rulesengine/engine_test.go | 20 ++++++++++++++++++- rulesengine/parse_and_match_test.go | 30 ++++++++++++++++++++++++++--- rulesengine/rules.go | 3 ++- rulesengine/rules_test.go | 2 +- 5 files changed, 57 insertions(+), 9 deletions(-) diff --git a/rulesengine/engine.go b/rulesengine/engine.go index 4b068eb8..4bf6ae42 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 72f4311d..5a98deae 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 02efdfca..c9948009 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 a8c9f5d1..25e6131a 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 543e2d74..03d71cbc 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}, }, }, From 7af16a3993e7f2e95f4495a257a1d4d38d6f6da9 Mon Sep 17 00:00:00 2001 From: YEVHENII SHCHERBINA Date: Thu, 15 Jan 2026 18:12:37 +0000 Subject: [PATCH 2/2] docs: update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6e37ceef..d2c06c3c 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