# Privacy Map

> See which outside companies an app sends its users' data to, what each receives (email, phone, location, IDs, tokens) and where, with privacy label hints.

Source: https://docs.subcloak.com/cloak-proxy/insights/privacy-map/

Added in Cloak Proxy 0.2.1.

Privacy Map shows where one app's users' data goes. For every outside company the app talks to, such as analytics, attribution, advertising and crash-reporting services, it lists the personal data and identifiers that company received: email addresses, phone numbers, names, locations, advertising and device IDs, user IDs, the app's own auth tokens, and IDs the app's own servers issued, such as order numbers. For each one it shows exactly where in the request it was sent.

Use it to review what your app's SDKs collect before a release, to fill in the App Store privacy label or the Google Play Data safety form, or to catch an SDK that receives something it shouldn't, such as a login token.

## Map an app

1. Capture the app while you use it, with [SSL Proxying](https://docs.subcloak.com/cloak-proxy/capture/ssl-proxying/) on for it, so its HTTPS requests are decrypted. Sign up, log in and go through the screens you want to check.

2. Open Privacy Map:

   - Choose **Tools › Privacy Map…** (<kbd>⌥</kbd> <kbd>⌘</kbd> <kbd>P</kbd>). If an app is selected in the [sidebar](https://docs.subcloak.com/cloak-proxy/inspect/sidebar/), the map opens for it.
   - Or right-click the app in the sidebar and choose **Privacy Map for _app_…**.
   - On Linux, press <kbd>Ctrl</kbd> <kbd>Alt</kbd> <kbd>P</kbd>, or right-click a request from the app and choose **Privacy Map for _app_…**.

3. If needed, choose the app from the **App** menu. The map is built right away.

4. Check **Own domains**. Remove domains that aren't the app's, and add any of its servers that are missing.

5. Optionally, under **Test account**, type the **Email**, **Phone** and **Name** you tested with and press <kbd>Return</kbd>, so Cloak Proxy finds them wherever they were sent.

6. Read the summary, then the details for each company. Click a company or a request count to select those requests in the traffic list.

*[Screenshot: Privacy Map for ShopApp, with shopapp.example treated as its own: ShopApp sends personal data or identifiers to 5 of the 6 outside companies it talks to, a red banner saying sdk-collector.example received the app's auth token, summary chips per data type, a map from ShopApp to five companies with colored lines, and below, what each company received and where, with partly hidden values. One app's requests to outside companies, colored by the most sensitive data each received.]*

## The window

| Control | What it does |
|---|---|
| **App** | The app to map. Lists every app in the captured traffic except Cloak Proxy itself. |
| **Rescan** | Builds the map again from the traffic captured so far. |
| **Copy Report** | Copies the map as Markdown. Reads **Copied** until the map changes. Only when the app talked to at least one outside company. |
| **Own domains** | The domains that belong to the app. Everything else is an outside company. Click **×** on a domain to treat it as a third party. |
| **Add a domain** | Type one of the app's domains and press <kbd>Return</kbd>. It's reduced to its registrable domain, so `api.shopapp.example` adds `shopapp.example`. |
| **Test account** (optional): **Email**, **Phone**, **Name** | Values to look for in every request to outside companies. Press <kbd>Return</kbd> in any of them to save them and rescan. |

The map is rebuilt whenever you choose another app, change the app's domains or press <kbd>Return</kbd> in a test account field. It doesn't update by itself as new traffic arrives; click **Rescan**.

Privacy Map reads decrypted requests only. If the app's HTTPS isn't decrypted yet, the window says how many encrypted connections it made and offers **Decrypt _app_'s HTTPS**, which turns on [SSL Proxying](https://docs.subcloak.com/cloak-proxy/capture/ssl-proxying/) for every domain the app connects to. Use the app again, then click **Rescan**.

*[Screenshot: Privacy Map for ShopApp with a lock and the message ShopApp's requests are encrypted: ShopApp made 1 HTTPS connection, but Privacy Map can only read decrypted requests. Below it, the button Decrypt ShopApp's HTTPS. Privacy Map for an app whose HTTPS isn't decrypted yet.]*

When the app has no requests, the window says **No requests from _app_**: use the app with Cloak Proxy capturing, then rescan.

### The app's own domains

Privacy Map needs to know which servers are the app's, so it can tell what the app's backend sends from what outside companies receive. It works with registrable domains, such as `shopapp.example` for `api.shopapp.example` and `cdn.shopapp.example`.

The first time you map an app, Cloak Proxy suggests its domains:

- Domains whose name shares a word of 4 or more letters with the app's name or bundle identifier (ignoring words such as `com`, `app`, `ios`, `mobile`, `client` and `prod`), or contains the app's name. ShopApp's requests to `api.shopapp.example` make `shopapp.example` its own.
- Domains you [captured for the app](https://docs.subcloak.com/cloak-proxy/capture/apps-that-ignore-the-proxy/) because it ignores the proxy.

Domains of known SDK companies are never suggested. Once you change the list, by removing a domain, adding one or clicking **Mark as _app_'s Own**, Cloak Proxy remembers it for that app.

> **Check the app's domains first:**
> If one of the app's own servers isn't in the list, it shows up as an outside company that receives everything. If an outside company is in the list, its requests aren't checked at all. Look at the list before you trust the map.

### Your test account

Personal data in SDK payloads doesn't always sit under a recognizable key. Type the email, phone number and name of the account you used while capturing, and Privacy Map finds them in any value sent to an outside company:

| Field | Matches |
|---|---|
| **Email** | The address anywhere in a value, ignoring case. |
| **Phone** | The last 10 digits of the number anywhere in a value. Needs at least 7 digits. |
| **Name** | The name anywhere in a value, ignoring case. Needs at least 3 characters. |

Cloak Proxy remembers these values per app, in its preferences on this Mac. Use a test account, not your own details.

## Read the map

### The summary

| Part | Shows |
|---|---|
| Headline | **_App_ sends personal data or identifiers to N of the M outside companies it talks to**, or that none was found. |
| Request count | **N of M requests from _app_ went to outside companies.** |
| **Fix:** banner | In red, when an outside company received the app's auth token or a card number: **Nothing outside your own servers should get these.** |
| Chips | One per kind of data, in its severity color, followed by **→** and the companies that received it. |

### Colors

Each kind of data has a severity, shown by color on the chips, the map's lines and the details:

| Color | Meaning | Data |
|---|---|---|
| Red | Fix it: no outside company should get this | Your app's auth token, card number |
| Orange | Personal data: declare it, and check the company needs it | Email address, phone number, name, precise location |
| Blue | Identifiers that link activity to a user or device | Approximate location, advertising ID, device ID, user ID, IDs from your backend |

### The map

On the left, the app with its own servers. On the right, under **Outside companies that received data**, a card for each company that received something, most sensitive first, then by how many findings and how many requests. Each card shows:

- The company's name and category, such as **Analytics** or **Attribution**, when Cloak Proxy knows it; otherwise the domain.
- How many requests went to it, and up to three of its hosts.
- A chip for each kind of data it received.

The line from the app to each card takes the color of the most sensitive data that company received, and is thicker for red. Under the cards, **Also talked to, with no personal data found:** lists the other outside companies. Click a card to select every request that sent it data.

### What each one received, and where

Below the map, a section per company lists every finding:

| Column | Shows |
|---|---|
| Data | The kind, in its severity color. |
| Where | The exact place in the request: `query phone`, `header X-User`, `cookie uid`, `body $.events[*].user_properties.email`, `body email`. Array positions are shown as `[*]`. |
| **From** | For values the app's own servers issued or the app authenticates with: where they came from, such as **From POST api.shopapp.example/v1/orders response $.data.user.id**, or **From the Authorization header of POST api.shopapp.example/v1/orders**. |
| Example | The value, partly hidden. |
| **N req** | How many requests sent it there. Click to select them in the traffic list. |

**Mark as _app_'s Own** next to a company moves its domain into the app's own domains and rescans. Use it for the app's servers on another domain, such as a separate API or CDN domain.

### For your privacy label

**For your privacy label** lists each kind of data sent to outside companies (except auth tokens, which are a problem to fix rather than something to declare), the App Store privacy label category it usually falls under, and the companies that received it:

| Data | App Store category |
|---|---|
| Card number | Financial Info › Payment Info |
| Email address | Contact Info › Email Address |
| Phone number | Contact Info › Phone Number |
| Name | Contact Info › Name |
| Precise location | Location › Precise Location |
| Approximate location | Location › Coarse Location |
| Advertising ID | Identifiers › Device ID |
| Device ID | Identifiers › Device ID |
| User ID | Identifiers › User ID |
| IDs from your backend | Depends on what it identifies (often Purchases or Other Data) |

> **A starting point, not an answer:**
> The categories are hints. Check Apple's and Google Play's definitions, including whether each use counts as tracking and whether data is linked to the user. Data sent to your own servers isn't on the map at all, and you may still need to declare it.

## What Privacy Map looks for

Privacy Map reads every request from the app (except encrypted tunnels) and splits them into requests to the app's own domains and requests to everyone else.

### Values the app's own servers issued

First, it learns from the app's own traffic:

- **The app's auth tokens in its requests.** The value of each `Authorization` header, any header with `token` in its name, and `X-API-Key`, without a `Bearer`, `Token` or `Basic` prefix, when it's at least 12 characters.
- **Values in its JSON responses that look like IDs**: at least 4 characters with no spaces, containing a digit or at least 20 characters long, and not a timestamp, a date or a version number. Each one is sorted by its key:
  - under keys such as `access_token`, `refresh_token`, `id_token`, `jwt`, `session_token` and `api_key`: **Your app's auth token**;
  - under keys such as `user_id`, `customer_id`, `account_id`, `member_id`, `distinct_id`, `external_id` and `profile_id`: **User ID**;
  - anything else: **IDs from your backend**. Order numbers, cart IDs and payment references land here.

Any of these values sent to an outside company is a finding, with where it came from. A value of more than 24 characters is also checked piece by piece, so a token inside a URL or a longer string is found too.

### Data in requests to outside companies

Then it checks every value in each request to an outside company: query parameters, the path, headers (except standard ones such as `Host`, `User-Agent`, `Accept`, `Content-Type`, `Origin` and `Referer`), each cookie, and the body. JSON bodies are read field by field, including JSON packed inside a string (common in analytics SDKs) and JSON inside a form field. Form bodies are read field by field, and other text bodies as a whole.

Most kinds are found by the key name together with the shape of the value. Key names ignore case and punctuation, so `customer_user_id`, `customerUserId` and `CUSTOMER-USER-ID` are the same key.

| Data | Found when |
|---|---|
| **Your app's auth token** | A value matches one of the app's own tokens. |
| **Card number** | Any value contains a 13 to 19 digit number that passes the Luhn check. |
| **Email address** | A key such as `email`, `email_address`, `mail` or `user_email` holds an email address; or any value contains one. Outside email keys, mailboxes such as `noreply@`, `support@` and `info@`, and addresses at `example.com`, are ignored. |
| **Phone number** | A key such as `phone`, `phone_number`, `mobile`, `msisdn` or `tel` holds 7 to 15 digits with only `+`, `-`, spaces and parentheses besides; or any value contains a `+` followed by 10 to 14 digits. |
| **Name** | A key such as `first_name`, `last_name`, `full_name`, `display_name`, `given_name` or `family_name` holds 2 to 80 characters with a letter and no `@`. |
| **Precise location** | A `lat` or `latitude` key has a `lng`, `lon`, `long` or `longitude` key next to it in the same JSON object, with valid coordinates and at least 3 decimal places. |
| **Approximate location** | The same, with fewer decimal places. |
| **Advertising ID** | A key such as `idfa`, `advertising_id`, `gaid`, `adid` or `aaid` holds a UUID that isn't all zeros. |
| **Device ID** | A key such as `idfv`, `vendor_id`, `device_id`, `android_id`, `install_id` or `installation_id` holds at least 8 characters. |
| **User ID** | A user-ID key, as above, holds a value other than `0`, `-1`, `anonymous` or `guest`; or a value matches a user ID the app's servers issued. |
| **IDs from your backend** | A value matches an ID the app's servers issued. When the same value is sent under a more specific key, such as `user_id`, it's listed as that kind instead, with where it came from. |

Your test account's email, phone and name are found anywhere, whatever the key.

### Known companies

Cloak Proxy recognizes the domains of more than 40 common SDK companies and shows their name and a category: **Advertising**, **Analytics**, **Attribution**, **Engagement**, **Push notifications**, **Crash reporting**, **Monitoring**, **Session recording**, **Support**, **Chat**, **Payments**, **Sign-in** or **Google services**. Any other outside domain is shown by its registrable domain, such as `sdk-collector.example`.

### Partly hidden values

Examples are partly hidden everywhere, in the window, the report and the MCP tool, so you can recognize a value without exposing it:

| Data | Shown as |
|---|---|
| Email address | `a•••@example.net` |
| Phone and card numbers | `+••••••••••23`: every digit but the last two hidden |
| Location | `12.9…, 77.5…`: one decimal place |
| Name | `A•••` |
| Anything else | `tok_…56`: the first 4 and last 2 characters, or the first 2 for values of 8 characters or fewer |

## Copy the report

**Copy Report** puts the map on the clipboard as Markdown, for a privacy review, a ticket or your release checklist:

- A heading with the app, how many of its requests went to outside companies, and the domains treated as its own.
- **Summary**: each kind of data and the companies that received it.
- A section per company with a table of **Data**, **Where**, **Example** and **Requests**, including where backend values came from.
- The companies that received no personal data.
- **Privacy label starting point**: the table of data types, App Store categories and companies.

It ends with a note that values are partly hidden and only decrypted HTTPS traffic was checked.

## Limits

- **Only decrypted traffic.** Requests that pass through as encrypted tunnels can't be read. Turn on SSL Proxying for the app, and [capture it through DNS](https://docs.subcloak.com/cloak-proxy/capture/apps-that-ignore-the-proxy/) if it ignores the proxy. Apps that pin certificates can't be decrypted at all.
- **Only what you captured.** Data sent on screens you didn't open, or by SDKs that send in batches later, isn't on the map. Use the app thoroughly, then wait a moment before you rescan.
- **Only plain HTTP payloads.** SDKs that encrypt or encode their payloads before sending them, or send data over something other than HTTP, such as raw sockets, don't show their contents.
- **Heuristics can miss or misclassify.** Data under unusual key names isn't recognized unless it has a recognizable shape or matches your test account. A value can also be flagged that isn't personal data. Check findings against the request before you act on them.
- **Your own servers aren't checked.** Data sent to the app's own domains doesn't appear, even though it may still belong on a privacy label.

> **Map apps you're allowed to inspect:**
> Privacy Map reads decrypted traffic. Use it on your own apps, or on apps you have permission to test, with test accounts. See [Security and privacy](https://docs.subcloak.com/cloak-proxy/reference/security-and-privacy/).

## Map from an AI assistant

The [MCP server](https://docs.subcloak.com/cloak-proxy/automation/mcp-server/) has a read-only `privacy_map` tool. It takes the `app` name (as `list_apps` shows it) and optionally `first_party`, a list of the app's own registrable domains. Without `first_party`, it uses the domains you saved for the app in the window, or the suggested ones. It returns each kind of data with its recipients and App Store category, and each company's findings with partly hidden examples. It doesn't use the test account values. See the [MCP tools reference](https://docs.subcloak.com/cloak-proxy/automation/mcp-tools-reference/#privacy_map).

## Related

  - [Secrets](https://docs.subcloak.com/cloak-proxy/insights/secrets/): Find API keys, tokens and card numbers anywhere in captured traffic.
  - [Trace Value](https://docs.subcloak.com/cloak-proxy/inspect/trace-value/): Follow one ID or token through every request that carried it.
  - [SSL Proxying](https://docs.subcloak.com/cloak-proxy/capture/ssl-proxying/): Decrypt the app's HTTPS traffic so Privacy Map can read it.
  - [Release diff](https://docs.subcloak.com/cloak-proxy/testing/release-diff/): See which companies receive new data in a new build.
