# Third-party sync

Most ways of getting secrets into a running app keep decryption on your side:
[`seekrit run`](/docs/guides/run) decrypts in your process, the
[egress proxy](/docs/guides/agent-proxy) decrypts in your proxy, the
[SDKs](/docs/guides/sdks) decrypt in your code, and
[ESO](/docs/guides/kubernetes) decrypts in your cluster.

Some platforms don't let you run anything before your app starts. A Vercel build
reads environment variables that Vercel already holds; there is no earlier point
to inject them. **Third-party sync** is for exactly that case: seekrit stays the
source of truth, and pushes the environment out whenever it changes.

## Choose the right tool first

| If you… | Use | Who decrypts |
| --- | --- | --- |
| control the process (CI job, container, server) | [`seekrit run`](/docs/guides/run) | your machine |
| have untrusted or agent workloads | [egress proxy](/docs/guides/agent-proxy) | your proxy |
| run Kubernetes | [ESO chart](/docs/guides/kubernetes) | your cluster |
| are writing the app | [a language SDK](/docs/guides/sdks) | your process |
| **don't control the runtime** (Vercel build env, a managed platform's env vars) | **sync** | **seekrit's sync engine** |

Sync is the last row, and it is the only feature in seekrit where our servers
decrypt anything. Reach for it when the rows above don't apply.

> **Warning:** **Enabling sync lets seekrit decrypt that environment.** To push a value to Vercel, something has to hold it in the clear, and sync runs when nobody is logged in — so the sync engine must be able to decrypt on its own. This applies **only** to environments you explicitly enable it for, and only to the destination you named. Everything else stays zero-knowledge: seekrit cannot decrypt an environment that has no sync grant, and enabling one requires a key holder, so it can't be switched on server-side.

## How it works

Enabling sync creates two things:

1. A **connection** — the destination account (a Vercel token). The token is
   encrypted in your browser to a public key held by the sync engine, so the
   control plane stores ciphertext it cannot open.
2. A **binding** — one environment → one destination. Creating it also creates
   a **key grant**: the environment's data key, wrapped in your browser to that
   same public key. This grant is what authorizes decryption, and it is
   [an ordinary key grant](/docs/concepts/access-control) — visible alongside
   your users and service tokens, and revocable the same way.

```
secret write ─▶ seekrit API ─▶ sync engine ──▶ Vercel project env
                               (decrypts here, only with a grant)
```

The private half of that keypair lives inside the sync engine and is never
written to the database. Deleting the connection destroys it, which turns every
grant made to it into ciphertext nobody can open — including us.

## Set up a Vercel connection

### 1. Create a Vercel token

In Vercel, go to **Settings → Tokens** and create a token with access to the
project you want to sync into. If the project belongs to a Vercel Team, note the
team id (`team_…`) — a personal-scope token can't write to a team project, and
Vercel reports that as a bare 403.

### 2. Add the connection

In the dashboard, open **Sync → Add connection**, choose Vercel, paste the token,
and set the team id if you have one. The token is encrypted in your browser
before it is sent.

Or from the CLI:

```bash
seekrit sync connections add vercel --name "acme-production" --team-id team_abc
```

### 3. Bind an environment

Pick the application environment to sync and the Vercel project and targets
(`production`, `preview`, `development`). This is the step that shows the
decryption disclosure and creates the key grant, so it has to be done by someone
who can already read the environment.

```bash
seekrit sync enable acme/storefront/production \
  --to acme-production \
  --project prj_abc \
  --target production
```

seekrit pushes once immediately, then on every change.

## Naming and filtering

By default, secret names are pushed verbatim. A binding can adjust that:

- **Prefix / suffix / case** — e.g. prefix `NEXT_PUBLIC_` or force upper case.
- **Rename** — map individual names exactly. An explicit rename is used
  verbatim; prefix and case are not applied on top of it.
- **Include / exclude** — glob allow/deny lists (`DB_*`, `*_PASSWORD`).
  Exclusion always wins over inclusion.

If two secrets would end up with the same destination name, the run fails and
tells you which two. seekrit will not silently let one value shadow another.

## Secret references

[`${OTHER_SECRET}` references](/docs/guides/references) are expanded before the
push, the same way `seekrit run` and the SDKs expand them.
The destination receives the final value, not the reference — pushing the raw
stored text would put a literal `${OTHER_SECRET}` into your Vercel project.

A reference to a name that doesn't exist is left as written (so CI templating
like `${GITHUB_SHA}` passes through untouched). A reference **cycle** fails the
run, because no value would be correct to push.

## Composed environments

Sync pushes the **effective** environment — what your app actually resolves,
including every [composed group](/docs/guides/environments). That means the
connection needs a grant on each composed group environment too, not only the
application environment. The dashboard walks you through granting all of them;
if one is missing, the run fails with `no key grant for group "…"` rather than
pushing a partial environment.

Sync targets application environments. To sync a shared group, bind the
application environments that compose it.

## Branch environments

A [branch environment](/docs/guides/branches) can be synced like any other, and
the pairing is a natural one: point a branch at a Vercel `preview` target with
its git branch set, and each PR gets its own overlay downstream.

Two things follow from how branches inherit:

- A write to the **parent** re-syncs its branches too, since a branch supplies
  only the values that differ.
- When a branch **expires**, its binding goes with it and seekrit stops pushing.
  Values already at the destination are left alone — expiry removes the branch,
  not the copy Vercel is holding. Delete those in Vercel if you need them gone.

## Deletions

A binding's `onDelete` policy decides what happens when a secret disappears from
seekrit:

- **`delete`** (default) — remove it at the destination too, so the destination
  is a true mirror.
- **`retain`** — leave it. Use this when something else also writes to that
  project and seekrit is not the only source.

Only variables whose Vercel targets overlap the binding's are ever removed, so
two bindings can point at one project with different targets without stepping on
each other.

## When a sync fails

Runs are recorded with per-name outcomes. A partial run is normal — the Vercel
API is not transactional, so some names can land while others fail — and only
the failures are retried.

Retries back off exponentially. After five consecutive failures the binding
stops and org admins get an email, because the destination is now serving stale
values and nothing downstream will say so on its own. Fix the cause, then
re-enable the binding or use **Sync now**.

Two failures worth recognizing:

- `no key grant for …` — the grant was revoked, or the environment now composes
  a group the connection can't read. Re-enable sync for that environment.
- A Vercel `403` — the token lost access, or the project is team-owned and the
  connection has no team id.

## Turning it off

Deleting a binding revokes that environment's grant to the connection (unless
another binding still needs it). Deleting the connection revokes all of its
grants and destroys its keypair.

Either way the values already at the destination stay there — seekrit stops
updating them, it does not reach in and clean up. Remove them in Vercel if you
want them gone.
