Security and credentials
Look up what Canonfig signs and sends between machines, where it stores credentials on each platform, and how to revoke a Follower Machine.
What leaves each machine
| Data | Where it goes | How it travels |
|---|---|---|
| Profile Revisions and the files they name | Source Machine to Follower Machines | HTTPS pinned at enrollment, signed by the Source Machine |
| Shared secrets (opt-in only) | Source Machine to Follower Machines in the canonfig:secrets group | The same pinned HTTPS connection |
| Follower Machine status and run results | Stay on the Follower Machine | Not sent |
Local Overlay keys and local append-local text | Stay on the Follower Machine | Not sent |
credential resource values | Never sent; the profile holds only a reference | Not sent |
| Command log | Stays on the machine | Not sent |
Everything in a profile, including inline content and every source file,
is published to every Follower Machine that selects the profile. Do not put
tokens, passwords, private keys, or cookies in profile content.
Canonfig signs Profile Revisions and pins the connection; it does not encrypt
profile content at rest. Follower Machines write applied files with the modes
the profile declares (0600 by default).
Source keys
canonfig source init creates the Source Machine's signing key, HTTPS private
key, and certificate, and stores them in the native credential store. Anyone
who can read them, or copy ~/.canonfig/state.sqlite together with them, can
publish Profile Revisions your Follower Machines accept. Protect the Source
Machine's user account accordingly.
Each Canonfig state directory has its own key names, so two Source Machines under one OS account do not overwrite each other's keys.
Running canonfig source init again never creates another identity. It checks
that the stored keys match the fingerprints recorded at initialization. If they
do not, Canonfig signs and serves nothing and fails with exit 4:
The native credential store no longer holds this Source's own credentials:
<which>. They were not used, so nothing was signed or served as another Source. This Source's keys cannot be recovered from the store: move this state directory aside, runcanonfig source initto create a new Source identity, then enroll its followers again with new invitations.
Transport trust
canonfig source serve listens on a loopback address only (127.0.0.1 or
::1). A Follower Machine on another host connects through the managed SSH
tunnel, canonfig tunnel start.
Trust is set on first use, at enrollment. The invitation file carries the
Source Machine's HTTPS certificate fingerprint and signing key fingerprint,
and the Follower Machine pins both. Treat the invitation file as a secret:
anyone who holds it before it expires can enroll one machine. It is
single-use, valid for 15 minutes unless you pass --expires (at most 24
hours), and written with mode 0600. Move it over a private channel.
After enrollment, the Follower Machine rejects:
- a different HTTPS certificate;
- a Profile Revision with an invalid signature, or a file whose digest does not match;
- a reused invitation;
- a revoked Follower Identity (exit 5).
The tunnel also pins the SSH host key you pass with --ssh-host-key-file, and
refuses options that turn off host key checking or add forwards.
The Source Machine and each Follower Machine must run the same major.minor
release. A mismatch fails with a "source/follower version mismatch" error
(exit 2), never a signature error.
Follower credentials
Each Follower Machine gets its own credential at enrollment, its Follower Identity, stored in its native credential store. Follower Machines never share a token, so revoking one does not affect the others.
| OS | Credential store |
|---|---|
| Linux | Secret Service (for example GNOME Keyring), when it can be used without a prompt |
| macOS | The login Keychain |
| Windows | Credential Manager |
| Any | A plaintext file with mode 0600, only when you choose it with CANONFIG_LOCAL_CREDENTIAL_ROOT |
Canonfig never falls back to a plaintext file on its own. When no secure store is usable, enrollment fails with Human Action Required, before it uses up the invitation. The store a Follower Machine enrolled with is recorded and used by later runs, including scheduled ones.
Credential lifecycle
| Event | What happens |
|---|---|
| Enrollment fails or you cancel it | The credentials it created are removed on both machines. |
| You enroll a name that is already active | Refused with "the follower identity is already enrolled". The existing credential is unchanged. Revoke the name on the Source Machine first. |
canonfig follower enroll --replace with a different name | The identity under the other name is enrolled first, then the old one is revoked and its local credential deleted. The output lists every local setting that was reset. |
canonfig source revoke <follower-id> on the Source Machine | The Follower Machine is revoked. Its next request fails with exit 5. |
canonfig follower unenroll on the Follower Machine | The identity is revoked on the Source Machine, then the local credential, received shared secrets, and enrollment record are deleted. |
canonfig follower unenroll deletes the local credential even when the Source
Machine is unreachable. The output then shows sourceRevoked: false and the
canonfig source revoke <id> command to run on the Source Machine. Applied
files, the schedule, and the tunnel record stay; remove the last two with:
canonfig schedule remove
canonfig tunnel stop --forgetLocal credential-store failures
When Canonfig cannot read or write this machine's own credential store, the
command fails with exit 3 (Human Action Required), not exit 5, and prints the
store's own recovery text. For example, during canonfig sync:
the follower credential could not be read from this machine's credential store: …
Linux. Canonfig reaches Secret Service through DBUS_SESSION_BUS_ADDRESS,
or the socket at $XDG_RUNTIME_DIR/bus. From a background shell, set
XDG_RUNTIME_DIR=/run/user/$(id -u). If that socket does not exist, no user
session manager is running; sudo loginctl enable-linger <user> starts one.
The login keyring must also be unlocked, which a password login does. A private
dbus-run-session bus does not work. Run canonfig secrets bootstrap to check
the store end to end; see
Headless Linux credential storage.
macOS. Over SSH or in the background, the login Keychain is often locked:
The login Keychain is locked in this SSH or background session. To continue in this session, run
security unlock-keychain ~/Library/Keychains/login.keychain-dband retry; the unlock lasts only for this session and does not carry over to new SSH sessions, the desktop session, or scheduled runs. Scheduled runs need the logged-in desktop session (launchd domain gui/<uid>).
Windows. An OpenSSH session that signed in with a public key may have no
access to Credential Manager. Set up credentials from the signed-in desktop
session. On Windows 10 ARM64, install PowerShell 7 in
%ProgramFiles%\PowerShell\7\pwsh.exe, or set CANONFIG_POWERSHELL to its
path.
Opt-in shared secrets
Canonfig sends secret values to a Follower Machine only when you enroll it with
the canonfig:secrets group:
canonfig source invite --endpoint https://127.0.0.1:17342 --output ./canonfig-invite --group canonfig:secretsSet a secret on the Source Machine through standard input, so the value never appears in the process list or shell history:
printf '%s' "$GITHUB_TOKEN" | canonfig secrets set github-tokenA successful canonfig sync --apply then copies the authorized secrets to the
Follower Machine. canonfig secrets sync retries the copy on its own.
- Values travel only over the pinned HTTPS connection and are written straight
to the Follower Machine's native credential store. The state database,
Profile Revisions, and
canonfig secrets listhold only names and origins. - Canonfig reads every stored value back and compares it before reporting success. If the store returns different bytes, Canonfig deletes the item and fails with exit 3.
- Follower Machines never pass the Source Machine's secrets on to other machines.
- When you remove a secret on the Source Machine, Follower Machines delete their copy on the next apply.
canonfig secrets setand secret transfer require a secure store. A plaintextCANONFIG_LOCAL_CREDENTIAL_ROOTfile is not accepted for shared secrets.
Value limits
- At most 16384 bytes of valid UTF-8, not empty, with no NUL byte.
- Piped input is stored exactly, including a trailing newline. Use
printf '%s', notecho. - At a terminal, input is hidden and the final Enter is not stored.
- A pipe must end within 10 seconds.
Secret ownership
On a Follower Machine, canonfig secrets set refuses to overwrite a name the
Source Machine owns. Change the value on the Source Machine, or pick another
local name.
If another Canonfig process is changing secrets, the command fails with exit 3 before changing anything; a Follower Machine's fetch from a busy Source Machine fails with exit 6. Wait for the other process to finish and retry.
Secrets in output and logs
Canonfig removes configured secret values from captured command arguments, output, and JSON results before it stores or prints them. Treat this as a safety net: keep secrets off command lines and out of profiles in the first place.
The command log, ~/.canonfig/canonfig.log, records only the command name,
time, process ID, duration, and exit code. It never records arguments, output,
invitations, or secret values. It is readable only by your user (mode 0600,
or a user-only ACL on Windows). Set CANONFIG_LOG=off to turn it off, or
CANONFIG_LOG_FILE to write it elsewhere.
CANONFIG_DEBUG=1 adds error details to the output of an internal failure
(exit 1). Read that text before sharing it; it can include paths.
Agent limits
A Configuration Agent runs only inside the paths, programs, HTTPS origins, and
capabilities you allow with canonfig agent harness. Canonfig starts programs
directly, without a shell. An agent's report never counts as success; the
resource's own verification does. See
Let a Configuration Agent resolve work.
Audit a machine
canonfig doctor --no-input
canonfig status --json
canonfig secrets list --jsondoctor checks the credential store and the connection to the Source
Machine, status shows the applied Profile Revision and the last run, and
secrets list shows secret names and where each came from.