Secrets

A value a workflow reads by name. Scoped to a space, a repository, or an environment; written once and never shown again; injected only into a job's container and masked in its log.

Scopes

A secret lives at one of three scopes. A workflow sees the union, and a name at a more specific scope shadows the same name at a broader one.

  • Space: every repository in the space. Set by the space's admin permission.
  • Repository: one repository, over the space's. Set by the repository's admin permission.
  • Environment: one named environment of a repository, over the repository's, injected only into a job that deploys to that environment. Set by the repository's admin permission.

Only the requested scope's rows are read when a job starts, so an out-of-scope secret is never injected.

Setting a secret

Under the repository's Settings → Secrets, enter a name and a value and save. The value is written once and never shown again: the listing carries the name, the scope, who set it and when, and nothing else. Saving the same name again replaces the value; Remove revokes it, and a revoked secret is never injected. Space and environment secrets are set through the API.

The Secrets page of a repository: a name and a value to add, an empty list of repository secrets, and variables below
Settings → Secrets: a value is written once and never shown again; the list holds names and dates only
# a repository secret
curl -X PUT -H 'Sigbound-Version: 2026-08-14' -H 'Authorization: Bearer $SIGBOUND_TOKEN' \
  -H 'Content-Type: application/json' -d '{"value":"..."}' \
  https://api.sigbound.com/orgs/<space>/repos/<repo>/secrets/DEPLOY_KEY
# a space secret:        PUT /orgs/<space>/secrets/<NAME>
# an environment secret: PUT /orgs/<space>/repos/<repo>/environments/<env>/secrets/<NAME>
# list:                  GET  ...same path without the name
# revoke:                DELETE ...same path with the name

Names and values

A name is upper-case letters, digits and underscores, not starting with a digit, at most 128 bytes. Names that would collide with the job's own environment are refused: PATH, HOME, LANG, LC_ALL, DATABASE_URL and the proxy variables, and any name starting with SIGBOUND_, GITHUB_, GIT_, LD_, DYLD_, AWS_, GOOGLE_ or AZURE_. A value is at most 4096 bytes.

How a workflow reads them

A step reads a secret as ${{ secrets.NAME }}, as it would on GitHub. The runner decrypts the job's scope when the job starts and registers every value with the log redactor before the job can print, so a value that reaches the log is masked. A job that deploys to an environment gets that environment's secrets laid over the repository's for the duration of the job.

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - run: ./deploy.sh
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}

Secrets reach a job only on a container-mode runner: the container is made for that one job and removed after it. A runner that runs jobs on its host injects nothing, says so once in the run's log, and secrets.* reads empty there. A repository with no secrets stored gives every secrets.* read an empty string. GITHUB_TOKEN is not a stored secret: it is the job's own token, minted per job, scoped to the repository, and write-capable only when the job's permissions say so.

Custody

Values are sealed under a keyring bound to their scope, and the platform has no operation that returns a plaintext value: the only decrypt path is the injection into a sandbox, and it fails closed, all or nothing. An identity provider's client secret is sealed under a separate key and is never injected into a job.