snapshot of https://github.com/ActivityWatch/aw-client.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Build / Test on ubuntu-latest (push) Successful in 2m4s
Examples / Test examples (ubuntu-latest, 3.10) (push) Successful in 2m16s
Lint / lint (push) Successful in 1m25s
Build / Test on macOS-latest (push) Has been cancelled
Build / Test on windows-latest (push) Has been cancelled
CodeQL / Analyze (python) (push) Failing after 1s
Some checks failed
Build / Test on ubuntu-latest (push) Successful in 2m4s
Examples / Test examples (ubuntu-latest, 3.10) (push) Successful in 2m16s
Lint / lint (push) Successful in 1m25s
Build / Test on macOS-latest (push) Has been cancelled
Build / Test on windows-latest (push) Has been cancelled
CodeQL / Analyze (python) (push) Failing after 1s
This commit is contained in:
147
examples/redact_sensitive.py
Normal file
147
examples/redact_sensitive.py
Normal file
@@ -0,0 +1,147 @@
|
||||
"""
|
||||
Interactive script to redact sensitive data.
|
||||
Be careful to not delete stuff you want to keep!
|
||||
|
||||
Issues/improvements:
|
||||
- If an event matches the sensitive string, only the sensitive field will be redacted (so if the title matches but not the URL, the URL will remain unredacted)
|
||||
- One might not want to redact to the non-informative 'REDACTED', but instead to a string with secret meaning.
|
||||
- No preview of the events/strings to be redacted.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from copy import deepcopy
|
||||
from typing import (
|
||||
List,
|
||||
Pattern,
|
||||
Set,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from aw_client import ActivityWatchClient
|
||||
from aw_core import Event
|
||||
|
||||
aw: ActivityWatchClient
|
||||
|
||||
REDACTED = "REDACTED"
|
||||
DRYRUN = True
|
||||
|
||||
|
||||
def main():
|
||||
global DRYRUN
|
||||
if "--wet" in sys.argv:
|
||||
DRYRUN = False
|
||||
|
||||
global aw
|
||||
aw = ActivityWatchClient(testing=True)
|
||||
|
||||
buckets = aw.get_buckets()
|
||||
print("Buckets: ")
|
||||
print("\n".join([" - " + bid for bid in buckets.keys()]) + "\n")
|
||||
|
||||
bid_to_redact = input(
|
||||
"In which bucket are the events you want to redact? (* for all): "
|
||||
)
|
||||
assert bid_to_redact == "*" or bid_to_redact in buckets, "Not a valid option"
|
||||
|
||||
regex_or_string = input(
|
||||
"Do you want to search by regex or string? (regex/string): "
|
||||
)
|
||||
assert regex_or_string in ["regex", "string"], "Not a valid option"
|
||||
|
||||
print("\nNOTE: Matching is not case sensitive!")
|
||||
pattern: Union[str, Pattern]
|
||||
if regex_or_string == "string":
|
||||
pattern = input("Enter a string indicating sensitive content: ").lower()
|
||||
else:
|
||||
pattern = re.compile(
|
||||
input("Enter a regex indicating sensitive content: ").lower()
|
||||
)
|
||||
|
||||
print("")
|
||||
if DRYRUN:
|
||||
print(
|
||||
"NOTE: Performing a dry run, no events will be modified. Run with --wet to modify events."
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"WARNING: Note that this performs an operation that cannot be undone. We strongly recommend that you backup/export your data before proceeding."
|
||||
)
|
||||
input("Press ENTER to continue, or Ctrl-C to abort")
|
||||
|
||||
if bid_to_redact == "*":
|
||||
for bucket_id in buckets.keys():
|
||||
if bucket_id.startswith("aw-watcher-afk"):
|
||||
continue
|
||||
_redact_bucket(bucket_id, pattern)
|
||||
else:
|
||||
_redact_bucket(bid_to_redact, pattern)
|
||||
|
||||
|
||||
def _redact_bucket(bucket_id: str, pattern: Union[str, Pattern]):
|
||||
print(f"\nChecking bucket: {bucket_id}")
|
||||
|
||||
global aw
|
||||
events = aw.get_events(bucket_id, limit=-1)
|
||||
sensitive_ids = _find_sensitive(events, pattern)
|
||||
print(f"Found {len(sensitive_ids)} sensitive events")
|
||||
|
||||
if not sensitive_ids:
|
||||
return
|
||||
|
||||
yes_redact = input(
|
||||
f"Do you want to replace all the matching strings with '{REDACTED}'? (y/N): "
|
||||
)
|
||||
if yes_redact == "y":
|
||||
for e in events:
|
||||
if e.id in sensitive_ids:
|
||||
e_before = e
|
||||
e = _redact_event(e, pattern)
|
||||
print(f"\nData before: {e_before.data}")
|
||||
print(f"Data after: {e.data}")
|
||||
|
||||
if DRYRUN:
|
||||
print("DRYRUN, would do: aw.insert_event(bucket_id, e)")
|
||||
else:
|
||||
aw.delete_event(bucket_id, cast(int, e_before.id))
|
||||
aw.insert_event(bucket_id, e)
|
||||
print("Redacted event")
|
||||
|
||||
|
||||
def _check_event(e: Event, pattern: Union[str, Pattern]) -> bool:
|
||||
for v in e.data.values():
|
||||
if isinstance(v, str):
|
||||
if isinstance(pattern, str):
|
||||
if pattern in v.lower():
|
||||
return True
|
||||
else:
|
||||
if pattern.findall(v.lower()):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _redact_event(e: Event, pattern: Union[str, Pattern]) -> Event:
|
||||
e = deepcopy(e)
|
||||
for k, v in e.data.items():
|
||||
if isinstance(v, str):
|
||||
if isinstance(pattern, str):
|
||||
if pattern in v.lower():
|
||||
e.data[k] = REDACTED
|
||||
else:
|
||||
if pattern.findall(v.lower()):
|
||||
e.data[k] = REDACTED
|
||||
return e
|
||||
|
||||
|
||||
def _find_sensitive(el: List[Event], pattern: Union[str, Pattern]) -> Set:
|
||||
sensitive_ids = set()
|
||||
for e in el:
|
||||
if _check_event(e, pattern):
|
||||
sensitive_ids.add(e.id)
|
||||
|
||||
return sensitive_ids
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user