Skip to content

feat: add cookie allowlist policy - #98

Merged
mvanhorn merged 1 commit into
mvanhorn:mainfrom
lennartorlando:feat/cookie-allowlist-mode
Jun 17, 2026
Merged

feat: add cookie allowlist policy#98
mvanhorn merged 1 commit into
mvanhorn:mainfrom
lennartorlando:feat/cookie-allowlist-mode

Conversation

@lennartorlando

@lennartorlando lennartorlando commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

agentcookie can now run cookie sync in explicit allowlist mode for high-trust agent deployments. Existing users keep the same behavior: omitted policy, policy: blocklist, missing blocklist.yaml, and empty blocklist domains all preserve the current sync-all/blocklist semantics.

What Changed

Area Behavior
Config blocklist.yaml accepts policy: blocklist or policy: allowlist; unknown policies fail closed with a field-specific error.
Filtering Source, sink, cookies, cmux-sync, and agent-sync use the same policy-aware matcher.
Visibility status, doctor, and sink startup logs report sync-all, blocklist, or allowlist mode.
Account toggles accounts on/off remain blocklist-only and refuse to mutate allowlist policy files.
Docs README, quickstarts, protocol, threat model, FAQ, changelog, and the example config explain the compatibility and allowlist posture.

Allowlist mode is defense in depth: source-side filtering avoids shipping non-allowed hosts, and sink-side filtering drops them again if they arrive anyway. An empty allowlist syncs no cookie hosts and is surfaced as a doctor warning.

Compatibility Notes

  • No existing blocklist.yaml needs to change.
  • A missing blocklist.yaml still means sync-all.
  • A present blocklist with no policy still drops matching patterns and passes everything else.
  • Legacy allowlist.yaml migration behavior is unchanged.
  • Malformed YAML and unknown policy values still halt sync instead of falling back to sync-all.

Verified

  • go vet ./...
  • go test ./...

Post-Deploy Monitoring & Validation

  • Log searches: cookie policy, load blocklist, non-allowlisted, blocklisted, agentcookie source: read, agentcookie sink: wrote.
  • Healthy signals: agentcookie status shows the intended cookie policy; agentcookie doctor reports sync-all/blocklist/allowlist clearly; allowlist-mode sinks report expected non-allowlisted drops without write failures.
  • Failure signals: unexpected cookie policy: sync-all, load blocklist failures, unexpected spike in TotalRejects, or agents losing access because a needed host was omitted from the allowlist.
  • Rollback/mitigation: remove policy: allowlist or set policy: blocklist to return to legacy behavior, then rerun agentcookie doctor and one source/sink sync cycle.
  • Validation window/owner: first operator enabling allowlist mode should validate one manual source --once and one sink receipt before leaving the daemon unattended.

Review Questions

  • Is keeping allowlist mode in blocklist.yaml acceptable for compatibility, or would you prefer a neutral cookie-policy.yaml in a follow-up?
  • Should empty allowlist remain a WARN in doctor, or should it be OK because it is fail-closed and sometimes intentional?

@mvanhorn
mvanhorn marked this pull request as ready for review June 17, 2026 13:49
@mvanhorn
mvanhorn merged commit 1df9dd3 into mvanhorn:main Jun 17, 2026
@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds an explicit policy: allowlist option to blocklist.yaml, enabling operators to sync only named cookie hosts in high-trust headless agent deployments. Existing behavior (missing file = sync-all, omitted/blocklist policy = opt-out) is fully preserved.

  • A new CookiePolicy type and ShouldSyncHost abstraction in BlocklistMatcher unify blocklist and allowlist filtering across all sync surfaces (source push, sink /sync handler, cookies command, cmux-sync, agent-sync).
  • LoadBlocklist validates the policy field and fails closed on unknown values; doctor gains a dedicated cookie-policy check including a WARN for empty allowlists; status and sink startup logs surface the active mode.
  • accounts on/off refuse to mutate allowlist-policy files, keeping the toggle helpers blocklist-only.

Confidence Score: 4/5

Safe to merge. All sync boundaries remain fail-closed on policy load errors, backwards compatibility with existing blocklist.yaml files is preserved, and the allowlist path is covered by tests at every layer.

The core policy logic is sound — ShouldSyncHost correctly inverts the decision for each mode, empty allowlist reliably syncs nothing, and LoadBlocklist rejects unknown policy values rather than falling back to sync-all. The one issue is a leftover "load blocklist:" string in the HTTP error response body that was not updated alongside the matching stderr log change, creating a terminology mismatch that could slow down operator debugging.

internal/cli/sink.go — the HTTP error response body on line 199 still references "load blocklist" after the surrounding log messages were updated to "cookie policy".

Important Files Changed

Filename Overview
internal/protocol/allowlist.go Adds policy field to BlocklistMatcher; introduces ShouldSyncHost, PolicySummary, and DropLabel; updates Filter to delegate to ShouldSyncHost. Logic is correct for both modes and nil-safe throughout.
internal/config/allowlist.go Adds CookiePolicy type/constants, PolicyMode and CookiePolicySummary methods; LoadBlocklist now validates policy and returns an error for unknown values (fail-closed). Backwards compatibility preserved — omitted policy defaults to blocklist.
internal/cli/sink.go Startup log and /sync handler messaging updated for policy awareness; DropLabel() used in response bodies. HTTP error body at line 199 still says "load blocklist:" while the stderr log was updated to "cookie policy load failed" — minor string inconsistency.
internal/cli/cookies.go Correctly migrates collectDomainCookies from matcher.MatchesHost (blocklist-only) to !matcher.ShouldSyncHost (policy-aware); load error remains fail-closed; allowlist test coverage added.
internal/cli/accounts.go Correctly guards runAccountsToggle against allowlist-mode files; accountsListResult gains a Policy field (no omitempty, intentional for JSON consumers); emitAccountsList prints a header in allowlist mode.
internal/cli/doctor.go New checkCookiePolicy check added; correctly distinguishes sync-all (INFO), empty allowlist (WARN), and configured policy (OK); fail-closed on load error (FAIL).
internal/cli/source.go Adds cookies_filtered and cookie_policy to the push result map alongside the legacy cookies_blocked field for monitoring compatibility; logging updated to policy-neutral language.
internal/cli/status.go Adds cookie_policy to both JSON and human-readable outputs; correctly reads CookiePolicySummary() after a successful blocklist load.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[LoadBlocklist] -->|file missing| B[Empty Blocklist\nsync-all]
    A -->|parse error / unknown policy| C[Error\nfail closed]
    A -->|policy: blocklist| D[BlocklistMatcher\npolicy=blocklist]
    A -->|policy: allowlist| E[BlocklistMatcher\npolicy=allowlist]

    D --> F[ShouldSyncHost]
    E --> F

    F -->|blocklist mode| G{MatchesHost?}
    G -->|yes — matched blocklisted pattern| H[DROP cookie]
    G -->|no — not blocked| I[PASS cookie]

    F -->|allowlist mode| J{MatchesHost?}
    J -->|yes — on allowlist| I
    J -->|no — not on allowlist| H

    B --> K[Pass all cookies\nsync-all]

    subgraph Sync Boundaries
        S[source push]
        SK[sink /sync]
        CO[cookies cmd]
        CM[cmux-sync]
        AG[agent-sync]
    end

    S & SK & CO & CM & AG -->|loadFreshBlocklist\nerror = fail closed| A
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[LoadBlocklist] -->|file missing| B[Empty Blocklist\nsync-all]
    A -->|parse error / unknown policy| C[Error\nfail closed]
    A -->|policy: blocklist| D[BlocklistMatcher\npolicy=blocklist]
    A -->|policy: allowlist| E[BlocklistMatcher\npolicy=allowlist]

    D --> F[ShouldSyncHost]
    E --> F

    F -->|blocklist mode| G{MatchesHost?}
    G -->|yes — matched blocklisted pattern| H[DROP cookie]
    G -->|no — not blocked| I[PASS cookie]

    F -->|allowlist mode| J{MatchesHost?}
    J -->|yes — on allowlist| I
    J -->|no — not on allowlist| H

    B --> K[Pass all cookies\nsync-all]

    subgraph Sync Boundaries
        S[source push]
        SK[sink /sync]
        CO[cookies cmd]
        CM[cmux-sync]
        AG[agent-sync]
    end

    S & SK & CO & CM & AG -->|loadFreshBlocklist\nerror = fail closed| A
Loading

Fix All in Codex Fix All in Claude Code Fix All in Cursor Fix All in Conductor

Reviews (1): Last reviewed commit: "feat: add cookie allowlist policy" | Re-trigger Greptile

Comment thread internal/cli/sink.go
Comment on lines 196 to 199
if err != nil {
fmt.Fprintf(os.Stderr, "agentcookie sink: blocklist load failed: %v\n", err)
fmt.Fprintf(os.Stderr, "agentcookie sink: cookie policy load failed: %v\n", err)
recordSinkReject(sinkState, stateWriter, err)
http.Error(w, "load blocklist: "+err.Error(), http.StatusInternalServerError)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Stale "load blocklist" string in HTTP response body

The stderr log on the line above was updated to "cookie policy load failed", but the http.Error body still reads "load blocklist: ...". When a source operator is debugging a policy load failure, the HTTP response they receive will still say "load blocklist" while the sink log says "cookie policy" — the mismatch is especially confusing for allowlist policy errors where "blocklist" is semantically wrong. Consider changing this to "cookie policy load failed: " + err.Error().

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex Fix in Claude Code Fix in Cursor Fix in Conductor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants