# Privacy & data handling

A local build sends nothing. A cloud build obfuscates in memory and keeps nothing, except the Protection Map you asked it to store, which you can turn off. Telemetry is on by default, reports when a build reports an error-level diagnostic, never on a clean build, and turns off with one env var.

Source: https://www.afterpack.dev/docs/privacy

AfterPack's obfuscation product handles your code and account data as described below. This site's cookies and analytics are covered separately, in the [Privacy Policy](https://www.afterpack.dev/privacy).

> **The short version**
>
> A [local (Free) build](https://www.afterpack.dev/docs/deployment-modes) never sends your source anywhere. A cloud (Pro) build obfuscates in memory and discards the source at the end of the request. One artifact is stored: the **[Protection Map](https://www.afterpack.dev/docs/protection-map)** for the [dashboard](https://www.afterpack.dev/docs/dashboard). It is [workspace](https://www.afterpack.dev/docs/dashboard/workspaces)-scoped, it expires, and you can turn it off per project. Telemetry is on by default, reports when a build reports an error-level diagnostic (a refused or partial build), never on a clean build, and turns off with [`AFTERPACK_telemetry_enabled=false`](https://www.afterpack.dev/docs/config#telemetry-enabled). A DPA and the full sub-processor list are available on request.

## A local build sends nothing

The [Free engine](https://www.afterpack.dev/docs/deployment-modes) is a native binary that runs on your machine. Nothing leaves your filesystem, so the cloud sections below don't apply to it. The only network call a local build ever makes is the telemetry report described below, which fires only when a build reports an error-level diagnostic (a refused or partial build), carries no source, and can be switched off.

## A cloud build obfuscates in memory and keeps nothing

When a [Pro build](https://www.afterpack.dev/docs/pro) sends source to `api.afterpack.dev` to be obfuscated, it is processed **in memory only**, for the duration of that single request, then discarded:

- **The request runs inside a sandboxed Cloudflare Worker isolate** that is destroyed at the request boundary. Nothing survives past the response.
- **Logging never carries source.** Log lines carry a build id, file counts, byte totals, diagnostic codes, and the path of a file that failed. They never carry file contents, identifier names, or constant values.

This holds identically whether you call the API directly, through [the CLI](https://www.afterpack.dev/docs/cli) with [`AFTERPACK_key`](https://www.afterpack.dev/docs/config#key) set, or through a [framework plugin](https://www.afterpack.dev/docs/frameworks). They all speak the protocol described in the [Cloud API reference](https://www.afterpack.dev/docs/cloud-api).

One artifact is stored: the Protection Map, described below, written after the obfuscation is finished. The source you upload to be obfuscated is still gone at the end of the request.

### What logs do and don't carry

| Recorded | Never recorded |
| --- | --- |
| Timestamps (request received, response closed) | File contents |
| HTTP status codes | Identifier names |
| Build id, file counts, input/output byte totals | Constant values, including secrets that happened to appear in source |
| [Diagnostic codes](https://www.afterpack.dev/docs/diagnostics) (`DIAG_*`) | Diagnostic *message text* from telemetry |
| Client IP, for rate limiting | Source map `sources` / `sourcesContent` |
| Workspace tier, for metering | Anything that could reveal your business logic |

Aggregated metrics (per-day request counts, per-workspace metered bytes, build timings) are kept as counters and contain no code.

## Telemetry is on by default, and only reports a build's error-level diagnostics

The client can report anonymous build diagnostics so engine bugs get found and fixed. It is on unless you turn it off. A clean build has nothing to report:

- **Only a build that emits an `error` or `critical` diagnostic sends anything.** A clean build sends nothing at all: no periodic ping, no phase timings, no usage stats for a build that just worked.
- **Turn it off with [`AFTERPACK_telemetry_enabled=false`](https://www.afterpack.dev/docs/config#telemetry-enabled)**, [`--telemetry.enabled=false`](https://www.afterpack.dev/docs/config#telemetry-enabled) on the CLI, or [`telemetry: { enabled: false }`](https://www.afterpack.dev/docs/config#telemetry-enabled) in your integration's config. Those are the whole surface.
- **It runs identically in CI.** No TTY or CI check changes the default or skips reporting; the payload simply records `ci: true`, read from the same `CI` variable your pipeline already sets.
- **No prompt, ever.** The first build after install prints a one-time notice (what is collected, what is not, how to turn it off) and moves on. It never blocks waiting for an answer. State (a random install id that rotates every 30 days, and whether the notice has printed) lives in `~/.afterpack/telemetry.json`.
- **Test runs are exempt.** `NODE_ENV=test` and Vitest's own `VITEST` marker disable telemetry.

### What's collected, when a build reports an error-level diagnostic

- The failing diagnostic's **code**, severity, and byte-offset span, plus a short list of typed fields chosen per diagnostic kind, never its free-text message
- Engine, client, and Node **versions**, OS/arch, and the detected framework
- Whether the build ran in **CI**
- The complexity level: a named [preset](https://www.afterpack.dev/docs/presets), or `custom` for a raw [`complexity`](https://www.afterpack.dev/docs/config#complexity)
- File count and build duration, **bucketed** (`5-20`, `1-5s`) rather than exact; an exact number fingerprints a specific project
- A random **install id** that rotates every 30 days, never derived from your machine or project

### What's never collected, telemetry or otherwise

- Source code or file contents
- File names and file paths in telemetry. A cloud build's server-side log carries one path, the file that failed inside the batch, as described above
- Diagnostic **message text**, ever; only its stable `code`
- API keys or credentials
- Constant values
- String encoding choices
- Any user-identifying information beyond what your account itself holds

Telemetry has a fixed shape.

## Protection Maps for cloud builds are stored, and you can turn that off

A [Protection Map](https://www.afterpack.dev/docs/protection-map) *is* your original source, coloured by how hard each token is to reverse. On a cloud build, AfterPack generates it on the server that did the obfuscation and keeps it, so you can open any recent build's map from the [dashboard](https://www.afterpack.dev/docs/dashboard/builds).

> **This is the one place your source sits at rest**
>
> Storing Protection Maps is **on by default, per project**, and a stored map is readable by **every member of the [workspace](https://www.afterpack.dev/docs/dashboard/workspaces) that owns the project**. If your source shouldn't be readable workspace-wide, a workspace admin [turns the setting off](https://www.afterpack.dev/docs/protection-map#turning-it-off) in that project's settings.

- **What's stored:** the map data for each build, your original source plus the per-token metrics behind the colours, compacted and gzipped into Cloudflare R2, encrypted at rest, plus a row of summary numbers for the dashboard. Your obfuscated output is not stored.
- **Scoped to one project**, the one the build's [key](https://www.afterpack.dev/docs/dashboard/api-keys) is bound to.
- **Readable only by members of that workspace.** Not public, not indexed, no shareable link.
- **Retention: one year, and the last 500 builds per project, whichever runs out first.** An hourly job deletes whatever is past either bound. For a project that builds often, the count is the bound that bites first. It caps how many copies of your source exist at any moment.
- **A map may be stored partial.** When a build runs long, capture stops before obfuscation does. The stored record says which chunks were skipped and why.
- **Turning it off** stops new builds from storing anything for that project. Maps already stored age out under the retention above rather than being deleted immediately. Local builds are unaffected: the Protection Map a local build [writes into `.afterpack/`](https://www.afterpack.dev/docs/cli#files) never leaves your machine.
- **What's still recorded with it off:** the build's numbers (coverage, inflation, complexity, timings), so the project's [dashboard charts](https://www.afterpack.dev/docs/dashboard/builds) keep working. These are aggregates derived from a build. They contain no code, and nothing in them can be read back as code.

## Sub-processors

A cloud build touches these processors. Source passes through Cloudflare **in memory only** while it is being obfuscated; Cloudflare is also the only one that ever holds source at rest, as the Protection Map described above:

| Sub-processor | Role |
| --- | --- |
| **Cloudflare** | Workers / R2 / D1: edge compute, routing, Protection Map storage, and the account database |
| **Creem** | [Billing](https://www.afterpack.dev/docs/dashboard/usage) only, as merchant of record; never receives source |
| **Email / auth providers** | Magic-link and transactional email for account and key-lifecycle events; never receive source |
| **Anthropic** (via Cloudflare AI Gateway) | Powers the in-site assistant ("Ask AfterPack"); receives what you type into it and the documentation pages it reads to answer, never your source code |

## Data residency

The sections above answer whether source is stored. Residency is the separate question of where it is processed while a request is live, and where the stored map sits.

Neither is region-pinned. A cloud build is routed by Cloudflare to a nearby region, and under failover or load it can land elsewhere. There is no per-request region pin, and R2 storage is not region-locked. **If you need a firm residency guarantee, use a [local build](https://www.afterpack.dev/docs/deployment-modes).** The Free engine runs entirely on your own infrastructure, so no residency question arises.

## DPA and compliance

A Data Processing Agreement is available on request. The DPA you sign is the legal, executable contract, and it points back to the sub-processor list and residency behaviour described above.

## Next

- [Cloud API reference](https://www.afterpack.dev/docs/cloud-api): the wire protocol these guarantees apply to.
- [Deployment modes](https://www.afterpack.dev/docs/deployment-modes): local versus cloud, and what each one sends.
- [Protection Map](https://www.afterpack.dev/docs/protection-map): what the stored map shows, and where the per-project switch lives.
- [Pro](https://www.afterpack.dev/docs/pro): what running in the cloud actually buys.
- [Workspaces & projects](https://www.afterpack.dev/docs/dashboard/workspaces): who can read what, and how a key is scoped.
- [Dashboard](https://www.afterpack.dev/docs/dashboard): where a stored map and its build record are shown.
