Repository navigation
fix(resolver): scope allowlist-only mode to the whole client - #2249
Conversation
An allowlist-only group (allowlist entries, no denylist key) put a client into exclusive "allow nothing else" mode even when the client was also assigned ordinary denylist groups: the flag was ORed across the client's groups and the branch returned before the denylist was ever consulted. A client that combined a shared denylist group with a small client-specific allowlist group therefore had every unlisted domain blocked, and its denylist hits lost their attribution -- reported as "BLOCKED (ALLOWLIST ONLY)" with an empty reason label instead of naming the group and rule that matched. Exclusive mode now requires that *every* group assigned to the client be allowlist-only. It is derived from the client's configured groups rather than the currently active ones, so deactivating the denylist group -- via a schedule or the disable API -- cannot promote the remaining allowlists into a whitelist. Disabling blocking must never block more. The documented behaviour was wrong on two counts and is corrected: exclusive mode is a property of the client, not of a single group, and the domains it permits are the union of the allowlists across all of the client's groups. Fixes #2207 Claude-Session: https://claude.ai/code/session_01QyEFFhrY3j9QtGzR2wUJr9
There was a problem hiding this comment.
🟡 Changes recommended
A remaining call site (resolver/blocking_resolver_concurrency_test.go) still calls groupsToCheckForClient as a single-return function and will not compile until updated.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR fixes a blocking resolver edge case where assigning a client both (1) a denylist group and (2) an allowlist-only group incorrectly forced the entire client into “exclusive allow” mode, preventing normal denylist evaluation and losing denylist attribution.
Changes:
- Scope “exclusive allowlist-only” mode to apply only when all configured groups for the client are allowlist-only.
- Propagate the client-level allowlist-only flag from
groupsToCheckForClientintohandleDenylistto avoid OR-ing allow-only behavior across mixed group sets. - Add unit + e2e coverage and update docs to reflect correct client-scoped behavior and precedence rules.
File summaries
| File | Description |
|---|---|
| resolver/blocking_resolver.go | Changes allowlist-only determination to be client-scoped (all groups) and threads it into denylist handling. |
| resolver/blocking_resolver_test.go | Adds unit tests for mixed allowlist-only + denylist group behavior and attribution. |
| resolver/blocking_resolver_bench_test.go | Updates benchmarks for the new groupsToCheckForClient return signature. |
| e2e/blocking_test.go | Adds e2e coverage for the mixed-group case and updates outdated commentary. |
| docs/configuration.md | Corrects documentation to describe client-scoped exclusivity and cross-group allowlist precedence. |
| docs/config.yml | Updates sample config comments for client-scoped exclusive allow mode. |
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // groupsToCheckForClient returns the groups which should be checked for the client's | ||
| // request, and whether those groups form an exclusive allowlist (block everything not | ||
| // allowlisted). | ||
| func (r *BlockingResolver) groupsToCheckForClient(request *model.Request) ([]string, bool) { |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2249 +/- ##
==========================================
+ Coverage 88.00% 88.03% +0.02%
==========================================
Files 126 126
Lines 9940 9939 -1
==========================================
+ Hits 8748 8750 +2
+ Misses 926 924 -2
+ Partials 266 265 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Fixes #2207
Problem
A group holding only allowlist entries (no
denylistskey) put the whole client into exclusive "allow nothing else" mode, even when that client was also assigned ordinary denylist groups. The flag was ORed across the client's groups and the branch returned before the denylist was ever consulted:So the config from the issue — a shared
adsdenylist plus a small client-specific allowlist group — blocked everything for that client:Note the third line: denylist hits also lost their attribution, since the exclusive branch returned before the match was known. The query log and the
reasonmetric label sawBLOCKED (ALLOWLIST ONLY)with an empty reason label rather than the group and rule that matched.The reporter's workaround was a dummy
.invaliddomain in a denylist for that group. It works becausedetermineAllowlistOnlyGroupstests key presence incfg.Denylists, not whether the group denies anything — undiscoverable from the docs.Fix
Exclusive mode now requires that every group assigned to the client be allowlist-only.
hasAllowlistOnlyAllowedis gone;groupsToCheckForClientreturns the flag alongside the active groups.Exclusivity is derived from the client's configured groups, before the schedule and disable-API filtering. Deriving it from the active groups would introduce a new bug: deactivating the denylist group would leave only the allowlist group active and promote its exceptions into a whitelist, so disabling blocking would block more. There is a test pinning that.
This keeps the pure allow-only use case (a client whose groups are all allowlist-only) working exactly as before — every pre-existing allowlist-only spec passes unchanged.
Docs
The documented behaviour was wrong on two counts, both corrected in
docs/configuration.mdanddocs/config.yml:adsgroup resolves for a client whose exclusive mode came from a different group.)The docs now also state that allowlist precedence holds across a client's groups, which is the supported way to add client-specific exceptions to a shared denylist without duplicating it. Added schedule rule 5 noting exclusivity follows configuration, not currently-active groups.
Tests
Written test-first; the failing cases were observed before the fix.
Unit (
resolver/blocking_resolver_test.go) — 5 new specs, 3 of which failed against the old code:The mixed case had no coverage at all before this: every existing allowlist-only spec used clients whose groups were all allowlist-only, which is why this shipped.
E2E (
e2e/blocking_test.go) — 3 new specs over two list servers. Against a pre-fix image the first one reproduces the report end to end:Also replaced the stale note claiming allow-only mode could not be tested here.
Verification
./resolversuite: 713/713 passgolangci-lint(v2.12.2): 0 issuesgofmtcleanhttps://claude.ai/code/session_01QyEFFhrY3j9QtGzR2wUJr9