Skip to content
Back to skills

Privacy And Store Declarations

ASecurity

Proves an app's privacy - network via the API port or offline-only, SDK inventory, privacy manifest, App Privacy and Data safety answers. Use when an SDK lands, a release nears or an audit fails. Not for installs (dependency-management).

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 18, 2026
developmentjavascriptrustgojavanodetestingdebugginggitapisecurity

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 19 files and shows the line behind each finding

Scanned October 18, 2026

npx -y skills add zakariaf/ReactNative-Skills --skill privacy-and-store-declarations --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Privacy And Store Declarations?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Privacy And Store Declarations
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/zakariaf-privacy-and-store-declarations/badge)](https://www.skillsdirectory.com/skills/zakariaf-privacy-and-store-declarations)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: privacy-and-store-declarations
description: Proves an app's privacy - network via the API port or offline-only, SDK inventory, privacy manifest, App Privacy and Data safety answers. Use when an SDK lands, a release nears or an audit fails. Not for installs (dependency-management).
---

# Privacy and store declarations

Makes the app's privacy story true and provable: its own code reaches the network only through the
declared seam (or not at all), every SDK that goes online is declared with what it collects, the iOS
privacy manifest covers every pod, the App Privacy and Data safety answers come from one checked
contract, the release binaries match it, and no key or keystore sits in the repo. Four zero-dependency
audits prove it on the source, the shipped JavaScript, the prebuilt pods and the built app.

## Rules that must hold

1. **First-party network code lives only in the declared seam.** Profile `api-port` (default):
   `fetch`, `XMLHttpRequest`, `WebSocket`, `EventSource`, `sendBeacon`, expo-file-system transfers
   and request-client imports (`expo/fetch`, axios, …) only in `src/api/**` and the `networkModules`
   of `contracts/privacy.json`; remote URL literals only in `src/config` and `linkFiles` (never
   `src/api`: endpoints are paths on the configured base URL). Profile `offline-only`: no
   first-party request at all, URLs only in `linkFiles`. Aliased and bracket forms count. *Why:* one
   seam owns deadlines, validation and the privacy inventory, and remote images and fonts request
   without `fetch`. (`network-call`, `network-global`, `remote-url`; in the bundle
   `first-party-network`)
2. **Every network-capable SDK is declared.** Each installed SDK that goes online by itself (ads,
   purchases, crash reporting, analytics, attribution, push, OTA, web views, sign-in, NetInfo, HTTP
   clients) has an entry in `contracts/privacy.json` `sdks` with why, its trunk pods, its Gradle
   groups, whether it tracks and what it collects; no entry outlives its package. *Why:* the store
   answers cover SDK collection too, and an undeclared SDK is an undeclared data flow.
   (`network-sdk`, `sdk-inventory`, `contract`)
3. **Native SDKs are only the declared ones.** Every CocoaPods trunk pod belongs to a declared SDK;
   no pod comes from `:git`, `:http` or outside the repo; tracking, analytics or crash Gradle groups in
   the release classpath belong to a declared SDK. *Why:* binaries bypass every source layer.
   (`vendor-pod`, `banned-pod`, `pod-source`, `vendor-maven`)
4. **Every shipped third-party network capability is declared or reasoned.** A package the app uses
   to go online is a declared SDK (rule 2), which covers its findings; any other capability has a
   baseline entry saying why the app cannot reach it. First-party code is never baselined. The
   baseline and the contract are gated paths in `quality-gates.json`, so a change carries a
   `Gate-Change:` trailer and the reviewer's approval; STALE entries are notes. *Why:* Metro does not
   tree-shake, so unused SDK clients ship. (`baseline-new`, `baseline-reason`, `baseline`,
   `gated-paths`)
5. **The iOS privacy manifest declares every reason any pod declares, and no tracking.**
   `src/config/privacy-manifest.ts` is wired through `ios.privacyManifests`, keeps
   `NSPrivacyTracking: false` and no `NSPrivacyTrackingDomains`; only pods of SDKs declared
   `"tracking": true` may declare tracking; the prebuilt and shipped manifests are current. *Why:*
   Apple does not reliably read static pods' manifests (ITMS-91053), and listed domains are blocked for
   everyone who declines ATT. (`privacy-manifest`, `required-reason`, `missing-reason`, `app-tracking`,
   `tracking-outside-declared`, `prebuilt-stale`, `unreadable-manifest`, `built-manifest`,
   `privacy-manifest-file`)
6. **The store answers come from the contract and match the SDKs.** Every data type a pod's manifest
   collects is in its SDK's `collects` with the same linked and tracking answers and every purpose
   the pod names; the app's own code never tracks; offline-only apps collect nothing themselves; a
   purchase server adds purchase history; push adds the push token (device-id); an app with accounts
   (or a sign-in SDK or account data in the contract) declares its account data and both deletion
   paths. *Why:* both forms are legal declarations, and Apple compares them with the SDKs'
   manifests. (`undeclared-collection`, `store-answer`)
7. **Every permission has a store answer.** Each kind in `contracts/permissions.json` and each purpose
   string or runtime permission in the built binaries (library-merged ones included) is answered in
   `permissionAnswers`, or removed with `android.blockedPermissions`; a merged `AD_ID` needs a
   tracking SDK or a device-id row for advertising or analytics, and Play Console's Advertising ID
   answer. *Why:* libraries and the prebuild template merge permissions nobody asked for.
   (`permission-answer`, `android-ad-id`)
8. **The release binaries carry no surprise.** No `NSAllowsArbitraryLoads` or cleartext exception, no
   dev launcher local-network keys in production, the tracking text in every language exactly when
   tracking is declared, no placeholder values in production. *Why:* the binary is what users get.
   (`ats`, `android-cleartext`, `local-network`, `att-string`, `ship-placeholder`)
9. **The offline-only profile is enforced at install and config time too.** The dependency policy
   lists `offline-only`, `updates.enabled` is false, no OTA plugin, no ATS or cleartext switch.
   *Why:* a banned package is easier to stop than to find. (`profile-policy`, `config-updates`,
   `config-ats`)
10. **Test builds prove it at runtime.** `contracts/e2e.json` `network.mode` is the profile's policy
    (`offline-only` for offline-only; `allowlist` with the API host, or `offline-only`, for api-port),
    so e2e-maestro's socket sampler on each named simulator and emulator fails any other peer; the JS
    network guard (test-only code, never imported elsewhere) blocks and counts every request outside
    the same hosts. *Why:* the runtime layer catches what static layers miss.
    (`runtime-proof`, `network-guard-import`)
11. **No key, keystore or credential anywhere in the repo.** `.gitignore` covers them, no such file
    sits in the tree (installed skills included), no PEM armour in any file, agent deny rules cover
    them. Never open, print, copy or log a key or a token made from it. *Why:* a leaked key publishes
    apps. (`gitignore-secrets`, `secret-file`, `private-key`, `deny-rules`)
12. **Fix findings, never the audit.** Never loosen a baseline, the contract, a lint rule or a checker
    to get green; stop and ask the reviewer for any baseline or profile change, and the developer
    before entering store answers.

## Workflow

1. Read [references/network-layers.md](references/network-layers.md) for the profiles and what each
   layer catches. Decide the profile with the developer (it is a product decision).
2. Copy the templates (each names its repo path on line 1, JSON files sit at their path): `contracts/`
   (`privacy.json`, `privacy.schema.json`, `network-baseline.json`), `src/config/privacy-manifest.ts`
   (+ test), `src/services/test-hooks/network-guard.ts` (+ test) and `scripts/audit/*.ts` (+ tests).
   Set `ios: { privacyManifests: PRIVACY_MANIFESTS }` in `app.config.ts` and add
   `"audit:network": "node scripts/audit/audit-network.ts"` and
   `"audit:privacy": "node scripts/audit/audit-privacy.ts"` to `package.json`. Run their Jest tests.
   Add `contracts/network-baseline.json` and `contracts/privacy.json` to `quality-gates.json`
   `gatedPaths` (quality-gates owns the file), so the commit-msg gate asks for the trailer.
3. Fill `contracts/privacy.json`: the profile, every SDK
   ([references/sdk-inventory.md](references/sdk-inventory.md)), the app's own collection, the
   permission answers and account deletion
   ([references/store-declarations.md](references/store-declarations.md); shape:
   `examples/contracts/privacy.json`). Offline-only: also add `offline-only` to
   `contracts/dependency-policy.json` `profiles` (dependency-management).
4. Run `node ${CLAUDE_SKILL_DIR}/scripts/audit-repo.mjs .` before every push and after any dependency,
   config or native change; fix each `FAIL` (file, rule, fix) and rerun until `RESULT: PASS`. With an
   Android build at hand add `--gradle-deps <report>`.
5. After a native install or an SDK upgrade: `npx expo prebuild --platform ios --clean`, then
   `node ${CLAUDE_SKILL_DIR}/scripts/audit-privacy-manifest.mjs .`; add missing reasons to
   `privacy-manifest.ts` and collected data to the contract, prebuild again
   ([references/privacy-manifest.md](references/privacy-manifest.md)).
6. `npm run export:audit`, then `node ${CLAUDE_SKILL_DIR}/scripts/audit-bundle.mjs .` (or
   `npm run audit:network` for every layer). A `NEW` finding: a package the app uses to go online
   is an SDK to declare (that covers it); an unreachable capability gets a baseline entry with a
   reason, a `Gate-Change:` trailer and the reviewer's approval.
7. Runtime ([references/network-layers.md](references/network-layers.md), layer F): set
   `contracts/e2e.json` `network` from the profile (e2e-maestro runs the sampler on named devices
   with it). Wire the guard by hand (debug-menu-and-test-hooks' entry does not carry it): add
   `export { installNetworkGuard } from '@/services/test-hooks/network-guard';` to
   `src/test-only-entry.ts`, and call
   `TEST_ONLY?.installNetworkGuard(globalThis, { onAttempt, allowedHosts })` in
   `src/services/test-hooks/install-network-guard.ts`, imported by `src/app/_layout.tsx` right after
   the error net (the module and its hosts: layer F in the reference). Expect zero blocked attempts
   and an empty sampler report on both platforms.
8. Release: run `audit-app-bundle.mjs` on the built `.app` and the merged Android manifest
   ([references/release-audit.md](references/release-audit.md)).
9. Secrets: follow [references/secrets.md](references/secrets.md); on any leak, stop and walk the
   developer through the leak response.
10. Print the answers with `audit-repo.mjs . --answers` and report in plain words
    (`examples/audit-run.md`): what passed, any baseline waiting for approval, and the App Privacy and
    Data safety answers the developer enters by hand.

## Definition of done

- [ ] `contracts/privacy.json` names the profile, every network-capable SDK with its pods, Gradle groups,
      tracking and collected data, the app's own collection, every permission answer and account deletion.
- [ ] The templates are in place, wired (`ios.privacyManifests`, `audit:network`, `audit:privacy`), and
      their Jest tests pass.
- [ ] After the latest prebuild `audit-privacy-manifest.mjs` passes; after the latest export
      `audit-bundle.mjs` passes for iOS and Android; `npm run audit:network` passes.
- [ ] E2E shows zero blocked attempts and an empty socket report from live samplers on both platforms.
- [ ] Release: `audit-app-bundle.mjs --app … --android-manifest …` passes on the production build.
- [ ] The developer has the printed App Privacy and Data safety answers; every baseline or profile change
      has a `Gate-Change:` trailer and the reviewer's approval.
- [ ] `node ${CLAUDE_SKILL_DIR}/scripts/audit-repo.mjs .` prints `RESULT: PASS` (expected SKIP lines:
      `ios/Podfile.lock [vendor-pod]` before a prebuild; `contracts/dependency-policy.json [profile-policy]`
      for an offline-only app before dependency-management creates the policy; `quality-gates.json
      [gated-paths]` before quality-gates; `contracts/e2e.json [runtime-proof]` before e2e-maestro)

## Anti-patterns

- **Baselining first-party code or adding a pod "to unblock the release".** Remove the code or the SDK;
  allow-list changes are the reviewer's call.
- **Grepping the raw bundle for an ad ID or a URL.** SDKs ship their test IDs and docs links in every
  bundle; attribute by module through the source maps (`audit-bundle.mjs`).
- **Answering "Data Not Collected" because the app's own code collects nothing.** The SDKs' collection
  is the app's collection in both stores; print the answers from the contract.
- **Listing an ads SDK's domains in `NSPrivacyTrackingDomains` "to be safe".** iOS then blocks them for
  everyone who declines ATT, and their ads stop.
- **Editing `ios/` (PrivacyInfo, Info.plist) or `android/` by hand.** Prebuild rewrites them; change
  `privacy-manifest.ts`, `app.config.ts` or a config plugin.
- **Trusting `expo config --type introspect` for ATS.** Without `ios/` it shows a placeholder
  Info.plist with `NSAllowsArbitraryLoads: true` that prebuild never writes; check the built app.
- **Installing NetInfo for online detection.** Its default reachability check requests a Google URL;
  use `expo-network` behind the `ConnectivityPort` (networking-and-server-state).
- **Running the runtime layer on a development build or with ads on.** Metro and ads open sockets
  legitimately; use a release build of a testing variant with ads off, on named devices.
- **Printing a JWT or a request with its headers while debugging an upload.** Log the error body only.

## Files in this skill

| File | What it is | Read/run when |
|---|---|---|
| [references/network-layers.md](references/network-layers.md) | The profiles; layers A to F, what each catches and misses (and the limits of the source patterns); the runner; the runtime policy, the guard and what the samplers measure (iOS and Android); baselines and gated paths | Workflow steps 1 and 7; on any NEW or STALE finding |
| [references/sdk-inventory.md](references/sdk-inventory.md) | What counts as a network SDK, declaring one, facts for common SDKs, before adding a package, multi-package | Workflow step 3; before installing any SDK |
| [references/store-declarations.md](references/store-declarations.md) | The contract key by key, collected/linked/tracking/shared, App Privacy and Data safety mapping, facts other skills hand over, permissions, deletion, the human step, a worked answer set | Workflow steps 3 and 10 |
| [references/privacy-manifest.md](references/privacy-manifest.md) | Why the manifest is aggregated, the wiring, the verified SDK 57 reasons, the after-prebuild audit, tracking, collected data | Workflow step 5 |
| [references/release-audit.md](references/release-audit.md) | The binary audit for iOS and Android, rule by rule, commands in order, checklist | Workflow step 8 |
| [references/secrets.md](references/secrets.md) | What counts as a secret, where secrets live, the checks, agent rules, leak response | Workflow step 9 |
| `examples/contracts/privacy.json` | A filled contract for a Tasks app with accounts, ads, Premium, crash reporting and pushes; its SDK rows pass `audit-privacy-manifest.mjs` on a real SDK 57 prebuild | Workflow step 3, as the shape |
| [examples/audit-run.md](examples/audit-run.md) | A complete audit run with findings handled and the report | Workflow step 10, as the model |
| `templates/contracts/privacy.json` | The empty contract (api-port, nothing declared) | Workflow step 2 |
| `templates/contracts/privacy.schema.json` | Its JSON schema (editors validate the contract with it) | Workflow step 2 |
| `templates/contracts/network-baseline.json` | The starting js and native baseline of a `create-expo-app` SDK 57 app and the canonical stack, with reasons | Workflow step 2 |
| `templates/src/config/privacy-manifest.ts` | `PRIVACY_MANIFESTS`: the seven reasons every Expo SDK 57 app ships, no tracking | Workflow steps 2 and 5 |
| `templates/src/config/privacy-manifest.test.ts` | Pins the reasons and the absence of tracking (add a reason here first) | Workflow step 5 |
| `templates/src/services/test-hooks/network-guard.ts` | The test-only JS network guard with allowed hosts and the dev loopback exception; keeps WebSocket's constants and prototype and reads a Request's url (installed from the test-only entry) | Workflow steps 2 and 7 |
| `templates/src/services/test-hooks/network-guard.test.ts` | Its test on a fake global scope | Workflow step 2 |
| `templates/scripts/audit/` | `audit-network.ts` and its layers (contract, bundle modules, JS, native, pods, config, baseline, `sdk-packages.ts`: the packages a declared SDK installs), `audit-privacy.ts` + `privacy-manifest.ts`, with tests | Workflow steps 2 and 6 |
| `assets/privacy-facts.json` | Network SDK families, pod and Gradle families, verified required reasons per package, permission kinds, secret patterns, JS patterns | Read by the scripts; change with the reviewer |
| `assets/data-types.json` | Data types and purposes mapped to App Privacy, the manifest constants and Data safety | Read by the scripts and references |
| `scripts/audit-repo.mjs` | Static audit: network seams, SDK inventory (workspaces included), store answers, permissions, offline profile, pods, Gradle, manifest, tooling, gated paths, runtime policy, secrets; `--answers` prints both forms and the Data safety security practices | Workflow steps 4 and 10 |
| `scripts/audit-bundle.mjs` | The release export's JavaScript, iOS and Android, attributed per module | Workflow step 6 |
| `scripts/audit-privacy-manifest.mjs` | Pod manifests (trunk and development) against the declared manifest and the contract | Workflow step 5 |
| `scripts/audit-app-bundle.mjs` | The built `.app` and the merged Android manifest against the contract | Workflow step 8 |
| `scripts/lib/privacy-scan.mjs` | Shared helpers: facts, the contract reader, installed packages, source files, seams | Read only to change a script |
| `scripts/lib/plist.mjs` | Shared plist parser, XML and binary (synced; never edit here) | Read only to change a script |
| `scripts/lib/usage-strings.mjs` | Shared purpose-string reader for Info.plist and `.lproj` files (synced; never edit here) | Read only to change a script |
| `scripts/lib/ship-placeholders.mjs` | Shared list of placeholder values a release refuses (synced; never edit here) | Read only to change a script |
| `scripts/check-lib.mjs` | Shared script helper (synced; never edit here) | Never by hand |
| `scripts/selftest.mjs` | Proves the four audits on assembled fixtures | After changing a script, template or fixture |
| `assets/shared.json` | Declares the synced files | When adding a shared file |
| `tests/` | Bases (`repo`, `bundle`, `pods`, `app`) and one overlay case per planted bug | When adding a rule |

## Related skills

- `networking-and-server-state` - the ApiClient port, cleartext and base URLs under api-port.
- `dependency-management` - installs, pins and the offline-only ban list in the dependency policy.
- `architecture-and-boundaries` - SDKs only in their adapters.
- `admob-ads-and-consent` - the ads SDK, consent and the ATT flow and text.
- `in-app-purchases` - expo-iap, its server opt-ins and StoreKit artefacts.
- `push-notifications`, `crash-reporting-and-observability`, `analytics-and-consent` - SDK facts to declare.
- `permissions-and-system-prompts` - the permissions contract this skill answers.
- `e2e-maestro` - the runner and socket sampler that run layer F with `contracts/e2e.json` `network`.
- `debug-menu-and-test-hooks` - the test-only entry the guard is installed from.
- `quality-gates` - `gatedPaths`, which makes the baseline and the contract need a `Gate-Change:` trailer.
- `ios-release-testflight`, `android-release-play`, `eas-build-and-submit` - the release gates and consoles.
- `git-commits-and-reporting` - the `Gate-Change:` trailer and the report.

Files in this skill

  • SKILL.md19.1 KB
  • assets/data-types.json10.1 KB
  • assets/privacy-facts.json13.7 KB
  • assets/shared.json323 B
  • examples/audit-run.md4.6 KB
  • examples/contracts/privacy.json7.1 KB
  • references/network-layers.md22.1 KB
  • references/privacy-manifest.md8.4 KB
  • references/release-audit.md7.3 KB
  • references/sdk-inventory.md12.3 KB
  • references/secrets.md4.5 KB
  • references/store-declarations.md16.4 KB
  • scripts/audit-app-bundle.mjs14.2 KB
  • scripts/audit-bundle.mjs12.3 KB
  • scripts/audit-privacy-manifest.mjs15.2 KB
  • scripts/audit-repo.mjs45.5 KB
  • scripts/lib/plist.mjs10.5 KB
  • scripts/lib/privacy-scan.mjs15.4 KB
  • scripts/lib/ship-placeholders.mjs8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.