# Trust and your data

Short answers about your code and data. The details are in the
[security model](security.md).

## What we see and store

Upseam downloads your repository's archive to find affected code. The archive
exists only in a temporary directory during a step.

| Stored                                                  | Not stored                                                  |
| ------------------------------------------------------- | ----------------------------------------------------------- |
| Installation, repositories, settings                    | Your code, code lines, diffs, pull request and issue bodies |
| Surfaces: provider, SDK, versions, pins as file and line | Repository archives, after the step                         |
| Matches: path, line, symbol                             | The model's answer, after the step                          |
| Change events from your internal contracts: changed operations, types and fields | |
| Proposals: branch, commit, pull request number, CI status | GitHub installation tokens: in memory only, for up to an hour |
| Approvals: channel, decision, who decided               | Your model key, when you generate in your GitHub Actions    |
| Encrypted: Slack bot token, model key (when stored); its last four characters in plain text as a hint |                                                             |
| Product events: ids, flags and fixed values, for 13 months | |

Rows that belong to an installation are scoped to it by the database itself,
with row-level security. Upseam's internal tables, such as the work queue and
the analytics export state, are limited by database role grants instead.

## Where keys live

- **A model key stored with Upseam** is encrypted with AES-256-GCM under a
  data key of its own, and the data key is wrapped with a master key. The
  installation id is bound into both layers, so the ciphertext cannot be
  moved to another installation.
- The master key is a secret of the hosting platform, not a value in the
  database. Anyone who holds both the master key and a copy of the database
  can decrypt the stored key.
- The stored model key is decrypted only for **Test** and inside the patch
  step, and is never logged. For keys of 20 characters or more, the last
  four characters are kept in plain text as a hint. **Disconnect model**
  deletes both.
- **When you generate in your GitHub Actions**, the key stays in your GitHub
  secrets and Upseam never sees it. See
  [Generate in your GitHub Actions](models.md#generate-in-your-github-actions).
- **When your own coding agent writes the fixes**, its credential is an
  Actions secret in your repository. The task Upseam sends has no key and no
  token. See [Bring your own agent](own-agent.md).
- **The Slack bot token** is encrypted the same way as the model key. It is
  decrypted each time Upseam sends a Slack message.

## Who else receives your data

- **GitHub**, where your code already lives.
- **Your Slack workspace**, if connected: file paths and line numbers, no
  code.
- **The model vendor you choose**: the change data and the files with
  matches, sent with your key.
- **PostHog**: pseudonymous product events with ids, flags and fixed values,
  no code, repository names or user logins.
- **TypeSafe** labels vendor changes with Jev when Upseam ingests them. The
  App never sends your code to TypeSafe. The CLI's `agent` commands send
  TypeSafe what you pass them; see [The CLI](#the-cli).

This landing page and this docs site also send their own, separate events
to PostHog US in cookieless mode: no cookies, no visitor profile, no
local or session storage, and nothing loads if your browser sends Do Not
Track or Global Privacy Control. These site events cannot be linked to an
installation. Each one asks PostHog to skip IP geolocation; PostHog still
uses the visitor's IP address itself to compute the daily cookieless
identifier, which cookieless mode depends on, but does not store that
address as an event property.

## How long data stays

- Product events are deleted after 13 months.
- Records of finished or abandoned work are deleted after 30 days.
- Records of received webhook deliveries are deleted after 14 days.
- The rest of an installation's data stays while the App is installed.
- When GitHub reports the uninstall, Upseam deletes the installation with its
  repositories, surfaces, findings, settings, stored model key and Slack link.
  Its product events are deleted and Upseam asks PostHog to delete the
  pseudonymous person.
- Two markers with the installation id stay: a removal marker with the time
  of removal, so that late GitHub notices do not bring the installation back,
  and an analytics marker, deleted after 13 months, so that late product
  events are not exported.
- Records of queued work and received webhook deliveries are not part of the
  uninstall; they follow the periods above.
- Uninstalling does not revoke the Slack bot token with Slack. **Disconnect
  Slack** before uninstalling, or remove the Upseam app from your Slack
  workspace. See [Uninstall](install.md#uninstall).
- Repository archives exist only in a temporary directory during a step.

## What your CI runs

Upseam never runs your code or the model's answer. Your CI does.

- The model's answer becomes a commit on an `upseam/*` branch of your
  repository. Your `push` and `pull_request` workflows run on it with your
  repository's secrets before anyone reviews the pull request.
- Deterministic [patch gates](security.md#patch-gates) decide what may be
  pushed. A patch that fails any of them is not pushed, and the finding goes
  to **Needs you**.
- `ask` is the default mode: nothing is pushed before a person approves the
  finding. See [Why `ask` is the default](delivery-modes.md#why-ask-is-the-default).
- Dependabot and Renovate bumps always go through `ask`.
- On your side, keep secrets away from jobs that run on `upseam/*` before
  review, and protect the default branch.

## What the App may do

| Permission      | Access       |
| --------------- | ------------ |
| Metadata        | read         |
| Contents        | read & write |
| Pull requests   | read & write |
| Issues          | read & write |
| Checks          | read         |
| Commit statuses | read         |

Contents write is used for commits to `upseam/*` branches only and for
`repository_dispatch` when fixes are generated in your Actions. Upseam does
not ask for Workflows, Actions, Administration, Secrets or organization
permissions. Upseam never pushes to your default branch. Why each permission
is needed: [Permissions](install.md#permissions).

## The CLI

The `upseam` CLI runs locally or in your CI, with no Upseam account and no
Upseam server; the `agent` commands need a TypeSafe API key. These commands
are local only: `init`, `inspect <provider>`, `inspect internal`
without `--consumer-repo` and `--github`, and `report --dry-run`. These use
the network:

- `inspect internal --consumer-repo` downloads each consumer repository from
  GitHub with your token.
- `--github` on `inspect internal` and `report` sends the rendered comment or
  issue body to the GitHub API.
- `fix` sends the change and the matched files to your model vendor with
  your key, except with `--pulls`, `--publish`, or `--group` with `--edits`.
  `fix --github` then pushes one branch to your `origin` and opens a pull
  request. With `--stage <dir>` it only writes the patch to that directory and
  pushes nothing; the separate `--publish` step pushes and opens the pull
  request. With `--group` it only pushes the branch, and the App opens the
  pull request.
- `fix --pulls` looks up Upseam's pull requests through the GitHub API.
- `agent route` and `agent prune` send the task and the fragments you pass to
  TypeSafe with your `TYPESAFE_API_KEY`.

See [Data and privacy](cli.md#data-and-privacy).
