feat(privacy-filter): capture-group replacement in redact - #665
feat(privacy-filter): capture-group replacement in redact#665TimeToBuildBob wants to merge 7 commits into
Conversation
Redact previously replaced the whole field with a static string. When the pattern has capturing groups, treat replacement as a regex template ($1, $name) like awatcher filters. No capturing groups keeps the existing whole-field behavior. Related to ActivityWatch#659.
Greptile SummaryThe PR adds opt-in capture-group substitution for privacy-filter redaction while retaining whole-field replacement for static or invalid templates.
Confidence Score: 3/5The PR is not yet safe to merge because malformed mixed capture templates can leave sensitive event-field content unredacted. The replacement parser can accept an earlier valid capture after encountering a dangling or unsupported dollar form, causing replace_all to preserve unmatched sensitive content at the datastore privacy boundary. Files Needing Attention: aw-datastore/src/privacy_filter.rs
|
| Filename | Overview |
|---|---|
| aw-datastore/src/privacy_filter.rs | Adds capture-aware redaction and extensive tests, but mixed malformed templates can bypass the intended fail-closed behavior. |
Reviews (4): Last reviewed commit: "fix(privacy-filter): reject $0 and unmat..." | Re-trigger Greptile
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #665 +/- ##
==========================================
+ Coverage 70.81% 78.81% +8.00%
==========================================
Files 51 66 +15
Lines 2916 5524 +2608
==========================================
+ Hits 2065 4354 +2289
- Misses 851 1170 +319 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…capture template Capture-group replacement was keyed only on captures_len > 1, so an existing stored rule like `(token)` + `REDACTED` switched from whole-field redaction to replace_all and leaked unmatched text (`token=abc token=def` became `REDACTED=abc REDACTED=def`). Opt in only when the replacement contains a capture template (`$1`, `$name`). Static replacements keep whole-field behavior. Addresses Greptile P1 on ActivityWatch#665.
replacement_is_capture_template now requires every $ reference to name a group that exists on the compiled regex. A replacement like `REDACTED $5` on a 1-group pattern would otherwise take replace_all, expand $5 to "", and leak unmatched field text. Addresses in-band P1 on ActivityWatch#665.
The regex crate only interpolates $N / $name / ${name}. $& /$` /$' are
Perl-only; treating them as valid refs would take replace_all and leak
unmatched field text. Fall through to whole-field redaction instead.
|
@greptileai review |
`${}` is a named ref with an empty name, not `$0`. The regex crate
expands it to "" and would leak unmatched field text via replace_all.
Addresses Greptile P1 on ActivityWatch#665.
Git-Session-Id: 589e6fad-191b-5e97-93c6-70c22c1f5d4c
|
@greptileai review |
$0 / ${0} is the whole match; replace_all would persist the match and
unmatched field text. Alternation groups that exist on the regex but
do not participate in a match expand to empty and leak leftover text.
Stay whole-field unless every referenced group is present in every match.
Addresses Greptile P1 on ActivityWatch#665.
Git-Session-Id: d3267f9a-1817-52b6-92cd-86e006957db2
|
@greptileai review |
A valid $1 plus a dangling or unsupported dollar form ($1$, $1$&) used to skip the suffix and still enable replace_all, leaking unmatched field text. Any unparsed $ now keeps the rule whole-field. $1$$ (group plus literal dollar) still substitutes. Git-Session-Id: 1c0d8fb8-0b35-5596-9027-cd416fbe048a
Greptile convergence adjudicationHit the re-review cap. Not triggering another Greptile review. Fixed this session
Earlier rounds (already on the branch)
Remaining
CI
Domain riskPrivacy-filter is a datastore boundary. Maintainer should sanity-check: (1) static Convergence
Merge-ready does not mean auto-merge — maintainer judgment. |
Summary
Follow-up to #659:
redactcurrently replaces the whole field with a static string. Awatcher filters let you extract capturing groups and put them in the replacement ($1,$name). This adds the same, opt-in via the replacement template.Capture substitution runs only when the pattern has capturing groups and
replacementcontains a real capture template ($1,$name,${name}) whose every$ref names a group that exists and participates in every match.$0/${0}(whole-match identity), dangling refs, empty${}, unmatched alternation/optional groups, and mixed malformed templates ($1$,$1$&) stay whole-field. Static replacements keep whole-field redaction even if the pattern has groups — so existing stored rules like(token)+REDACTEDdo not switch toreplace_alland leak unmatched text.Example (same as the awatcher README):
● (.*)$1"● file.rs - Visual Studio Code"→"file.rs - Visual Studio Code"Also works for URL path stripping:
https://([^/]+)/.*+https://$1/keeps the host.Test plan
cargo test -p aw-datastore --lib privacy_filter(30 passed)$1/$namereplacement in the webui