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.

# 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 nameNames 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.