#!/usr/bin/env python3 """Leak scan for a skills repository: find target identity that should not be shipped. Why this exists: a skill repository is published, and the material feeding it is real work notes -- device transcripts, packet captures, package listings. Target identity leaks into those notes one line at a time (a bundle id in a `pm path` line, a device serial echoed before a `dumpsys`, a token pasted while debugging a download), and it is invisible to every other gate: `check_repo.py` verifies structure, `check_refs.py` verifies anchors, neither looks at *content*. Human grep finds it only after someone thinks to look. Two principles, both borrowed from published anonymization practice, shape the design: 1. **A "do not anonymize" list.** Tool names, library names, function names, protocol field names, CVE ids, hardening-product names, public crackme/benchmark names and URLs, and placeholders like ``/`` are *reusable* and must never be reported. A scanner that flags them trains its readers to ignore it. 2. **Context retention.** A finding is reported with the surrounding characters of its line, not a bare line number, so the fixer does not have to reopen the file to know what to delete. Exit codes are fixed so this can gate a pipeline: 0 clean nothing found outside the exemptions 0 leaks_found_strong_only findings exist, but every one of them is `weak` 1 leaks_found at least one strong/certain finding, or any finding at all under --fail-on any 2 error usage or read error and the last line of stdout is always one of RESULT=clean | leaks_found_strong_only | leaks_found | error `--fail-on strong` (the default) decides which findings take the exit code off zero. It is the maintenance-habit setting, and it exists because a repository's own documentation legitimately contains RFC 5737 addresses, which the endpoint rules report on purpose; `--fail-on any` is the strict pass to run before publishing. Neither setting changes what is printed. Everything is a heuristic. A finding means "a human should look at this line", never "this is definitely a secret". Strength labels (`certain` / `strong` / `weak`) say how much of the matching was context rather than shape -- see references for the table. Usage ----- python scan_leaks.py # scan the repo it lives in python scan_leaks.py --root . # explicit root python scan_leaks.py --root . --format json # machine-readable python scan_leaks.py --root . --only pat,appkey # one or more categories python scan_leaks.py --root . --quiet # token line only python scan_leaks.py --root . --fail-on any # strict: weak hits fail too python scan_leaks.py --root . --list-rules Only text files are read. Directories named in --exclude, VCS metadata, caches and `tools/` (a git-ignored work area by convention) are skipped; a root supplied explicitly is still scanned even if it sits under an excluded name. Pure standard library, python3, no third-party imports. POSIX and Windows. """ from __future__ import annotations import argparse import json import os import re import sys # -------------------------------------------------------------------------------------- # Exemption data -- the "do not anonymize" list, expressed as code so it can be audited. # -------------------------------------------------------------------------------------- # Reverse-domain prefixes that are never a target's own identity: platforms, frameworks, # SDK vendors, JDK/ART packages, documentation examples and this task's own fixtures. # Matching is on the value itself and on its first two segments, lowercased. BENIGN_PACKAGE_PREFIXES = ( # platforms / runtimes / language packages "android", "androidx", "java", "javax", "jdk", "kotlin", "kotlinx", "dalvik", "org.w3c", "org.xml", "org.json", "org.apache", "org.jetbrains", "org.lsposed", "org.reactivestreams", "com.android", "com.google", "com.sun", "com.oracle", "com.squareup", "com.github", "io.reactivex", "io.github", "dalvik.system", # ad / analytics SDKs and their activities (these are inventory, not identity) "com.bytedance", "com.qq.e", "com.tencent", "com.kwad", "com.kuaishou", "com.baidu", "com.umeng", "com.sigmob", "com.bdx", "com.anythink", "com.mbridge", "com.chinatowercom", "cn.connor", "bytedance", # hardening products, by their own naming (a product name, not a target) "com.stub", "com.secneo", "com.qihoo", "com.nqshield", "com.tencent.StubShell", "com.wrapper", "com.eg.android.AlipayGphone", # documentation examples / fixtures used by this repository itself "com.example", "org.example", "net.example", "io.example", "probe.synthetic", # probe modules built by this repository's own verification passes "com.t1", "com.revprobe", "com.lsphook", ) # Whole-value exemptions for the `bundle` rule: placeholders and documented stand-ins. PLACEHOLDER_TOKENS = ( "", "", "", "", "", "", "", "", "