Skip to content

Commit 9403ba2

Browse files
committed
Update version to 0.3.0, enhance PII handling instructions, and add PII scan script
1 parent ebcdcf8 commit 9403ba2

5 files changed

Lines changed: 191 additions & 2 deletions

File tree

.github/copilot-instructions.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,63 @@ Kotlin `TakPacketV2Data` uses camelCase field names. Wire-generated proto types
102102
- Don't change the `Team.Unspecifed_Color` typo — it's the canonical Wire-generated name
103103
- Don't retrain dictionaries without coordinating a version bump — new dicts break wire compatibility with old receivers
104104
- Don't use `toInt()` for `(longitude * 1e7)` comparisons — IEEE 754 rounding requires `roundToInt()`
105+
106+
## PII and test-fixture sanitization (CRITICAL)
107+
108+
**Never commit unredacted real-world ATAK captures.** CoT XML carries position
109+
data (lat/lon to sub-meter precision), Android device IDs that act as
110+
permanent device fingerprints, private LAN IPs that reveal home network
111+
topology, and personal callsigns. Anything captured from an operator's actual
112+
deployment must be sanitized BEFORE landing in `testdata/`, the SDK README,
113+
or the proto submodule — once it's in a git commit pushed to a public mirror,
114+
no amount of force-pushing scrubs it from caches, forks, or pre-existing
115+
clones reliably.
116+
117+
### Required replacements when sanitizing a real capture
118+
119+
| What | Pattern | Replace with |
120+
|---|---|---|
121+
| GPS coordinates | any real lat/lon | Public landmark coords (e.g. `38.8895, -77.0353` Washington Monument; `38.8814, -77.0502` Lincoln Memorial). For routes/polygons, pick distinct landmark pairs so the path topology survives. |
122+
| Stationary / not-a-place markers || `lat="0.0" lon="0.0"` (matches ATAK's "null island" convention for non-positional events like `m-t-t` and `y-`) |
123+
| Android device IDs (`ANDROID-[16 hex chars]`) | real device fingerprint | Sequential placeholders: `ANDROID-0000000000000001`, `…02`, etc. (Test fakes are already in this format — extend the numbering.) |
124+
| Private LAN IPs (`192.168.x.x`, `10.x.x.x`, `172.16-31.x.x`) | real network IP | RFC 5737 documentation range: `192.0.2.1`, `198.51.100.1`, `203.0.113.1` (these ranges are reserved for examples and never route on the public internet). |
125+
| MAC addresses | real radio MAC | Random or `00:00:00:00:00:0X` placeholder |
126+
| Real callsigns | personally-identifying handles | Generic operator handles: `ALPHA-1`, `BRAVO-2`, `ASPEN`, `ETHEL`, `CHARLIE` are fine — they're well-worn placeholders. Avoid the actual operator's first name, military rank+last-name, or amateur radio call. |
127+
| UUIDs | (no action) | Random UUIDs are high-entropy and not by themselves identifying. Keep them. |
128+
| Voice profile IDs, room IDs, chatgrp UIDs | (no action unless they look like a real account ID) | Usually safe — they're UUIDs. |
129+
130+
### Workflow when adding a new fixture from a real capture
131+
132+
1. **Diff-redact, don't just rename.** Pull the source XML into `/tmp/` and edit there. Apply every replacement above. Save back into `testdata/cot_xml/`.
133+
2. **Scan before staging.** Run this against the new file:
134+
```bash
135+
grep -nE '\b\d{1,3}\.\d{5,}\b|ANDROID-[0-9a-f]{12,}|\b(192\.168|10\.|172\.(1[6-9]|2[0-9]|3[01]))\.[0-9]+\.[0-9]+\b' testdata/cot_xml/<your-new-fixture>.xml
136+
```
137+
Hits on the coord regex with 5+ decimal places, on a non-sequential ANDROID hex ID, or on an RFC 1918 IP, mean it isn't sanitized yet. The sequential `ANDROID-0+\d+` pattern is fine — that's the test-fake convention.
138+
3. **Add a redaction note** in the commit message: "redacted: coords → DC landmarks; android ids → 000…0NN; LAN IP → 192.0.2.1".
139+
4. **Regenerate goldens twice.** `gradle jvmTest --tests CompressionTest.generate compression report --rerun-tasks` — the first pass mints `.pb`/`.bin`; rerun to confirm steady state.
140+
5. **Never assume binary blobs auto-sanitize.** Filter-repo's `--replace-text` skips files containing null bytes, so the `.pb` protobuf intermediates and `.bin` golden files retain whatever bytes were generated. After scrubbing the source XML, ALWAYS regenerate the derived binaries.
141+
142+
### Pre-push self-check
143+
144+
Before `git push`, especially when pushing fixture changes, run from repo root:
145+
146+
```bash
147+
# Quick PII sweep
148+
grep -rEon '\b34\.[0-9]{1,2}\.[0-9]{5,}|\b\d{1,3}\.\d{5,}\b|ANDROID-[0-9a-f]{12,}|\b(192\.168|10\.|172\.(1[6-9]|2[0-9]|3[01]))\.[0-9]+\.[0-9]+\b' \
149+
--include='*.xml' --include='*.md' --include='*.swift' --include='*.kt' \
150+
--include='*.cs' --include='*.py' --include='*.ts' \
151+
| grep -v 'ANDROID-0\{12\}'
152+
```
153+
154+
Empty output = clean. Any hit gets eyeballed before push.
155+
156+
### If real PII slips through
157+
158+
If you discover real PII in a commit that has already been pushed:
159+
1. **Stop pushing** anything that builds on the leak.
160+
2. Use `git filter-repo --replace-text` for text-format leaks (XML, README, code).
161+
3. Follow up with `git filter-repo --blob-callback` for binary leaks (`.pb`, `.bin`) — filter-repo's `--replace-text` SKIPS files with null bytes, so binaries need an explicit callback that swaps the bad blob SHA for a clean re-baselined version.
162+
4. Force-push every branch and tag (`git push --force origin master --tags`).
163+
5. Open a GitHub Support ticket to purge the orphaned commits from cache — they remain accessible by SHA URL for 30+ days otherwise.
164+
6. Reach out to fork owners — your force-push doesn't propagate.

CLAUDE.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,38 @@ cd kotlin && gradle jvmTest
158158

159159
- **Meshtastic-Android** (`core/takserver`): depends on `takpacket-sdk-jvm` via JitPack, proto submodule at `core/proto/src/main/proto`
160160
- **Meshtastic-Apple**: depends on `MeshtasticTAK` Swift package via remote SPM URL, proto submodule at `protobufs/`, regenerated `atak.pb.swift` at `MeshtasticProtobufs/Sources/meshtastic/`
161+
162+
## PII / sensitive-data handling — read before adding any fixture
163+
164+
The repo has had real-world ATAK captures land in test fixtures by accident
165+
before. The compressed `.bin` and `.pb` intermediates retain the leaked
166+
identifiers even after the source XML is fixed, because filter-repo's text
167+
replacement skips files containing null bytes. Treat this as a recurring
168+
hazard.
169+
170+
**Patterns that must never appear in committed fixtures or docs:**
171+
172+
| Pattern | What it leaks | Required substitute |
173+
|---|---|---|
174+
| High-precision lat/lon (5+ decimals) that's not a public landmark | Operator's actual location, often a home | DC-area public landmarks: `38.8895,-77.0353` (Washington Monument), `38.8814,-77.0502` (Lincoln Memorial), or `0.0,0.0` for non-positional events like `m-t-t` / `y-` |
175+
| `ANDROID-[16 hex chars]` where the hex isn't all-zeros | A specific Android device's ANDROID_ID — never changes, can fingerprint the device for years | Sequential placeholder: `ANDROID-0000000000000001`, `…02`, etc. |
176+
| RFC 1918 private IPs (`192.168.x.x`, `10.x.x.x`, `172.16-31.x.x`) | Home/office network topology | RFC 5737 docs range: `192.0.2.1`, `198.51.100.1`, `203.0.113.1` |
177+
| MAC addresses | Hardware fingerprint | `00:00:00:00:00:0X` placeholder |
178+
| Real operator callsigns (first names, military rank+name, ham calls) | Identity | Generic operator handles: `ALPHA-1`, `BRAVO-2`, `ASPEN`, `ETHEL` |
179+
180+
**When you're handed a real CoT Explorer capture / pg_dump excerpt to add as a fixture:**
181+
182+
1. Don't drop it into `testdata/` raw. Edit a redacted copy in `/tmp/` first.
183+
2. Apply every substitution above. Random UUIDs (message IDs, room IDs, chatgrp UIDs) can stay — they're high-entropy and not identifying on their own.
184+
3. Sanity-grep the redacted file before staging:
185+
```bash
186+
grep -nE '\b\d{1,3}\.\d{5,}\b|ANDROID-[0-9a-f]{12,}|\b(192\.168|10\.|172\.(1[6-9]|2[0-9]|3[01]))\.[0-9]+\.[0-9]+\b' /tmp/<file>.xml
187+
```
188+
Hits on coords with 5+ decimal places, non-sequential ANDROID hex, or RFC 1918 IPs mean it's still dirty. (Sequential `ANDROID-0+\d+` is allowed — that's the test-fake convention.)
189+
4. Regenerate goldens via `gradle jvmTest --tests "CompressionTest.generate compression report" --rerun-tasks` so the derived `.pb` / `.bin` artifacts pick up the clean strings. The Kotlin test writes both into `testdata/golden/` and `testdata/protobuf/`.
190+
191+
**If a leak ships to master:**
192+
193+
The recovery is `git filter-repo` in two passes — text substitution for the XML/code/docs, then a `--blob-callback` for the binary `.pb` and `.bin` files (filter-repo skips text replacement on files with null bytes, so binaries need an explicit callback that swaps the bad blob SHAs for clean re-baselined ones). Force-push every branch and tag. Open a GitHub Support ticket to purge orphaned commits — they remain reachable by SHA URL until cache expires (~30 days). Forks aren't touched by the force-push.
194+
195+
The detailed playbook is in `.github/copilot-instructions.md` under "PII and test-fixture sanitization."

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.2.6
1+
0.3.0

kotlin/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ org.gradle.parallel=true
55
# Publishing coordinates (picked up by com.vanniktech.maven.publish)
66
GROUP=org.meshtastic
77
POM_ARTIFACT_ID=takpacket-sdk
8-
VERSION_NAME=0.2.6
8+
VERSION_NAME=0.3.0

scripts/pii-scan.sh

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#!/usr/bin/env bash
2+
# pii-scan.sh — flag candidate PII in staged or working-tree files.
3+
#
4+
# Two modes:
5+
# ./scripts/pii-scan.sh # scan entire working tree
6+
# ./scripts/pii-scan.sh --staged # scan only files staged for commit
7+
#
8+
# Install as a pre-commit hook:
9+
# ln -s ../../scripts/pii-scan.sh .git/hooks/pre-commit
10+
#
11+
# Exit code: 0 = clean, 1 = PII candidate found (review before commit).
12+
#
13+
# What it flags:
14+
# - GPS coords with 5+ decimal places that aren't on the known-synthetic whitelist
15+
# - ANDROID-[16+ hex chars] device IDs (allows ANDROID-0+\d+ test placeholders)
16+
# - RFC 1918 private network IPs (allows 192.0.2.x / 198.51.100.x / 203.0.113.x docs ranges)
17+
#
18+
# What it does NOT flag (intentional false negatives):
19+
# - High-entropy UUIDs (high-entropy, not identifying alone)
20+
# - Real names embedded in callsign strings (callsigns vary; impossible to regex reliably)
21+
# - Coordinates with <5 decimals (too imprecise to be identifying)
22+
23+
set -euo pipefail
24+
25+
STAGED_ONLY=0
26+
if [[ "${1:-}" == "--staged" ]]; then
27+
STAGED_ONLY=1
28+
fi
29+
30+
# Pattern: matches coords with 5+ decimal places, ANDROID device IDs,
31+
# and RFC 1918 IPs in one pass.
32+
PII_PATTERN='\b[0-9]{1,3}\.[0-9]{5,}\b|ANDROID-[0-9a-f]{12,}|\b(192\.168|10\.|172\.(1[6-9]|2[0-9]|3[01]))\.[0-9]+\.[0-9]+\b'
33+
34+
# Whitelist (allowlist): things that match PII_PATTERN but are KNOWN safe.
35+
# ANDROID-0+\d+ — sequential test placeholders (ANDROID-0000000000000001, ...02, ...)
36+
# ANDROID-589520ccfcd20f01 — specific ATAK-CIV upstream sample device ID (public)
37+
# 192\.0\.2\.|198\.51\.100\.|203\.0\.113\. — RFC 5737 docs IPs
38+
# \b38\.[0-9]+ — DC-area lat (Washington Monument neighborhood, public landmarks); covers high-precision ATAK-CIV upstream samples
39+
# \b77\.0[0-9]+ — DC-area lon (matches both -77.0x and 77.0x); public landmarks
40+
# \b33\.12[0-9]+ — established synthetic NM coords used in Meshtastic-Android fixtures
41+
# \b107\.25[0-9]+ — same NM range
42+
# \b12\.0{4,5}\b — established synthetic Bay-of-Bengal placeholders (low-precision)
43+
# \b9[01]\.0{4,5}\b — same Bay-of-Bengal range
44+
#
45+
# Numbers > 200 with 5+ decimals are very unlikely to be lat/lon (lat is
46+
# -90..90, lon is -180..180); they're almost always ellipse radii, bearings,
47+
# or other non-coord measurements. Filter those out too.
48+
ALLOW_PATTERN='ANDROID-0+[0-9]+|ANDROID-589520ccfcd20f01|\b192\.0\.2\.|\b198\.51\.100\.|\b203\.0\.113\.|\b38\.[0-9]+|\b77\.0[0-9]+|\b33\.1[0-9]+|\b107\.2[0-9]+|\b12\.0{4,5}\b|\b9[01]\.0{4,5}\b|\b16\.1[0-9]+|\b10\.00[0-9]+|\b95\.00[0-9]+|\b15\.181230\b|\b33\.30720360300985\b|\b45\.5965567[0-9]+\b|\b[2-9][0-9]{2,}\.[0-9]+\b|\b1[0-9]{3,}\.[0-9]+\b'
49+
50+
# Files to scan: source + docs + fixtures + manifests.
51+
INCLUDE_GLOBS=(
52+
"--include=*.xml" "--include=*.cot" "--include=*.md"
53+
"--include=*.kt" "--include=*.kts" "--include=*.swift"
54+
"--include=*.cs" "--include=*.py" "--include=*.ts" "--include=*.js"
55+
"--include=*.json"
56+
)
57+
58+
# Always-excluded paths (build artifacts, vendor dirs, the corpus dir in ZTSD).
59+
EXCLUDE_DIRS=(
60+
"--exclude-dir=.git" "--exclude-dir=node_modules" "--exclude-dir=build"
61+
"--exclude-dir=.build" "--exclude-dir=bin" "--exclude-dir=obj"
62+
"--exclude-dir=venv" "--exclude-dir=.venv" "--exclude-dir=DerivedData"
63+
"--exclude-dir=corpus" "--exclude-dir=Pods" "--exclude-dir=site-packages"
64+
)
65+
66+
if [[ "$STAGED_ONLY" -eq 1 ]]; then
67+
# Pre-commit mode: only scan files actually staged for commit.
68+
FILES=$(git diff --cached --name-only --diff-filter=AM \
69+
| grep -E '\.(xml|cot|md|kt|kts|swift|cs|py|ts|js|json)$' || true)
70+
if [[ -z "$FILES" ]]; then
71+
echo "pii-scan: no relevant staged files."
72+
exit 0
73+
fi
74+
HITS=$(echo "$FILES" | xargs -I{} grep -EHno "$PII_PATTERN" "{}" 2>/dev/null \
75+
| grep -Ev "$ALLOW_PATTERN" || true)
76+
else
77+
HITS=$(grep -rEHno "$PII_PATTERN" "${EXCLUDE_DIRS[@]}" "${INCLUDE_GLOBS[@]}" . 2>/dev/null \
78+
| grep -Ev "$ALLOW_PATTERN" || true)
79+
fi
80+
81+
if [[ -n "$HITS" ]]; then
82+
echo "❌ pii-scan found candidate PII (review before commit):" >&2
83+
echo "" >&2
84+
echo "$HITS" >&2
85+
echo "" >&2
86+
echo "Sanitization guide: .github/copilot-instructions.md → \"PII and test-fixture sanitization\"" >&2
87+
echo "" >&2
88+
echo "If a flagged match is actually safe (e.g. a new public landmark coord)," >&2
89+
echo "extend ALLOW_PATTERN in scripts/pii-scan.sh." >&2
90+
exit 1
91+
fi
92+
93+
echo "✅ pii-scan clean."
94+
exit 0

0 commit comments

Comments
 (0)