Filters
Allow and deny lists that decide which apps and sites are recorded, applied before events reach the store.
Filters decide which apps and sites are recorded. Zanei has three capture-time scopes: all events, typed/copied bodies, and content snapshots. An unattributed clipboard change can still be stored as clipboard.copy with app.name: "Unknown" and null attribution and body because it cannot be matched to an app.
Capture-time and query-time filtering
There are two ways to narrow by app, and they do different things:
| Capture-time filters (this page) | Query-time filters (--app etc.) |
|
|---|---|---|
| Where it acts | Before the store: matching events are discarded | Only when reading data back |
| Purpose | Keep sensitive apps out of the store | Narrow results |
| Configured via | zanei filter commands / [filter] in config.toml |
Per-command flags like --app, --types |
| From MCP | Cannot be changed (read-only) | Usable as query_events arguments |
Managing the lists
zanei filter show # all three scopes and their modes
zanei filter exclude-app add 1Password # all events
zanei filter text-content exclude-app add Slack # bodies only; facts remain
zanei filter text-content only-site add github.com # Chrome/Safari typed/copied bodies only on this site
zanei filter content-snapshot only-app add Terminal # snapshots only from this app
zanei filter content-snapshot exclude-site add mail.google.com
The command shape is zanei filter [<scope>] <list> add|remove [VALUE]. Omit <scope> for [filter], or use text-content for [filter.text_content] and content-snapshot for [filter.content_snapshot]. The four lists have the same meaning in every scope:
| List | Config key suffix | Meaning |
|---|---|---|
exclude-app |
exclude_apps |
Exclude these apps |
only-app |
include_only_apps |
When non-empty, include only these apps |
exclude-site |
exclude_websites |
Exclude these browser URL hosts |
only-site |
include_only_websites |
When non-empty, include only these browser URL hosts |
For each app or site axis, a non-empty include_only_* list selects only mode; an empty one selects exclude mode. exclude_* always wins in either mode. The daemon reloads all three scopes at roughly 2-second intervals, so changes apply within a few seconds without a restart.
What each scope removes
The order is fixed:
- Built-in app exclusions.
[filter]app rules, which discard every attributed event for a rejected app.[filter]website rules, which discard matching browser URL events and suppress typed/copied bodies and snapshots for that window.[filter.text_content]app and website rules. Outside this scope,input.key.text,ui.value.data.text, clipboardtext/size_bytes, andelement.valueare null, but the event and non-content facts remain as ifcapture.text_contentwere false there.[filter.content_snapshot]app and website rules. Outside this scope, nocontent.snapshotevent is created.
Website rules apply to Chrome and Safari, using the same scopes for standalone and embedded recording. Other browsers are controlled by app rules. Safari private-window exclusion is not guaranteed; its default content exclusions remain unchanged.
Admitting a supported browser on a filter reload starts its tracking without a recorder restart; excluding it again stops that tracking. The macOS Automation prompt appears when that browser is first admitted and the permission is first needed.
Choosing apps
zanei apps # installed, running, and recently recorded apps
zanei apps slack # case-insensitive name or bundle-ID search
zanei apps slack --json # machine-readable candidates
For every app list, add accepts a display name or bundle ID, resolves it against the same candidates, and saves the bundle ID when one exists. It prints the normalized result, such as Added com.apple.Terminal (Terminal). An unresolved value is not saved and exits with code 2, with a close candidate when available. Use --unverified only when you intentionally need to save an app that is not installed; Zanei warns that it could not verify the value.
Running add without a value opens a numbered selector ordered by recent use. In a non-TTY or with --quiet, a value is required and omission exits with code 2. remove resolves against the current list, so an uninstalled entry can still be removed. zanei filter show adds display names to resolved entries and marks unresolved hand-edited values as (not installed).
zanei apps [QUERY] [--json] needs no TCC permission and works while the daemon is stopped. The table shows name, bundle ID, installed/running/recent sources, and last use. JSON returns { "apps": [...], "recent_unavailable": null, "installed_unreadable": 0 }; if the store cannot provide recent apps, installed and running results remain and recent_unavailable explains why, while installed_unreadable counts app bundles whose metadata could not be read.
Default browser exclusions
Both content scopes exclude these browsers by default because Zanei cannot reliably identify their private windows:
| Browser | Bundle ID |
|---|---|
| Safari | com.apple.Safari |
| Firefox | org.mozilla.firefox |
| Brave | com.brave.Browser |
| Edge | com.microsoft.edgemac |
| Vivaldi | com.vivaldi.Vivaldi |
| Arc | company.thebrowser.Browser |
Adding one of these browsers with only-app, or removing it from exclude-app, prints a warning unless --quiet is set. The defaults are editable rather than built-in blocks. In 0.3.0 they also apply to existing users with capture.text_content = true: bodies become null in these browsers by default, while events and non-content facts remain.
Matching
- Apps — match by
bundle_id(recommended; display names can change). An app that has abundle_idis matched only by it; a display-name entry applies only to events whose app has nobundle_id. Matching is case-insensitive. - Sites — dot-boundary suffix match on the browser URL host:
example.comalso coversapi.example.com, but notevil-example.com. There is no Public Suffix List handling — an entry likecommatches every.comhost, so prefer full domain names. - Precedence — if
include_only_*is non-empty, an event is captured only if it is in that list and not inexclude_*. If empty, everything not excluded is captured. - App-level exclusion is total for attributed events — it drops all event types that can be attributed to that app (
ui.*,input.*,window.*, …). An unattributed clipboard change may remain underapp.name: "Unknown".
Always-on exclusions
Independent of your lists:
- Private browsing — Chrome Incognito produces no URL events and keeps text-content-derived bodies null. Titles and interaction metadata can remain.
- Secure Input — while Secure Input is active, Zanei takes no content snapshot.
- Built-in exclusions — password managers and credential stores (
1Password,Keychain Access, …) are excluded by a hard-coded layer that cannot be lifted, not even withinclude_only_apps. It is separate from the default entries you see inexclude_appsinconfig.toml; editing those does not affect it.filter showlists the built-in entries separately from your own.
Filters and MCP
Capture-time filters cannot be modified over MCP; the MCP server is read-only. Filter management happens only through the CLI or config.toml. See MCP server.