The runner's key.knotless-run makes a P-256 key on its first start, keeps it in --secrets-key <file> (secrets-key.json beside the state file by default, mode 0600), and publishes its public half as its dev.knotless.runnerKey record, a did:key. It writes the record again only when the key differs. The file holds a list, newest first, so a key can be replaced while values sealed to the old one are set again.
Sealing.knotless::sealed seals a value to a runner's key with HPKE (RFC 9180) in base mode, DHKEM(P-256, HKDF-SHA256), HKDF-SHA256 and AES-128-GCM, through the hpke crate at 0.12, the release on the workspace's p256 0.13 and rand_core 0.6. The AEAD's associated data binds the owner's DID, the repository's key, the runner's DID, the secret's name and its refs, so a value copied anywhere else does not open.
Nothing seals a secret yet: the appview's methods and knotless secret come next.
Why
The second part of the design on the issue: owners giving the hosted runner secrets without handing them to its operator. A record rather than a DID-document key, since writing the DID document would need the PLC rotation key on every runner.
Unit tests in sealed.rs: a value opens where it was sealed for, and not with another name, refs, repository, key, or bytes too short or garbled; a K-256 key is refused; a key file with no key, or a bad one, is an error. tests/run.rs: the runner publishes the key its file holds. sealed tests run here; the rest is left to CI.
Shortcomings
Base mode does not authenticate who sealed a value: whoever holds the sealed values (the appview, in the design) could swap one in, though never read one. docs/design/runner.md says so.
What changes
The runner's key.
knotless-runmakes a P-256 key on its first start, keeps it in--secrets-key <file>(secrets-key.jsonbeside the state file by default, mode 0600), and publishes its public half as itsdev.knotless.runnerKeyrecord, adid:key. It writes the record again only when the key differs. The file holds a list, newest first, so a key can be replaced while values sealed to the old one are set again.Sealing.
knotless::sealedseals a value to a runner's key with HPKE (RFC 9180) in base mode, DHKEM(P-256, HKDF-SHA256), HKDF-SHA256 and AES-128-GCM, through thehpkecrate at 0.12, the release on the workspace'sp2560.13 andrand_core0.6. The AEAD's associated data binds the owner's DID, the repository's key, the runner's DID, the secret's name and its refs, so a value copied anywhere else does not open.Nothing seals a secret yet: the appview's methods and
knotless secretcome next.Why
The second part of the design on the issue: owners giving the hosted runner secrets without handing them to its operator. A record rather than a DID-document key, since writing the DID document would need the PLC rotation key on every runner.
Part of A repository owner can't give the hosted runner a secret
How it was checked
Unit tests in
sealed.rs: a value opens where it was sealed for, and not with another name, refs, repository, key, or bytes too short or garbled; a K-256 key is refused; a key file with no key, or a bad one, is an error.tests/run.rs: the runner publishes the key its file holds.sealedtests run here; the rest is left to CI.Shortcomings
Base mode does not authenticate who sealed a value: whoever holds the sealed values (the appview, in the design) could swap one in, though never read one.
docs/design/runner.mdsays so.🤖 Generated with Claude Code