← back to Govarbitrage

apps/mobile/device-proof-evidence/privacy-scan.sh

51 lines

#!/bin/bash
# privacy-scan.sh — PII/private-content pre-screen for App Review recordings (TK-10279).
#
# verify-recording.sh checks ONLY codec/dimensions/duration/black — it canNOT see
# private content, which is exactly how the first recording leaked a Messages thread
# (name + phone + message body) and got the upload rejected. This scans the actual
# PIXELS: it samples frames and asks a LOCAL vision model ($0, ollama — no paid API,
# nothing leaves the machine) whether any frame shows content that must never appear
# in an Apple review video.
#
# It is a PRE-SCREEN, not a substitute for a human eyeball: exit 0 = no PII detected
# (still do a final manual review), exit 1 = PII flagged (DO NOT UPLOAD), exit >1 = error.
#
# Usage: privacy-scan.sh <recording.mov|.mp4> [seconds-between-frames=2]
set -uo pipefail
FILE="${1:-}"; STEP="${2:-2}"
MODEL="${PRIVACY_SCAN_MODEL:-qwen2.5vl:7b}"
OLLAMA="${OLLAMA_HOST:-http://localhost:11434}"
[ -f "$FILE" ] || { echo "usage: $0 <recording.(mov|mp4)> [step-secs]" >&2; exit 64; }

TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
echo "▶ extracting frames (1 every ${STEP}s) from $(basename "$FILE")…"
ffmpeg -hide_banner -loglevel error -i "$FILE" -vf "fps=1/${STEP}" -q:v 3 "$TMP/f%04d.jpg" || { echo "FAIL: ffmpeg extract" >&2; exit 68; }
N=$(ls "$TMP"/*.jpg 2>/dev/null | wc -l | tr -d ' ')
[ "$N" -gt 0 ] || { echo "FAIL: no frames extracted" >&2; exit 68; }
echo "  ${N} frames · model ${MODEL} (local, \$0)"

PROMPT='You are screening ONE frame from an App Store review screen-recording of an iOS app called GovArbitrage (a government-surplus auction analyzer showing auction listings, prices, ROI, scores, and a settings screen). Report ONLY private/sensitive content that must NOT appear in a recording sent to Apple: an incoming notification/banner from another app, a Messages/SMS/WhatsApp/email conversation, a visible personal human name, a phone number, an email address, or a password field showing typed characters. The app own UI (auction items, dollar amounts, percentages, a Settings screen with blank or masked/dotted password fields, a server URL) is NOT private. Answer EXACTLY the single word CLEAN if the frame shows only the app or a neutral home screen with no such content. Otherwise answer FLAG: followed by a short description of the private content.'

FLAGGED=0; FLAGS=""
i=0
for img in "$TMP"/*.jpg; do
  i=$((i+1)); t=$(( (i-1) * STEP ))
  b64=$(base64 -i "$img" | tr -d '\n')
  resp=$(curl -s "$OLLAMA/api/generate" -d "{\"model\":\"$MODEL\",\"prompt\":$(printf '%s' "$PROMPT" | python3 -c 'import json,sys;print(json.dumps(sys.stdin.read()))'),\"images\":[\"$b64\"],\"stream\":false}" \
    | python3 -c 'import json,sys;print((json.load(sys.stdin).get("response") or "").strip().replace(chr(10)," "))' 2>/dev/null)
  if printf '%s' "$resp" | grep -qiE '^FLAG|\bFLAG:'; then
    FLAGGED=$((FLAGGED+1)); FLAGS="${FLAGS}\n  ~${t}s: ${resp}"
    printf '  \033[31m● %ss FLAG\033[0m %s\n' "$t" "$resp"
  fi
done

echo "────────────────────────────────────────"
if [ "$FLAGGED" -gt 0 ]; then
  printf '\033[31m✗ PRIVACY FAIL: %d frame(s) flagged — DO NOT UPLOAD:\033[0m%b\n' "$FLAGGED" "$FLAGS"
  exit 1
fi
echo "✓ privacy pre-scan CLEAN across ${N} frames — no PII detected."
echo "  (still do a final human eyeball before any Apple upload — this is a pre-screen.)"
exit 0