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
148 lines
4.2 KiB
Python
148 lines
4.2 KiB
Python
"""
|
|
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()
|