| 1 | #!/usr/bin/env bash |
| 2 | # redact-events.sh -- opt-in redaction of identifying fields. |
| 3 | # |
| 4 | # Replaces UUID-shaped values in known identifying fields with |
| 5 | # stable placeholders like <redacted-A>, <redacted-B>, ... so |
| 6 | # patterns are still identifiable (same machine_guid -> same |
| 7 | # placeholder within the dump) but the raw value is gone. |
| 8 | # |
| 9 | # Use this only when sharing a dump externally. Default |
| 10 | # workflow keeps raw events under .local/audits/... |
| 11 | # (gitignored). |
| 12 | |
| 13 | set -euo pipefail |
| 14 | |
| 15 | usage() { |
| 16 | cat <<'EOF' |
| 17 | redact-events.sh [--input PATH] [--output PATH] |
| 18 | |
| 19 | Redacts identifying fields in a Function-envelope JSON dump or |
| 20 | a flat array of objects. |
| 21 | |
| 22 | Identifying fields redacted: |
| 23 | AE_AGENT_ID, AE_HOST_ID, AE_AGENT_NODE_ID, AE_AGENT_CLAIM_ID, |
| 24 | AE_HOST_BOOT_ID, AE_AGENT_EPHEMERAL_ID, AE_HW_SYS_UUID, |
| 25 | _MACHINE_ID, _BOOT_ID |
| 26 | |
| 27 | Stable mapping: same value -> same placeholder within the dump. |
| 28 | |
| 29 | Defaults: |
| 30 | --input stdin |
| 31 | --output stdout |
| 32 | EOF |
| 33 | } |
| 34 | |
| 35 | INPUT= |
| 36 | OUTPUT= |
| 37 | while [ $# -gt 0 ]; do |
| 38 | case "$1" in |
| 39 | --input) INPUT="$2"; shift 2 ;; |
| 40 | --output) OUTPUT="$2"; shift 2 ;; |
| 41 | -h|--help) usage; exit 0 ;; |
| 42 | *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; |
| 43 | esac |
| 44 | done |
| 45 | |
| 46 | if [ -n "$INPUT" ]; then exec < "$INPUT"; fi |
| 47 | if [ -n "$OUTPUT" ]; then exec > "$OUTPUT"; fi |
| 48 | |
| 49 | jq -c ' |
| 50 | def redact_fields: |
| 51 | ["AE_AGENT_ID","AE_HOST_ID","AE_AGENT_NODE_ID","AE_AGENT_CLAIM_ID", |
| 52 | "AE_HOST_BOOT_ID","AE_AGENT_EPHEMERAL_ID","AE_HW_SYS_UUID", |
| 53 | "_MACHINE_ID","_BOOT_ID"]; |
| 54 | |
| 55 | def alphabet: |
| 56 | ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P", |
| 57 | "Q","R","S","T","U","V","W","X","Y","Z"]; |
| 58 | |
| 59 | . as $root |
| 60 | | reduce (redact_fields[]) as $f ( |
| 61 | {state: {map: {}, idx: 0}, root: $root}; |
| 62 | . as {state: $st, root: $r} |
| 63 | | ($r | .. | objects | select(has($f)) | .[$f] | tostring) as $vals |
| 64 | | reduce $vals as $v ( |
| 65 | .; |
| 66 | if (.state.map | has($v)) then |
| 67 | . |
| 68 | else |
| 69 | .state.map[$v] = ("<redacted-" + (alphabet[(.state.idx % 26)]) + (if .state.idx >= 26 then ((.state.idx / 26 | floor) | tostring) else "" end) + ">") |
| 70 | | .state.idx += 1 |
| 71 | end |
| 72 | ) |
| 73 | ) |
| 74 | | .root as $r |
| 75 | | .state.map as $m |
| 76 | | ($r | walk( |
| 77 | if type == "object" then |
| 78 | with_entries( |
| 79 | if (.key | IN("AE_AGENT_ID","AE_HOST_ID","AE_AGENT_NODE_ID","AE_AGENT_CLAIM_ID","AE_HOST_BOOT_ID","AE_AGENT_EPHEMERAL_ID","AE_HW_SYS_UUID","_MACHINE_ID","_BOOT_ID")) |
| 80 | then .value = ($m[(.value | tostring)] // .value) |
| 81 | else . |
| 82 | end |
| 83 | ) |
| 84 | else . |
| 85 | end |
| 86 | )) |
| 87 | ' |