feat(session): TTL for session self-exemptions (default 30m)#56
Merged
Conversation
Session-scoped self-exemptions (`selfExemptionScope: 'session'`) never expired: pruneExpiredSessions only pruned `state.sessions`, so a host exemption lived for the lifetime of the session state. That is an unbounded blast radius for a grant, and the sharp edge behind #52 item 3 (a later same-network click on an already-exempted host stays suppressed indefinitely). Add `sessionExemptionTtlMs` (default 30 minutes, fixed from grant, not sliding) on the session and thread it through the content/webext/url adapters. ExemptionRecord gains an `expiresAt`; pruneExpiredState (renamed from pruneExpiredSessions) now drops lapsed exemptions alongside expired sessions. Set the TTL to 0 (or any non-positive value) to disable expiry and keep the old lifetime behavior. This bounds the window from item 3; Option C (narrowing what a live exemption suppresses) is the precise fix and lands separately. Addresses item 3 of #52 (bound). Part of #52. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PXZmuMFkq8dE2e2KLXvitx
i8ramin
added a commit
that referenced
this pull request
Jul 14, 2026
…mpetitor click The TTL tests (#56) used a competing same-network click to show the TTL boundary. #57 (Option C) landed in parallel and now stands down on a competing attribution param, so the within-window "still suppressed" assertion broke once both merged (each PR was green against a base without the other). Switch the TTL demonstration to the lingering-cookie signal, which a live exemption still re-attributes to us under Option C, so the test isolates the TTL boundary cleanly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PXZmuMFkq8dE2e2KLXvitx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First of two PRs for item 3 of #52. This one bounds the exposure; the precise fix (Option C) follows in a second PR.
Problem
Session-scoped self-exemptions (
selfExemptionScope: 'session') never expired.pruneExpiredSessionsprunedstate.sessionsonly, so a host'sExemptionRecordlived for the whole lifetime of the session state. That is an unbounded window for a single self-click grant, and it is the mechanism behind item 3: a later same-network click on an already-exempted host stays suppressed indefinitely.Change
sessionExemptionTtlMsoption onStanddownSession, default 30 minutes, fixed from grant (not sliding). Threaded through thecontent/webext/urladapters the same wayselfExemptionScopeis.ExemptionRecordgainsexpiresAt(set tograntedAt + ttlwhen the TTL is positive).pruneExpiredSessions→pruneExpiredState, now dropping lapsed exemptions alongside expired sessions. Runs on everyingest/shouldStandDown/recordActivity.0(or any non-positive value) to disable expiry and keep the previous lifetime behavior.Defaults I chose (flag if you'd prefer otherwise)
session-or-minminimum-duration ballpark.0: adopters who deliberately want the old lifetime semantics can opt back in explicitly.Tests
0): the exemption still applies hours later.Full suite green (165 tests, +2), typecheck / lint / citations / build clean. No behavior change for
selfExemptionScope: 'policy'(the default scope).Note
This does not by itself fix the competitor-click case within the window. That is Option C in the write-up on #52, coming as a follow-up PR, and it changes documented
ignore_param-parity behavior so it is scoped separately.