Skip to main content

Module secrets

Module secrets 

Source
Expand description

The one persisted GitHub credential, in a place a boot-time service can read.

07-security.md counts the persisted credential surface and gets to one: “The product now holds exactly one persisted GitHub credential and one short-lived sensitive value.” This module is where that one credential lives. The short-lived one — the encoded JIT configuration — belongs to crate::process::RestrictiveHandoff and is not stored here or anywhere else.

§The constraint that decides the whole design

D13 requires the service to start at machine boot. 05-infrastructure.md: “A boot-time service runs outside any user’s login session and cannot read a per-user keychain on any supported OS: macOS LaunchAgents start only at login, and Windows Credential Manager vaults are per-user.” So the default store is machine-scoped, and every per-user secret facility on all three operating systems is unavailable to it by construction rather than by preference.

OSSecretScope::MachineSecretScope::User
WindowsDPAPI machine scope, in a file under %ProgramData% with its own protected DACLDPAPI user scope, in a file under %LOCALAPPDATA% with its own protected DACL
macOSSystem Keychain (/Library/Keychains/System.keychain)the account’s login keychain
Linux0600 file under /var/lib/runner-manager, plus the systemd credential the service is started with0600 file under $XDG_DATA_HOME/runner-manager

service install --start-at login is the escape hatch 07-security.md promises operators who reject machine-scoped storage: “Operators who reject this can use service install --start-at login and keep a user-scoped store, accepting no unattended restart.” Both columns implement SecretStore, the active one is chosen by SecretScope::for_start_mode, and ActiveStore is what host show and service status print so the choice is inspectable rather than implied.

§The accepted trade-off, implemented honestly

A local administrator or root on this machine can read a machine-scoped secret. 07-security.md records that as an accepted consequence, on the grounds that such an account “can already read the runner’s own credentials and job workspaces”. Nothing here tries to defeat it, and nothing here pretends to. What is defended is the case that is actually in the threat model: an ordinary local user who is not an administrator must not be able to read the stored value, and SecretStore::protection reports whether that holds, per OS, through the one cross-platform name crate::process::permissions_summary already defines.

Delete is delete, not erasure. crate::process::RestrictiveHandoff sets out why no userspace program can promise that the bytes are unrecoverable — a journal, a snapshot, or an SSD’s wear levelling each keep copies an overwrite never reaches — and the same disclaimer applies here. The Linux backend overwrites before unlinking because the value is at rest in plaintext there and the overwrite is free; that is a best effort and is not a claim.

§What never holds this value

SQLite, TOML configuration, logs, diagnostics, UI state, and command-line arguments. The type system carries as much of that as it can: the value crosses this module’s surface only as a SecretString, which has no Display and a redacting Debug, and no error in SecretStoreError carries the value or any part of it. client_id is public by design (07-security.md: “Public by design; may appear in logs and documentation”) and is not a secret this store handles.

§Blocking, not async

DPAPI, Security.framework and open(2) are blocking calls that take microseconds. This runs twice in the life of a process — once at auth login, once at startup — so an async surface would buy nothing and would oblige every caller to be in a runtime to read a file.

§Where a test may point it, and where it may not

PlatformSecretStore::standard resolves the production location in the table above. PlatformSecretStore::rooted_at puts the same backend under a directory the caller names, exactly as crate::paths::AppPaths::rooted_at does for the four application-data directories, and for the same two reasons: a test needs a disposable store, and a service installed against an explicitly configured root has to reproduce one.

No test in this crate writes to a standard location, and that is a deliberate constraint rather than an oversight. Two of the six standard locations need root to create (/var/lib, the System Keychain), and the other four are the operator’s real store — a suite that wrote there would destroy a developer’s auth login every time it ran. So the standard locations are asserted by resolution, and every round trip runs against a rooted store. What that does and does not cover is written out at PlatformSecretStore::rooted_at.

Structs§

ActiveStore
Which store is in use, and whether that is the one the configured start mode obliges.
PlatformSecretStore
The real store: DPAPI on Windows, a keychain on macOS, a 0600 file plus systemd credentials on Linux.
Protection
What actually stands between the stored value and an ordinary local user.

Enums§

Removal
What SecretStore::delete found.
SecretScope
Which of the two stores a value lives in.
SecretStoreError
Something went wrong reaching the secret store.

Constants§

CREDENTIALS_DIRECTORY
The environment variable systemd sets for a unit that was given credentials. Read once, at PlatformSecretStore::standard time.
ROOTED_KEYCHAIN_PASSWORD
The password protecting a keychain created by PlatformSecretStore::rooted_at on macOS.
SYSTEMD_CREDENTIAL
The name of the systemd credential the Linux machine-scoped store reads before it reads its own file.

Traits§

SecretStore
Store, load, delete — and say where the value lives and what protects it.