feat(privacy): add watcher-side privacy filter (drop/redact before send) - #135
TimeToBuildBob wants to merge 4 commits into
Conversation
Greptile SummaryAdds watcher-side privacy filtering before window events are sent to the server.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the current code catches both regex syntax errors and TypeError from non-string pattern values, logs the malformed rule, and continues startup. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Read current window] --> B[Apply privacy-filter rules]
B -->|Drop| C[Skip heartbeat]
B -->|Pass or redact| D[Apply research/title transforms]
D --> E[Create window event]
E --> F[Send heartbeat to aw-server]
Reviews (2): Last reviewed commit: "fix(privacy): skip non-string regex patt..." | Re-trigger Greptile |
|
@greptileai review |
|
@TimeToBuildBob The watcher-side privacy filter should also be configurable via webui (non-technical user-facing central config) similar to the server-side privacy filter (but watcher-only config should also be supported, if e.g. server is untrusted). |
Adds a client-side privacy filter that drops or redacts window events before they are sent to aw-server, so sensitive data never leaves the machine at all. New module `aw_watcher_window/privacy_filter.py`: - `compile_privacy_rules(raw)` — validates and compiles regex patterns; invalid regexes and unknown actions are skipped with an error log - `apply_privacy_filters(window, rules)` — returns None (drop) or a filtered copy (redact); never mutates the input dict Config via `[[aw-watcher-window.privacy_filter]]` TOML tables: pattern = "(?i)private browsing|incognito" action = "drop" # or "redact" field = "title" # optional; defaults to "title" replacement = "excluded" # optional; used for redact action Integration in main.py: - Privacy filter runs before research and exclude_title transforms - A None return from transform_window skips the heartbeat entirely - heartbeat_loop passes privacy_filter_rules through 23 new tests in tests/test_privacy_filter.py; all existing tests pass. Mirrors the server-side privacy_filters engine in aw-server-rust (#600) so users can enforce the same rules at both the watcher and server layers. macOS note: the swift strategy bypasses this Python transform; use --strategy jxa or --strategy applescript to enable it on macOS.
cdf5c3b to
9176770
Compare
|
Good point. The TOML config in this PR covers the watcher-only path you described (useful when the server is untrusted or the user wants filtering before data ever leaves the machine). For webui configurability, the shape I'd expect is:
That touches aw-server and aw-webui (separate repos), so it's naturally a follow-up rather than something that fits cleanly into this PR. Would you prefer I scope it here (which would expand the PR significantly) or open a follow-up issue to track it? Also resolved the rebase conflict with master (#136, #137) — the branch is up to date now. |
codeql-action v2 is deprecated as of 2025-01-10 (GitHub changelog). Update all three action steps (init, autobuild, analyze) to v3. Also bump actions/checkout from v3 to v4 while here. This fixes the failing 'Analyze (python)' CI check on PR ActivityWatch#135.
|
Still waiting for direction on scope — whether to include the webui-configurable path in this PR or track it as a follow-up. The watcher-only TOML config path is ready, CI green, and rebased. Happy to expand or split depending on your preference. |
|
CI-green and mergeable (Greptile 5/5) — waiting only on a maintainer click. This PR is ready to merge, but the bot has pull-only access to this repo and can't self-merge — surfacing it here so it isn't lost. The monitoring loop will stop re-flagging it now that this note is posted. |
🤖 AI code reviewSafe to merge — 2 findings disposed (fixed in 1d68a6b; rejected)Updated after inline dispositions on finding threads — this is the current state; the verdict below is frozen at review time and is kept as the historical record of that pass. Finding disposition
Adds a watcher-side privacy filter that drops or redacts window events before they are sent to aw-server. New privacy_filter.py compiles and applies regex rules; config.py reads [[aw-watcher-window.privacy_filter]] TOML tables; main.py runs the filter first in transform_window and skips heartbeats for dropped events, and refuses to start under the macOS swift strategy when rules are configured. Adds tests for the filter, config parsing, and the swift refusal, plus README documentation and CodeQL action version bumps. Needs a look — P2 onlyConfidence 4/5 1 finding ·
|
| commit | score | findings | engine | when |
|---|---|---|---|---|
1bdacd80303b |
2/5 | 2 | llm | 2026-09-16 08:52 UTC |
Reviewed 1d68a6b12d6d · openrouter/deepseek/deepseek-v4-flash-0731 · llm engine · 30s · about this reviewer
Maintainer commands
@TimeToBuildBob review (own line) — fresh review · @TimeToBuildBob fix — a worker acts on the findings. Once per comment; 👀 = received.
The macOS swift strategy sends heartbeats from a separate binary and cannot apply privacy_filter rules. Refuse to start when those rules are configured so titles are not leaked; use jxa or applescript. Also wrap a single privacy_filter table (list(mapping) was silently dropping the rule) and skip non-string field/replacement values. Git-Session-Id: 106b67f2-1ed0-575b-af3a-59ab2170164e
Summary
Adds a client-side privacy filter that drops or redacts window events before they are sent to aw-server, so sensitive data never leaves the machine at all.
This mirrors the server-side
privacy_filtersengine that landed in aw-server-rust#600, allowing users to enforce the same rules at both layers. Previously the watcher only supported dropping titles viaexclude_title/exclude_titles(redact to"excluded"). This PR adds explicitdropandredactactions with user-configurable regex patterns and replacement strings.What changed
aw_watcher_window/privacy_filter.py(new) — pure Python, no new deps:compile_privacy_rules(raw)— validates and compiles regex patterns; invalid regexes and unknown actions are skipped with an error log rather than crashingapply_privacy_filters(window, rules)— returnsNone(drop) or a filtered copy (redact); never mutates the input dictaw_watcher_window/config.py— exposesprivacy_filter_rulesfrom[[aw-watcher-window.privacy_filter]]TOML array of tablesaw_watcher_window/main.py— privacy filter runs first intransform_window; aNonereturn skips the heartbeat;heartbeat_loopthreads the rules throughtests/test_privacy_filter.py(new) — 23 tests covering compile, drop, redact, input immutability, edge casesREADME.md— documents the new config section with examplesConfig example
Rule fields:
pattern(Python regex),field(default"title"),action("drop"or"redact"),replacement(for redact; default"excluded").Ordering
Privacy filter runs before Research Edition and
exclude_titletransforms. A"drop"rule exits immediately — subsequent rules are not evaluated for that event.macOS note
The default
swiftstrategy bypasses this Python transform (same as Research Edition). Use--strategy jxaor--strategy applescriptto enable watcher-side privacy filtering on macOS.Test plan
python3 -m pytest tests/test_privacy_filter.py -v— 23 passedpython3 -m pytest tests/test_main.py -v— 12 passed (no regressions)droprule matching title: heartbeat skippedredactrule matching title: heartbeat sent with replacement value