Confidentiality
Only the private keys of an environment's recipients decrypt its values. The repository, its forks, CI logs of check, and anyone with read access see only ciphertext.
Threat model
msecrets protects secret values at rest in your repository and makes every change to them attributable and reviewable.
age implementation.node:crypto (OpenSSL). msecrets has no dependencies.Only the private keys of an environment's recipients decrypt its values. The repository, its forks, CI logs of check, and anyone with read access see only ciphertext.
Readers verify signatures before decrypting. A value that was edited, moved to another secret, environment, or file, or written by someone outside the environment is rejected.
Access is per environment. Machine keys are read-only. Revocation re-encrypts every affected value or fails, so it can never silently leave access behind.
Who can read what is written in the file and visible in diffs. Keys and identity sources never come from the repository.
The file is plain text, and so is everything msecrets prints from it. Descriptions cannot contain control characters, error messages escape names, and revealed values are sanitized in the terminal UI, so a pull request cannot hide lines from reviewers or write to their clipboard with terminal escape sequences.
Treat the whole msecrets.json of a pull request as attacker-controlled, including who may read and sign. The
file cannot prove who a recipient really is; trust starts with the reviewed version on your default branch. Each
row is a regression test:
| The pull request… | Signatures and encryption | git diff shows |
|---|---|---|
| adds its author as a recipient | Reveal nothing; values become needs sync and msecrets check fails. A member’s sync would grant access. |
the new recipient and the changed access list |
| replaces a member’s key with its own | Every value that member signed becomes invalid. New values signed with the replaced key verify. | the member’s key fingerprint changing |
| restores an older signed value | The old value verifies (rollback). | the value’s signature line changing |
| moves or edits values, or claims another signer | Rejected: the value is invalid and never decrypted. | the changed values |
| writes with a read-only machine key | Impossible: machine keys cannot sign. | nothing to review |
So review every change to recipients, access, and values, and never run msecrets sync or grant on a pull
request you have not reviewed.
Parsers reject everything that is not canonical, and input is bounded: an age header has at most 1024 stanzas and 1 MiB, an environment at most 1024 recipients, and a secrets file at most 64 MiB. Signatures are checked before any value is decrypted, so a forged ciphertext never reaches the age parser.
age does, with the same plaintext. A weekly run covers 20,000 cases per platform with a fresh seed.age in
both directions.These are tests, not an audit: the implementation has not had an independent security review yet.
git diff shows it after
msecrets git setup); require review for msecrets.json, for example with CODEOWNERS.msecrets revoke lists every value the person could read.Please report security issues privately through GitHub security advisories.