Skip to content

Private repositories

Patchwire scans private repos via per-project HTTPS access tokens. Tokens are encrypted with AES-256-GCM before persistence and are never returned in any API response.

How it works

When you save a token on a project, the API:

  1. Reads PATCHWIRE_DATA_KEY from environment (32-byte symmetric key, mounted from a sealed Kubernetes Secret).
  2. Generates a fresh 12-byte nonce.
  3. Encrypts the plaintext PAT with AES-256-GCM.
  4. Stores the wire format nonce ‖ ciphertext ‖ tag in projects.access_token_enc (BYTEA).

When a scan starts:

  1. The scan task fetches the row including the encrypted blob.
  2. Decrypts in-memory, no longer than the duration of the clone.
  3. Builds the URL https://USER:TOKEN@host/path, passes to git clone.
  4. The URL string lives in the cloning task's stack frame and is dropped at end of scope.

The wire-format response from the API only ever shows has_access_token: true — never the blob, never the plaintext.

Per-provider token setup

GitHub

Use a fine-grained personal access token scoped to one repository.

  1. https://github.com/settings/personal-access-tokens/new
  2. Resource owner: your account or org.
  3. Repository access: Only select repositories → pick the one repo.
  4. Repository permissions: Contents: Read-only.
  5. Generate.
  6. In Patchwire: project edit form → Access token → paste.
  7. Token username: leave default (x-access-token).

Fine-grained tokens have a maximum 1-year lifetime. Rotate by editing the project and pasting a fresh token; the old encrypted blob is overwritten.

GitLab

Use a project access token.

  1. Project → SettingsAccess Tokens.
  2. Token name: patchwire.
  3. Role: Reporter (the lowest role that can clone).
  4. Scopes: read_repository.
  5. Click Create.
  6. Copy the token value (you won't see it again).
  7. In Patchwire: project edit form → Access token → paste.
  8. Token username: oauth2.

Bitbucket Cloud

Use an app password scoped to repository read.

  1. https://bitbucket.org/account/settings/app-passwords/
  2. Label: patchwire.
  3. Permissions: Repositories: Read.
  4. Create.
  5. Copy the password value.
  6. In Patchwire: project edit form → Access token → paste the app password.
  7. Token username: your Bitbucket username (the one that owns or has access to the repo).

Forgejo

You don't need to set anything. Patchwire's installation has its own cluster-wide Forgejo PAT (FORGEJO_CLONE_TOKEN) that authenticates clones from git.ahmedanbar.dev automatically. Per-project tokens for Forgejo are simply ignored.

Updating or removing a token

In the project edit form (/projects/<slug>):

ActionWhat to do
Replace the tokenType the new token in the field. Submit.
Keep the existing tokenLeave the field blank. Submit.
Remove the tokenClick Clear token, then submit. The badge reverts to "no token configured".

The API enforces this tri-state on the PATCH body:

  • access_token field absent → leave unchanged
  • access_token: "" → clear (NULL the column)
  • access_token: "<value>" → re-encrypt and replace

Security notes

  • Encryption-at-rest only. The plaintext is in memory inside the API pod for as long as a scan takes to clone — typically 10 seconds. We do not yet redact the URL from in-process logs (a known follow-up; logs go to stdout and are read by kubectl logs).
  • One key, no rotation today. Rotating PATCHWIRE_DATA_KEY would invalidate every existing token. A key_version column lands when rotation becomes a real ask.
  • Per-project scope. Compromising one tenant's data-key access (effectively, root on the API pod) yields tokens for every project in every org. The blast radius is the entire installation, not one tenant.
  • The token is yours. Patchwire never makes outbound calls beyond git clone. There is no telemetry, no third-party reporting.

Troubleshooting

Scan fails with Authentication failed — the token doesn't grant read on this repo, or the username field is wrong (try oauth2 for GitLab, your real username for Bitbucket).

Scan fails with repository not found — the URL is wrong, the token is for the wrong account, or the repo was deleted/archived.

Token field shows "(currently configured)" but you don't remember setting it — check the audit trail: git log -- crates/api/src/handlers/projects.rs will tell you when token storage was added.

Released under a proprietary licence.