From 30976fdff1986717e3c9209e69b0f0bfb3322686 Mon Sep 17 00:00:00 2001 From: Winston Howes Date: Mon, 13 Apr 2026 12:56:18 -0700 Subject: [PATCH 1/3] Restrict wildcard allowlist matching to anonymous callers --- app/allowlist.go | 5 ++--- app/allowlist_test.go | 9 ++++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/allowlist.go b/app/allowlist.go index 54b8b53..643b592 100644 --- a/app/allowlist.go +++ b/app/allowlist.go @@ -478,8 +478,7 @@ func findConstraint(i *Integration, callerID, pth, method string) (RequestConstr allowlists.RLock() callers := allowlists.m[i.Name] - // NOTE: We check wildcard callers too incase an allowlist has both defined callers - // and a fallback wildcard caller. + // NOTE: wildcard callers are only used for anonymous requests (callerID="*"). wildcard, hasWildcard := callers["*"] c, ok := callers[callerID] allowlists.RUnlock() @@ -496,7 +495,7 @@ func findConstraint(i *Integration, callerID, pth, method string) (RequestConstr } } } - if hasWildcard { + if hasWildcard && (callerID == "*" || callerID == "") { if len(wildcard.Capabilities) > 0 { wildcard = integrationplugins.ExpandCapabilities(i.Name, []CallerConfig{wildcard})[0] } diff --git a/app/allowlist_test.go b/app/allowlist_test.go index 79eba30..74e8657 100644 --- a/app/allowlist_test.go +++ b/app/allowlist_test.go @@ -115,7 +115,7 @@ func TestFindConstraintExpandsCallerAndWildcardCapabilities(t *testing.T) { if _, ok := findConstraint(integ, "direct", "/capability", http.MethodGet); !ok { t.Fatal("expected capability expansion for specific caller") } - if _, ok := findConstraint(integ, "other", "/capability", http.MethodGet); !ok { + if _, ok := findConstraint(integ, "*", "/capability", http.MethodGet); !ok { t.Fatal("expected capability expansion for wildcard caller") } } @@ -169,7 +169,7 @@ func TestFindConstraintExpandsCapabilitiesOnLookup(t *testing.T) { if _, ok := findConstraint(integ, "direct", "/capability", http.MethodGet); !ok { t.Fatal("expected capability expansion for caller during lookup") } - if _, ok := findConstraint(integ, "other", "/capability", http.MethodGet); !ok { + if _, ok := findConstraint(integ, "*", "/capability", http.MethodGet); !ok { t.Fatal("expected capability expansion for wildcard during lookup") } } @@ -211,9 +211,12 @@ func TestFindConstraintWildcard(t *testing.T) { if _, ok := findConstraint(integ, "abc", "/abc", http.MethodGet); !ok { t.Fatal("expected specific constraint") } - if _, ok := findConstraint(integ, "xyz", "/wild", http.MethodGet); !ok { + if _, ok := findConstraint(integ, "*", "/wild", http.MethodGet); !ok { t.Fatal("expected wildcard constraint") } + if _, ok := findConstraint(integ, "xyz", "/wild", http.MethodGet); ok { + t.Fatal("unexpected wildcard fallback for identified caller") + } if _, ok := findConstraint(integ, "xyz", "/none", http.MethodGet); ok { t.Fatal("unexpected match for unknown path") } From 620e9d1d3cb43976b54a309f2585acfb084edd23 Mon Sep 17 00:00:00 2001 From: Winston Howes Date: Mon, 13 Apr 2026 13:01:54 -0700 Subject: [PATCH 2/3] Allow wildcard fallback only for unknown or anonymous callers --- app/allowlist.go | 10 ++++++++-- app/allowlist_test.go | 10 +++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/allowlist.go b/app/allowlist.go index 643b592..416abad 100644 --- a/app/allowlist.go +++ b/app/allowlist.go @@ -478,7 +478,8 @@ func findConstraint(i *Integration, callerID, pth, method string) (RequestConstr allowlists.RLock() callers := allowlists.m[i.Name] - // NOTE: wildcard callers are only used for anonymous requests (callerID="*"). + // NOTE: wildcard callers are used for anonymous requests and when callerID + // does not have an explicit allowlist entry. wildcard, hasWildcard := callers["*"] c, ok := callers[callerID] allowlists.RUnlock() @@ -494,8 +495,13 @@ func findConstraint(i *Integration, callerID, pth, method string) (RequestConstr } } } + // Identified callers with explicit entries should not fall back to wildcard + // rules when their own rules do not match. + if callerID != "*" && callerID != "" { + return RequestConstraint{}, false + } } - if hasWildcard && (callerID == "*" || callerID == "") { + if hasWildcard && (!ok || callerID == "*" || callerID == "") { if len(wildcard.Capabilities) > 0 { wildcard = integrationplugins.ExpandCapabilities(i.Name, []CallerConfig{wildcard})[0] } diff --git a/app/allowlist_test.go b/app/allowlist_test.go index 74e8657..e4718c1 100644 --- a/app/allowlist_test.go +++ b/app/allowlist_test.go @@ -115,7 +115,7 @@ func TestFindConstraintExpandsCallerAndWildcardCapabilities(t *testing.T) { if _, ok := findConstraint(integ, "direct", "/capability", http.MethodGet); !ok { t.Fatal("expected capability expansion for specific caller") } - if _, ok := findConstraint(integ, "*", "/capability", http.MethodGet); !ok { + if _, ok := findConstraint(integ, "other", "/capability", http.MethodGet); !ok { t.Fatal("expected capability expansion for wildcard caller") } } @@ -169,7 +169,7 @@ func TestFindConstraintExpandsCapabilitiesOnLookup(t *testing.T) { if _, ok := findConstraint(integ, "direct", "/capability", http.MethodGet); !ok { t.Fatal("expected capability expansion for caller during lookup") } - if _, ok := findConstraint(integ, "*", "/capability", http.MethodGet); !ok { + if _, ok := findConstraint(integ, "other", "/capability", http.MethodGet); !ok { t.Fatal("expected capability expansion for wildcard during lookup") } } @@ -211,11 +211,11 @@ func TestFindConstraintWildcard(t *testing.T) { if _, ok := findConstraint(integ, "abc", "/abc", http.MethodGet); !ok { t.Fatal("expected specific constraint") } - if _, ok := findConstraint(integ, "*", "/wild", http.MethodGet); !ok { + if _, ok := findConstraint(integ, "xyz", "/wild", http.MethodGet); !ok { t.Fatal("expected wildcard constraint") } - if _, ok := findConstraint(integ, "xyz", "/wild", http.MethodGet); ok { - t.Fatal("unexpected wildcard fallback for identified caller") + if _, ok := findConstraint(integ, "abc", "/wild", http.MethodGet); ok { + t.Fatal("unexpected wildcard fallback for configured caller") } if _, ok := findConstraint(integ, "xyz", "/none", http.MethodGet); ok { t.Fatal("unexpected match for unknown path") From 01ceeb3db4c4e542023cac0bc14129b10b38bfa7 Mon Sep 17 00:00:00 2001 From: Winston Howes Date: Mon, 13 Apr 2026 13:04:30 -0700 Subject: [PATCH 3/3] Add explicit tests for wildcard fallback semantics --- app/allowlist_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/app/allowlist_test.go b/app/allowlist_test.go index e4718c1..06a2e6e 100644 --- a/app/allowlist_test.go +++ b/app/allowlist_test.go @@ -222,6 +222,38 @@ func TestFindConstraintWildcard(t *testing.T) { } } +func TestFindConstraintWildcardFallbackSemantics(t *testing.T) { + allowlists.Lock() + allowlists.m = make(map[string]map[string]CallerConfig) + allowlists.Unlock() + + if err := SetAllowlist("fc-fallback", []CallerConfig{ + {ID: "alice", Rules: []CallRule{{Path: "/alice-only", Methods: map[string]RequestConstraint{"GET": {}}}}}, + {ID: "*", Rules: []CallRule{{Path: "/wild-only", Methods: map[string]RequestConstraint{"GET": {}}}}}, + }); err != nil { + t.Fatalf("failed to set allowlist: %v", err) + } + + integ := &Integration{Name: "fc-fallback"} + + // Explicit caller matches explicit rules. + if _, ok := findConstraint(integ, "alice", "/alice-only", http.MethodGet); !ok { + t.Fatal("expected explicit caller rule match") + } + // Explicit caller must not fall back to wildcard. + if _, ok := findConstraint(integ, "alice", "/wild-only", http.MethodGet); ok { + t.Fatal("unexpected wildcard fallback for explicit caller") + } + // Unknown caller should use wildcard fallback. + if _, ok := findConstraint(integ, "bob", "/wild-only", http.MethodGet); !ok { + t.Fatal("expected wildcard fallback for unknown caller") + } + // Anonymous caller should use wildcard fallback. + if _, ok := findConstraint(integ, "*", "/wild-only", http.MethodGet); !ok { + t.Fatal("expected wildcard fallback for anonymous caller") + } +} + func TestSetAllowlistDuplicateCaller(t *testing.T) { allowlists.Lock() allowlists.m = make(map[string]map[string]CallerConfig)