# Threat model

This page puts the facts from the [security model](security.md), [Trust and your
data](trust.md) and [GitHub App](github-app.md#permissions) in one
place and says what they do not cover. It does not replace an independent
audit. Upseam has had no independent security audit, and it has no SOC 2
report.

## Where your data goes

The diagram follows one fix from your repository to the model and back. The
text under it says the same in words.

```diagram title="Where your data goes, from your repository to the model and back"
 Your repository (GitHub)
 +------------------------------------------------+
 | source files, lockfiles, .github/upseam.yml    |
 +------------------------------------------------+
          | read through the App (or the Action, or the CLI)
          v
 +------------------------------------------------+
 | Upseam, step by step                           |
 | - archive in a temporary directory             |
 | - find SDKs, versions and matched places       |
 +------------------------------------------------+
     |                                   |
     | change data + files with matches  | path, line, symbol
     | + your key                        v
     v                          +------------------------+
 +------------------------+     | Upseam database        |
 | Your model vendor      |     | no code, no diffs      |
 | OpenAI, Anthropic or   |     | encrypted: model key,  |
 | an OpenAI-compatible   |     | Slack bot token        |
 | endpoint you name      |     +------------------------+
 +------------------------+
     | answer: whole files
     v
 +------------------------------------------------+
 | Patch gates (deterministic)                    |
 +------------------------------------------------+
     | pass: one commit             | fail: nothing is pushed,
     v                              v the finding goes to Needs you
 branch upseam/* in your repository
```

- **Read.** The App downloads a repository archive into a temporary directory
  for one step and deletes it when the step ends. The scan reads source files
  of the [supported languages](languages.md#files-upseam-reads) under 1 MB,
  and skips directories such as `node_modules`, `vendor` and `dist`.
- **Sent to the model.** The change data (the vendor's changelog entry) and
  the complete content of the files with high-confidence matches, up to
  24,000 characters in all, never a file under `.github/`. With a stored key,
  also style hints taken from your `.editorconfig` and Prettier settings.
- **Stored by Upseam.** Installation, settings, surfaces, matches as path,
  line and symbol, proposals, approvals, and encrypted secrets. Not your code,
  diffs, the model's answer or pull request and issue bodies. The full table
  is in [Trust and your data](trust.md#what-we-see-and-store).
- **With your own GitHub Actions or your own agent**, the model is called on
  your runner and Upseam never sees your key. See
  [Generate in your GitHub Actions](models.md#generate-in-your-github-actions)
  and [Bring your own agent](own-agent.md).
- **The CLI** runs on your machine or in your CI, with no Upseam account and
  no Upseam server. See [The CLI](trust.md#the-cli).

## What the App can and cannot do

| Permission             | Access       | Why                                                                                                                                     |
| ---------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| Metadata               | read         | Basic access to the installation's repositories.                                                                                        |
| Contents               | read & write | Read the code and lockfiles; write commits to `upseam/*` branches; send `repository_dispatch` when fixes are generated in your Actions. |
| Pull requests          | read & write | Open and update Upseam's pull requests; read Dependabot and Renovate pull requests.                                                     |
| Issues                 | read & write | Keep the dashboard issue up to date and read its checkboxes.                                                                            |
| Checks                 | read         | Read your CI result.                                                                                                                    |
| Commit statuses        | read         | Read your CI result reported as statuses.                                                                                               |
| Members (organization) | read         | List the organization's teams for the Routing picker.                                                                                   |

- **Not requested:** Workflows, Actions, Administration, Secrets. Without the
  Workflows permission GitHub does not let the App change files in
  `.github/workflows`. The App cannot read your repository or organization
  secrets or change repository settings.
- **Writes go to `upseam/*` branches.** Upseam's code refuses to create or
  update any other branch and to open a pull request from any other branch.
  Upseam never pushes to your default branch, and it has no code that merges
  a pull request.
- **GitHub cannot narrow `contents: write`.** The permission applies to the
  whole repository: GitHub offers no way to limit it to a path or a branch.
  The limit to `upseam/*` is a rule in Upseam's code, not something GitHub
  enforces. Your own protection is a branch protection rule or ruleset on the
  default branch, which applies to the App unless you list the App as allowed
  to bypass it.
- **Installation tokens** are created for each use with the permissions the
  step needs, held in memory only, and expire after at most an hour, as GitHub
  sets. Upseam drops its cached tokens of an installation when GitHub reports
  that the installation was deleted or suspended.

## If Upseam is compromised

Assume an attacker who controls Upseam's hosting and its secrets: the GitHub
App's private key, the database and the master key for encrypted values.

What the attacker could do:

- **Read your code.** With the private key the attacker can create
  installation tokens and read every repository of every installation, as
  Contents read allows. The database holds no code, but GitHub does.
- **Write to your repositories.** The same tokens carry `contents: write`, and
  GitHub does not limit it to `upseam/*`. An attacker outside Upseam's code is
  not bound by its branch rule and could push to any branch the protection
  rules of your repository allow, including a branch your CI runs with
  secrets. Short token lifetime does not limit this: with the key the attacker
  creates new tokens.
- **Use stored model keys and Slack bot tokens.** They are encrypted with
  AES-256-GCM, and the master key is a secret of the hosting platform. Anyone
  who holds both the master key and the database can decrypt them. This is
  the reason to [generate in your own GitHub Actions](models.md#generate-in-your-github-actions)
  if the key must never leave your secrets.
- **Open pull requests and issues** in your repositories as the App.

What limits it:

- **No Workflows, Actions, Administration or Secrets permission.** The
  attacker cannot edit your workflow files, read your Actions secrets or
  change repository settings through the App.
- **Branch protection and rulesets on your default branch.** Require a review
  and status checks and do not let the App bypass them. Then a push to the
  default branch fails.
- **Review.** Upseam never merges. Nothing reaches the default branch without
  a person or a rule of yours.
- **`ask` mode and the patch gates** limit what Upseam's own pipeline does.
  They are code in Upseam's service, so an attacker who controls the service
  does not have to pass them. Do not count them as a defense against this case.
- **Your CI.** A branch the attacker pushes can trigger your `push` and
  `pull_request` workflows with your secrets, because it is a branch of the
  same repository. Keep secrets away from jobs that run on `upseam/*` before
  review, for example with a condition on `github.head_ref` or an environment
  with a required reviewer. This is the strongest defense on your side.
- **Uninstall** removes the App's access at once. If you suspect a
  compromise, uninstall, revoke the model key with your vendor and rotate
  Slack access. See [Uninstall](uninstall.md).

## Attacks, and what the gates catch and miss

The [patch gates](security.md#patch-gates) are deterministic code that checks
the model's answer before anything is pushed. They look at the answer, not at
who asked for it.

| Attack                                                                                                                          | What stops it                                                                                                                                                                                                                                                                                                                                                                                                                                                         | What does not                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| ------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Prompt injection in a vendor changelog.** Text that tells the model to add a network call or read environment variables.      | The brief quotes the changelog as untrusted data and tells the model to ignore instructions in it. The gates refuse new imports, URLs and hosts, forbidden names such as `process` and `fetch`, new locals, new control flow and edits outside the matched statement. `ask` pushes nothing before a person approves.                                                                                                                                                  | The instruction to the model is not a hard barrier. A patch that passes the gates can still change how data flows within two lines of the matched code, for example by renaming to a name from the vendor's data.                                                                                                                                                                                                                                                                                                                                                                                      |
| **Prompt injection in your repository text.** A comment or string in a file that is sent to the model.                          | Same brief rule and the same gates: they check the answer whatever the source of the instruction.                                                                                                                                                                                                                                                                                                                                                                     | Same as above. The gates do not read a comment for intent.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| **A poisoned changelog.** The vendor's published change, or Upseam's copy of it, says to rename to a wrong or malicious symbol. | The gates allow new names only from the file or from the vendor's change, only after `.` or as a key, and refuse anything that is a global or a forbidden name. Upseam's change data is built from vendor sources into `data/`, which has a code owner on every change. The App does not wait for that review: it reads OpenAI, Anthropic and Gemini model retirements from the vendor's deprecations page directly and uses them with no person checking them first. | The gates check the shape of an edit, not whether the vendor's data is true. If the change data names a wrong replacement, the patch uses it. If you turn **Simple fixes** on, a [simple fix](simple-fixes.md) opens without approval, so a wrong replacement model id, from a parser error, a changed page layout or a wrong row on the vendor's page, reaches your CI before review. The replacement must match the model id pattern (letters, digits, `.`, `_`, `:`, `/` and `-`, no spaces or quotes), and the gates still check the edit. Keep **Simple fixes** off, the default, to approve each one. Read the pull request and run your tests. |
| **An invented package.** The model adds an import or a dependency that does not exist or is hostile.                            | A patch may not add imports, new files or any name that is not in the file or the change. The model is given source files only, never `package.json` or a lockfile, so the patch edits no dependency list.                                                                                                                                                                                                                                                            | Upseam does not check a package against a registry. A Dependabot or Renovate bump that Upseam adapts code to is chosen by the bot, not by Upseam, and always waits for approval.                                                                                                                                                                                                                                                                                                                                                                                                                       |
| **Upseam itself is compromised.**                                                                                               | See [above](#if-upseam-is-compromised): the missing permissions, your branch protection and review.                                                                                                                                                                                                                                                                                                                                                                   | The gates and `ask` do not protect against it. GitHub's `contents: write` covers the whole repository.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| **Session theft.** Someone gets a signed-in session of the App.                                                                 | The session is an encrypted, `HttpOnly`, `Secure` cookie for at most seven days. An installation is shown only to users GitHub lists for it, and every change is checked against a fresh answer from GitHub. **Sign out** revokes the GitHub token. When you revoke Upseam's authorization in GitHub, your earlier sessions end.                                                                                                                                      | A stolen cookie works until it expires, you sign out or you revoke the authorization. A repeated, identical revocation notice from GitHub does not end a session opened after the first one: in that case an open page keeps showing your login until the session ends, while every change is still checked with GitHub. Slack buttons do not check GitHub access: any full member of the connected workspace can press them.                                                                                                                                                                          |

The gates are an allow-list. Legitimate edits they refuse, such as editing a
condition or a line with `process.env`, go to a person too.

## Models and your data

Upseam has no model of its own and no model keys of its own. It sends your
vendor the data described [above](#where-your-data-goes), and your vendor
handles it under the terms of your account. Upseam does not use your code to
train models. These are the vendors you can connect and their official pages:

| Vendor          | Training and retention                                                                                                                                                          | Official page                                                                                                                                                                                           |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Anthropic API   | Inputs and outputs of commercial products are not used for training by default. Inputs and outputs are deleted from the backend within 30 days, with exceptions the page lists. | [Training](https://privacy.claude.com/en/articles/7996868-is-my-data-used-for-model-training), [retention](https://privacy.claude.com/en/articles/7996866-how-long-do-you-store-my-organization-s-data) |
| OpenAI API      | Data sent to the API is not used for training unless you opt in. Abuse monitoring logs are kept for up to 30 days by default.                                                   | [Your data](https://developers.openai.com/api/docs/guides/your-data)                                                                                                                                    |
| Azure OpenAI    | Prompts and completions are not used to train foundation models without your permission. Abuse monitoring may store flagged samples.                                            | [Data, privacy, and security](https://learn.microsoft.com/en-us/legal/cognitive-services/openai/data-privacy)                                                                                           |
| OpenRouter      | OpenRouter does not use inputs or outputs for training, but some model providers behind it may.                                                                                 | [Privacy policy](https://openrouter.ai/privacy)                                                                                                                                                         |
| Your own server | Set by you.                                                                                                                                                                     |                                                                                                                                                                                                         |

The vendors change these terms. Read the page before you connect a key, and
check the plan your key belongs to: consumer plans can differ from API terms.

## Secrets and `.env` files

What the code does, and no more:

- **Which files are read.** The scan opens files with the extensions of
  its [supported languages](languages.md#files-upseam-reads), and manifests
  and lockfiles to read SDK versions. It also opens `.env`, YAML, JSON and
  TOML files, but only to find a retired model id: from such a file it keeps
  the path, the line and the id, and nothing else of the line is stored,
  shown, logged or sent to a model. A `.env` file is never part of a model
  brief; a model id in one is replaced by the
  [simple fix](simple-fixes.md) without a model.
- **Which files reach the model.** Only files with a high-confidence match of
  a vendor change, with a limit of 24,000 characters in all. Files under
  `.github/` are never included.
- **Likely secrets are masked first.** Before those files go to your model
  vendor, Upseam replaces keys in a known format and random-looking strings
  assigned to secret-like names with placeholders, puts the values back after
  the answer, and refuses a patch that changes a line holding one. Masking
  matches patterns and misses some secrets, such as short passwords or keys
  built at run time; the list is in
  [Secrets in your files](security.md#secrets-in-your-files). Keep secrets out
  of source files, and use [`ignore.paths`](configuration.md#ignore) to keep a
  path out of the scan.
- **New secrets are refused.** A patch that adds a likely secret that was not
  already in that file is refused. The reason names the file, line and kind,
  never the value.
- **Your model key** is encrypted in the database or stays in your GitHub
  secrets, and is never logged. See
  [Where keys live](trust.md#where-keys-live).

## Report a vulnerability

Write privately to contact@upseam.dev. Do not
open a public issue. Say what is affected (the CLI, the GitHub Action, the
GitHub App or the website), the steps to reproduce and the impact you expect.
We reply by email.

## What this page is not

It is not an audit. No third party has reviewed Upseam's code or service, and
Upseam has no SOC 2 report. The page lists what the code does today; the
code is the source of truth, and it changes. Check the
[changelog](changelog.md) and the code when it matters to you.
