Unused Permission Expiry

Persistent site permissions (camera, microphone, geolocation, etc.) do not traditionally expire. This can be a privacy and security concern: users may forget they granted a site access to powerful capabilities, especially after not visiting the site for a long time.

To address this, the permission manager can automatically expire permissions for sites the user has not interacted with recently. Rather than a simple lifetime cap on all permissions, this approach targets only inactive sites, so frequently-used sites keep their permissions.

Note

This feature is currently enabled on Nightly builds only.

How It Works

  1. Interaction tracking. Each time the user activates (clicks, types, etc.) a page, the content process sends an IPC message (RecordUserInteractionForPermissions) to the parent process. The permission manager records the current timestamp for the page’s origin in the moz_origin_interactions database table.

  2. Expiry check. On the idle-daily observer notification, the permission manager runs ExpireUnusedPermissions(). For each persistent permission whose type is in the configurable allowlist, it checks whether the origin’s last interaction timestamp is older than the expiry threshold. If so, the permission is removed. If there is no interaction data for an origin, its permissions are not expired.

  3. Cleanup. Orphaned interaction records (origins with no remaining permissions) are cleaned up on idle-daily and when permissions are removed through ClearDataService.

Important

Interaction tracking is always active, even when the expiry feature itself is disabled. This allows the interaction store to warm up before the feature is enabled, so that expiry decisions are based on real usage data from day one.

However, for profiles where the interaction store has only recently started collecting data, the permission manager must first observe user interactions with sites before it can expire their permissions. Since permissions without interaction data are never expired, users will have to wait at least the full expiry threshold (default: 13 months) after interactions are first recorded before any permissions are actually removed.

Configuration

Three preferences control the feature (see their definitions in modules/libpref/init/StaticPrefList.yaml):

permissions.expireUnused.enabled

Enables or disables the expiry mechanism. Currently enabled on Nightly only.

permissions.expireUnusedThresholdSec

Duration in seconds of site inactivity after which permissions are eligible for expiry.

permissions.expireUnusedTypes

Comma-separated list of permission types that can be expired due to inactivity. Double-keyed permission types include the ^ delimiter (e.g., open-protocol-handler^). This list is derived from the site permissions defined in browser/modules/SitePermissions.sys.mjs.

Interaction Tracking

User interactions are tracked in the moz_origin_interactions table in the permissions database (added in schema version 13).

The flow for recording an interaction:

  1. Document::MaybeStoreUserInteractionAsPermission() (in dom/base/Document.cpp) fires on first user activation in a document.

  2. In the content process, PermissionManager::RecordSiteInteraction() (in extensions/permissions/PermissionManager.cpp) sends PWindowGlobal::RecordUserInteractionForPermissions() via IPC.

  3. In the parent process, WindowGlobalParent receives the message and calls PermissionManager::UpdateLastInteractionForPrincipal(), which dispatches a write to the permissions database thread.

Interactions in private browsing are not recorded.

Origin Attributes Handling

Interaction timestamps are stored with origin attributes (OA) stripped, so a single timestamp represents all OA variants of the same origin.

During the expiry check, certain permission types that are forced to strip OA (e.g., cookie, https-only-load-insecure) are looked up against a separate OA-stripped index. This ensures the expiry logic correctly matches interactions to permissions regardless of origin attribute context.

Data Cleanup

Orphaned interaction records (entries in moz_origin_interactions whose origin has no corresponding entry in moz_perms) are removed:

  • Automatically during idle-daily maintenance.

  • After per-entry permission removal in ClearDataService, via the removeOrphanedInteractionRecords() IDL method.

  • Bulk removal methods (e.g., removeAll, removePermissionsWithAttributes) handle cleanup internally.

IDL API

Two methods were added to nsIPermissionManager:

updateLastInteractionForPrincipal

Manually update the last interaction timestamp for a principal. Skips system principals and private browsing contexts.

removeOrphanedInteractionRecords

Returns a Promise that resolves once orphaned interaction records have been deleted. Used by ClearDataService to ensure interaction data is cleaned up alongside permissions.

Telemetry

Three Glean metrics are recorded each time permissions are expired (see extensions/permissions/metrics.yaml for full definitions):

  • permissions.unused_permission_age_at_expiry – distribution of time since last site interaction when a permission was expired.

  • permissions.unused_permissions_expired_by_type – count of expired permissions broken down by permission type.

  • permissions.unused_permission_modified_age_at_expiry – distribution of time since the permission was last modified when it was expired.