Skip to content

Secrets broker: encrypted secrets, admin policy, audit - #6

Merged
KristiforMilchev merged 1 commit into
mainfrom
secrets
Nov 3, 2026
Merged

KristiforMilchev merged 1 commit into
mainfrom
secrets

Conversation

@KristiforMilchev

Copy link
Copy Markdown
Contributor

secrets: in pipelines now deliver values, under the admin's control. Part of milestone M1.

How a job gets a secret. All four checks must pass; any failure is a denial with a reason:

  1. The worker asking holds the job right now.
  2. The job token is the current attempt's. It's compared in constant time, and only its hash is stored.
  3. The job's pipeline lists the secret under secrets:, and the runner only asks for the secrets a step names.
  4. The admin's policy for that secret allows the run: repository globs, pools, ref globs, and pull requests, which are off by default because they run unreviewed code.

Storage

  • Values are encrypted with AES-256-GCM under a key file only the controller can read. Each ciphertext is bound to its secret's name, so it can't be copied to another secret in the database.
  • Moving the key into a KMS or HSM is on the roadmap (M5).

Audit: every request is recorded, granted or denied. If the record can't be written, the secret isn't handed out.

CLI

  • controller secret set NAME -repo 'acme/*' -pool trusted [-ref refs/heads/main] [-allow-pull-requests] reads the value from stdin, never from the command line.
  • controller secret list shows policies, never values.
  • controller secret delete removes a secret.
  • controller secret access NAME shows the audit trail.

Tests

  • End to end with real commands: the secret arrives, only the step that asked for it has it in its environment, and it shows as *** in the stored log.
  • Each policy denial: wrong pool, wrong repository, wrong branch, pull request.
  • Requests sent directly over a worker connection: an unlisted secret, a wrong token, another worker's job, an unknown job. All are denied and all are audited.
  • To check the tests catch real bugs, I removed each check in turn (worker, token, secrets list, pool, pull requests). Each removal made a test fail.
  • Also run by hand with the real binaries: secret set → run → log shows deploying with *** → secret access shows one granted request, and the raw value is in none of the stored log files.

🤖 Generated with Claude Code

- Secrets encrypted with AES-256-GCM under a key file only the controller
  reads; each ciphertext is bound to its secret's name
- A request is granted only if the worker holds the job, the job token is
  the current attempt's, the job lists the secret, and the admin's policy
  allows the run: repositories, pools, refs, and pull requests (off by
  default)
- Every request is recorded, granted or not; without an audit record the
  secret isn't handed out
- controller secret set (value from stdin), list, delete, access
- Tests: granted and masked end to end, each policy denial, and attempts
  around the checks (unlisted secret, wrong token, another worker's job);
  each check verified by removing it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@KristiforMilchev
KristiforMilchev merged commit 77a2463 into main Nov 3, 2026
1 check passed
KristiforMilchev added a commit that referenced this pull request Nov 3, 2026
…tains #6)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant