publish / check-anonymity.sh
870 words
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/usr/bin/env bash
# check-anonymity.sh — pre-publish audit for a repo about to go public.
#
#   ./check-anonymity.sh <repo-dir> [ident]
#
# ident defaults to the repo's `git config user.name`. Exits non-zero on any
# hard failure; heuristic findings are warnings unless STRICT=1.
#
# Identity patterns (your real names/emails, one extended-regex per line,
# '#' comments) are read from an UNTRACKED local file so that no published
# byte ever contains them:
#   $IDPAT_FILE, default ~/.config/cod-sursa/idpat
#
# Checks:
#   1  local git identity is <ident> / <ident>@noreply.local
#   2  every author+committer across all refs is exactly that identity
#   3  no identity-pattern bytes in ANY git object (all history, all refs)
#   4  no identity-pattern bytes in the working tree (incl. untracked-but-
#      committable files is your job; this scans tracked files)
#   5  no build output (deploy/.site) committed
#   6  heuristics: secret-shaped tracked files/content, absolute home paths
set -euo pipefail

REPO=${1:?usage: check-anonymity.sh <repo-dir> [ident]}
REPO=$(cd "$REPO" && pwd)
IDENT=${2:-$(git -C "$REPO" config user.name || true)}
IDPAT_FILE=${IDPAT_FILE:-"$HOME/.config/cod-sursa/idpat"}

fail=0
warn=0
say()  { printf '%s\n' "$*"; }
ok()   { say "  ok    $*"; }
bad()  { say "  FAIL  $*"; fail=1; }
note() { say "  warn  $*"; warn=1; }

say "== anonymity audit: $REPO (ident: ${IDENT:-<unset>})"

# ---- 0. patterns -----------------------------------------------------------
if [ ! -s "$IDPAT_FILE" ]; then
  bad "identity patterns file missing/empty: $IDPAT_FILE"
  say "        create it (one regex per line) — the leak scan cannot run without it"
  exit 1
fi
IDPAT=$(grep -v '^[[:space:]]*#' "$IDPAT_FILE" | grep -v '^[[:space:]]*$' | paste -sd'|' -)
[ -n "$IDPAT" ] || { bad "no usable patterns in $IDPAT_FILE"; exit 1; }
ok "patterns loaded from $IDPAT_FILE ($(printf '%s' "$IDPAT" | awk -F'|' '{print NF}') pattern(s))"

# ---- 1. local identity -----------------------------------------------------
NAME=$(git -C "$REPO" config user.name || true)
MAIL=$(git -C "$REPO" config user.email || true)
if [ -z "$IDENT" ]; then
  bad "no ident given and repo has no local user.name"
elif [ "$NAME" = "$IDENT" ] && [ "$MAIL" = "$IDENT@noreply.local" ]; then
  ok "local identity: $NAME <$MAIL>"
else
  bad "local identity is '$NAME <$MAIL>', expected '$IDENT <$IDENT@noreply.local>'"
fi

# ---- 2. history identities -------------------------------------------------
if git -C "$REPO" rev-parse -q --verify HEAD >/dev/null 2>&1; then
  AUTHORS=$(git -C "$REPO" log --all --format='%an <%ae>%n%cn <%ce>' | sort -u)
  if [ "$AUTHORS" = "$IDENT <$IDENT@noreply.local>" ]; then
    ok "history: single identity across all refs"
  else
    bad "history has non-anonymous identities:"
    printf '%s\n' "$AUTHORS" | sed 's/^/        /'
  fi
else
  note "no commits yet — history checks skipped (set identity before committing!)"
fi

# ---- 3. identity bytes in any git object -----------------------------------
# --batch-all-objects covers every object in the db, packed or loose,
# including unreachable ones left behind by rewrites.
if git -C "$REPO" rev-parse -q --verify HEAD >/dev/null 2>&1; then
  HITS=$(git -C "$REPO" cat-file --batch-all-objects --batch 2>/dev/null \
    | grep -aicE "$IDPAT" || true)
  if [ "${HITS:-0}" = "0" ]; then
    ok "object scan: no identity bytes in any git object"
  else
    bad "identity patterns matched in $HITS line(s) of git object data"
    say "        locate with: git -C '$REPO' grep -iE '<pattern>' \$(git -C '$REPO' rev-list --all)"
    say "        (unreachable objects: rewrite happened? re-clone --no-local drops them,"
    say "         but the source of the match must still be found and removed)"
  fi
fi

# ---- 4. identity bytes in tracked working tree -----------------------------
TREE_HITS=$(git -C "$REPO" ls-files -z 2>/dev/null \
  | xargs -0 grep -ailE "$IDPAT" -- 2>/dev/null || true)
if [ -z "$TREE_HITS" ]; then
  ok "working tree: no identity bytes in tracked files"
else
  bad "identity patterns in tracked files:"
  printf '%s\n' "$TREE_HITS" | sed 's/^/        /'
fi

# ---- 5. build output committed ---------------------------------------------
if git -C "$REPO" rev-parse -q --verify HEAD >/dev/null 2>&1 \
  && git -C "$REPO" ls-tree -r --name-only HEAD | grep -q '\.site'; then
  bad "deploy/.site committed into the repo"
else
  ok "no nested build output committed"
fi

# ---- 6. heuristics ---------------------------------------------------------
SECRET_PAT='BEGIN[[:print:]]*PRIVATE KEY|api[_-]?key[[:space:]"'\'':=]+[A-Za-z0-9_\-]{16,}|secret[[:space:]"'\'':=]+[A-Za-z0-9_\-]{16,}|Bearer [A-Za-z0-9_\-\.]{20,}|eyJ[A-Za-z0-9_\-]{20,}'
S_HITS=$(git -C "$REPO" ls-files -z 2>/dev/null \
  | xargs -0 grep -alE "$SECRET_PAT" -- 2>/dev/null || true)
if [ -n "$S_HITS" ]; then
  note "secret-shaped content in tracked files (verify these are placeholders):"
  printf '%s\n' "$S_HITS" | sed 's/^/        /'
fi
if git -C "$REPO" ls-files | grep -qE '(^|/)\.env(\.|$)|id_rsa|id_ed25519|\.pem$|\.p12$'; then
  note "credential-looking filenames are tracked:"
  git -C "$REPO" ls-files | grep -E '(^|/)\.env(\.|$)|id_rsa|id_ed25519|\.pem$|\.p12$' | sed 's/^/        /'
fi
# bracketed first letters so this script's own bytes never spell a home-dir
# prefix — an identity pattern like the literal macOS home prefix would
# otherwise match the auditor itself
P_HITS=$(git -C "$REPO" ls-files -z 2>/dev/null \
  | xargs -0 grep -alE '/[U]sers/[a-z]|/[h]ome/[a-z]' -- 2>/dev/null || true)
if [ -n "$P_HITS" ]; then
  note "absolute home paths in tracked files (may reveal a username):"
  printf '%s\n' "$P_HITS" | sed 's/^/        /'
fi

# ---- verdict ---------------------------------------------------------------
if [ "$fail" != 0 ]; then
  say "== FAILED — do not publish"
  exit 1
fi
if [ "$warn" != 0 ] && [ "${STRICT:-0}" = "1" ]; then
  say "== warnings present and STRICT=1 — do not publish"
  exit 1
fi
if [ "$warn" != 0 ]; then
  say "== OK — safe to publish (with warnings above)"
else
  say "== OK — safe to publish"
fi

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/publish.git