From open-source-prep
Audits local macOS .app bundles for embedded telemetry SDKs (Sentry, Firebase), upload endpoints, on-disk queues, and privacy fields via static analysis.
npx claudepluginhub iamzhihuix/happy-claude-skills --plugin open-source-prepThis skill uses the workspace's default tool permissions.
Static-only macOS app telemetry auditor. Produces a markdown report describing what an installed `.app` bundle reports, to whom, how often (inferred), and what it leaves on disk.
lib/card.tslib/shell.tsreferences/data_locations.mdreferences/known_endpoints.mdreferences/methodology_examples.mdreferences/safe_commands.mdreferences/sdk_fingerprints.mdscripts/classify_strings.tsscripts/inventory_data.tsscripts/match_fingerprints.tsscripts/render_report.tsscripts/run.tsscripts/snapshot_app.tstemplates/card_prompt.md.tmpltemplates/report.md.tmplGenerates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
Static-only macOS app telemetry auditor. Produces a markdown report describing what an installed .app bundle reports, to whom, how often (inferred), and what it leaves on disk.
Invoke when the user says any of: "审计 / 调查 / 看看 / 拆 / 逆向 / 上报 / 埋点 / 隐私 / 抓 SDK" combined with a .app path or app name. Also invoke when given paths under /Applications, ~/Applications, /Library/Input Methods, or /Library/PrivilegedHelperTools.
Do NOT invoke for: source-code repos, web sites, mobile (iOS/Android) packages — this skill is macOS-bundle specific.
curl/wget/nc/dig against discovered endpoints. No lldb attach, dtrace, fs_usage, tcpdump, mitmproxy, frida. No Keychain reads. No DRM bypass. No memory dump.references/safe_commands.md. If a step seems to need something outside the whitelist, stop and tell the user instead of improvising.device_id, uid, session_id, email, IDFV, IDFA, JWT, and any 16+ hex blob to <redacted:N> (keep length, drop content)./System/, /usr/libexec/, /private/var/db/com.apple.*. Those are OS components, not third-party telemetry targets.{baseDir} = directory of this SKILL.md.
All scripts are bun + TypeScript. Resolve runtime as: prefer bun in PATH, otherwise npx -y bun. If neither exists, abort with a one-line install hint.
# Smoke check
bun --version || npx --version || echo "Need bun (recommended) or npx"
Each phase has: Goal → Inputs → Commands → Output → Stop conditions. Do not skip ahead. Do not interleave.
Goal. Lock the target list to ≤5 valid .app paths.
Inputs. Whatever the user said — could be a path, a name, or "the input methods I have installed."
Commands.
.app/Applications (depth 2)
~/Applications (depth 2)
/Library/Input Methods (depth 1)
/Library/PrivilegedHelperTools (depth 1)
/System/, /usr/libexec/, /private/var/db/com.apple.*Output. A list target_apps[] with absolute paths.
Stop. If the list is empty, ask the user once. If >5, ask which to keep.
Goal. Per app, capture the immutable surface: bundle id, version, signing, entitlements, network policy, embedded frameworks.
Inputs. target_apps[] from Phase 0.
Commands. Run scripts/snapshot_app.ts:
bun {baseDir}/scripts/snapshot_app.ts <app-path> --out <workdir>/meta.json
The script collects:
plutil -p <app>/Contents/Info.plistcodesign -dv --entitlements - <app> (stderr)find <app>/Contents/Frameworks -maxdepth 3 -name '*.dylib' -o -name '*.framework'otool -L <main-binary>file <main-binary> for archdu -shOutput. <workdir>/meta.json with: bundle_id, version, sandboxed, arbitrary_loads, ats_exceptions[], entitlements_summary[], frameworks[] (each: name, path, size_bytes, archs).
Stop. If bundle_id cannot be read → abort, app is malformed.
Goal. Turn raw strings of every embedded binary into bucketed markdown that fits in context.
Inputs. meta.json::frameworks[].
Commands.
bun {baseDir}/scripts/classify_strings.ts <workdir>/meta.json --out <workdir>/strings/
For each binary, the script runs strings -a -n 6 and sorts each line into one of:
urls — anything matching https?://domains — bare hostnamespaths — /Library/..., ~/Library/..., container-relative pathssql — CREATE TABLE, INSERT INTO, SELECT ... FROMevents — looks like an event name (/^[a-z][a-z0-9_]{8,80}$/ with at least one underscore)keys — base64 / hex blobs ≥ 24 chars (kept count + first 12 chars only, never full)noise — discardedOutput. <workdir>/strings/<binary-name>.{urls,domains,paths,sql,events}.md (the keys bucket holds only counts + redacted previews).
Stop. If a binary is >200 MB → skip it and emit a warning line, do not OOM.
Goal. Identify which third-party SDKs are present and how confident.
Inputs. <workdir>/strings/, plus references/sdk_fingerprints.md.
Commands.
bun {baseDir}/scripts/match_fingerprints.ts <workdir>/strings/ \
--fingerprints {baseDir}/references/sdk_fingerprints.md \
--out <workdir>/matched.md
The script applies each fingerprint's tell-tale strings regex set to the bucketed strings. A fingerprint counts as confirmed when its min_hits threshold is met (defined per fingerprint).
Output. <workdir>/matched.md with one row per SDK: name, vendor, hits, evidence file lines, status (confirmed / partial / absent).
Stop. If zero fingerprints confirmed AND the app embeds no third-party .framework → write a one-line "no telemetry detected" report and skip Phase 4-5.
Goal. Build the table that answers "where does it talk to, with what protocol, for what purpose, how often?"
Inputs. <workdir>/strings/*.urls.md + <workdir>/strings/*.domains.md + <workdir>/matched.md + references/known_endpoints.md.
Commands. This phase is mostly Claude reading the files. The only mechanical step:
bun {baseDir}/scripts/match_fingerprints.ts <workdir>/strings/ \
--fingerprints {baseDir}/references/known_endpoints.md \
--out <workdir>/endpoints.md
Then Claude writes <workdir>/endpoint_table.md:
| Endpoint | SDK | Protocol | Inferred purpose | Frequency source |
|---|
Frequency source MUST cite either: a literal interval found in <workdir>/strings/, or a config file found in Phase 5, or "unknown — not stated in static evidence." Never guess.
Stop. If endpoints.md is empty but Phase 3 confirmed an SDK → flag in the report (likely runtime-resolved hosts).
Goal. Inventory the on-disk surface that the app writes to.
Inputs. meta.json::bundle_id, plus references/data_locations.md.
Commands.
bun {baseDir}/scripts/inventory_data.ts <bundle_id> --out <workdir>/local_data.md
The script finds:
~/Library/Application Support/<bundle>/~/Library/Containers/<bundle>/Data/~/Library/Group Containers/group.<bundle-prefix>.*/~/Library/Caches/<bundle>/~/Library/Preferences/<bundle>.plist~/Library/Logs/<bundle>/For each .sqlite* file: sqlite3 <file> '.schema' and .tables only — never SELECT. For each .mmkv / .json config: list path + size, do not open.
Output. <workdir>/local_data.md with: tree of relevant paths, sizes, and SQLite schemas.
Stop. If user is not the file owner → skip with a note (do not prompt sudo).
Goal. Assemble the user-facing report. Optionally render a 4:5 infographic card for sharing.
Inputs. All prior phase outputs.
Commands.
bun {baseDir}/scripts/render_report.ts <workdir> \
--template {baseDir}/templates/report.md.tmpl \
--out ~/Documents/app-telemetry-audit/<YYYY-MM-DD>_<bundle-id>/report.md
When --card is passed to scripts/run.ts, the orchestrator additionally:
lib/card.ts::renderCardPrompt(), which extracts top-6 SDKs (by size, with privacy-hot ones flagged red), top-6 endpoints (preferring endpoints.md confirmed matches with Chinese purpose labels and synthesized proto for quic/-ws. hosts), and top-5 local-data buckets (collapsed by parent dir + note, with DoubaoIme/doubaoime casing variants merged).<workdir>/card_prompt.md.baoyu-imagine at ~/.claude/skills/baoyu-imagine/scripts/main.ts (or via BAOYU_IMAGINE_SCRIPT env), invokes it with --ar 4:5 --quality 2k defaults, and writes <workdir>/card.png.Pass --no-image to write only the prompt and skip the image call. Pass any --image-* flag (--image-provider, --image-model, --image-imageSize, etc.) to override the defaults — e.g. --image-provider google --image-model gemini-3-pro-image-preview --image-imageSize 4K for native nano-banana-pro 4K output.
If baoyu-imagine is not installed, Phase 6 still writes card_prompt.md and prints an install hint, but skips the PNG. The skill remains fully functional without it.
Output. Final markdown report. Print its absolute path. If --card, also card_prompt.md and card.png.
~/Documents/app-telemetry-audit/<YYYY-MM-DD>_<bundle-id>/
├── meta.json
├── matched.md
├── endpoints.md
├── endpoint_table.md
├── local_data.md
├── strings/
│ └── <binary>.{urls,domains,paths,sql,events}.md
├── card_prompt.md # only if --card requested
└── report.md # the deliverable
Working files (strings/, intermediate *.md) are kept by default — they are the audit trail. Pass --clean to delete them after report.md is written.
# Single app, full audit (markdown report only)
bun {baseDir}/scripts/run.ts /Library/Input\ Methods/DoubaoIme.app
# Add a 4:5 share card (prompt + PNG via baoyu-imagine)
bun {baseDir}/scripts/run.ts /Library/Input\ Methods/DoubaoIme.app --card
# Card prompt only — skip image generation
bun {baseDir}/scripts/run.ts /Library/Input\ Methods/DoubaoIme.app --card --no-image
# Card with Google nano-banana-pro at 4K
bun {baseDir}/scripts/run.ts /Library/Input\ Methods/DoubaoIme.app --card \
--image-provider google \
--image-model gemini-3-pro-image-preview \
--image-imageSize 4K
# Multiple apps in one go (capped at 5)
bun {baseDir}/scripts/run.ts /Library/Input\ Methods/DoubaoIme.app /Applications/Foo.app
scripts/run.ts is a thin orchestrator that calls Phases 1→6 in sequence. Use it for the common case. Use individual phase scripts only when iterating.
--card flag surface| Flag | Purpose | Default |
|---|---|---|
--card | Render card_prompt.md AND card.png | off |
--no-image | With --card: write prompt, skip PNG | off |
--image-provider | baoyu-imagine provider (google, openai, replicate, dashscope, …) | provider auto-selected |
--image-model | Model id within the provider | provider default |
--image-ar | Aspect ratio | 4:5 |
--image-size | Explicit WxH | from --image-quality / provider |
--image-quality | normal or 2k | 2k |
--image-imageSize | Google/OpenRouter 1K/2K/4K | from --image-quality |
--out | Override output root | ~/Documents/app-telemetry-audit/ |
Any --image-* flag implicitly enables --card.
references/safe_commands.md — command whitelist + rationalereferences/sdk_fingerprints.md — SDK detection rules (12+ SDKs MVP)references/known_endpoints.md — domain → product reverse lookupreferences/data_locations.md — typical on-disk layout per vendorreferences/methodology_examples.md — two worked examples (WeType, DoubaoIme)templates/report.md.tmpl — final report skeletontemplates/card_prompt.md.tmpl — 4:5 visual card prompt skeleton.sqlite content — only schemas<workdir>/strings/, it does not go in the tableSmoke test on /Library/Input Methods/DoubaoIme.app and confirm the report covers:
applogrs, Parfait, bytenn, onnxruntime, sscronet, TTNet, ime_net_sdk, samiime.doubao.com/obric/ime/cloud/convert, log-klink.zijieapi.com, ime-gw.oceancloudapi.com, frontier-audio~/Library/Application Support/DoubaoIme/Parfait/ready/685343/0/Regression: run on the WeType IME bundle and confirm wetype.weixin.qq.com + CACHE_LOG_TBL schema appear.
Negative: run on a small app with no third-party telemetry — must produce the short "no telemetry detected" output without inventing SDKs.