cfgit records who changed a versioned record, when it happened, and why. The trust level of that "who" is explicit and configured per environment.
The short form is:
open: self-asserted author, useful for cooperative teams.authenticated: cfgit verifies who used cfgit, but direct DB writes may still happen and are handled by drift detection.enforced: cfgit verifies identity and the database write credentials are locked down outside cfgit so cfgit or CI is the only writer.
cfgit cannot prevent a direct DB write by code alone. Prevention is always
database-side credentials and RBAC. cfgit detects bypass with status, shows it
with diff, and records it with adopt.
Open mode is the default and remains a first-class mode. Users can pass
--author, set CFG_AUTHOR, rely on git config user.email, or fall back to
the OS username.
This is attribution, not authentication. It is right for local and dev workflows where the team is cooperative and drift detection is the safety net.
[env.dev.identity]
mode = "open"Authenticated mode requires cfgit to verify identity before mutating history. It does not take DB write access away; it makes cfgit's own trail trustworthy. Bypass can still happen, and cfgit still detects it as drift.
[env.staging.identity]
mode = "authenticated"
sources = ["token", "db_principal"]Enforced mode is the cfgit-side identity posture for production-style setups where direct database writes are also blocked by DB credentials. The database must be configured so humans and ad-hoc scripts do not hold write credentials for the versioned records.
[env.prod.identity]
mode = "enforced"
sources = ["token"]enforced does not magically close direct DB doors. It only becomes real when
the database is locked down to a cfgit service identity or CI identity.
Token identity is built for private human strings:
export CFGIT_IDENTITY_TOKEN='imkanyewest'cfgit hashes the full string with SHA-256 and compares it to configured hashes. The raw token is never stored in cfgit history. The visible 4-12 character fingerprint is only for humans to distinguish identities; it is never accepted as proof.
Generate a hash without putting the token in shell history:
printf '%s' 'imkanyewest' | cfg identity-hash --stdinThen configure the full hash:
[env.prod.identity]
mode = "authenticated"
sources = ["token"]
token_env = "CFGIT_IDENTITY_TOKEN"
fingerprint_chars = 5
tokens = [
{ author = "alice@example.com", name = "alice-main", sha256 = "sha256:..." },
]Use memorable tokens only when the config containing hashes is private enough for your risk level. If hashes are public, short or guessable phrases can be attacked offline. Prefer longer private phrases for production.
A common question: if CFGIT_IDENTITY_TOKEN lives only in the user's local
environment and is never stored centrally, what is it actually protecting?
It works like a password checked against a stored hash — but with no auth server and no session:
- Setup, once. The user picks a private string. You hash it locally
(
cfg identity-hash) and put only the hash in.cfg.toml. The raw string is never written into cfgit — not in the config, not in history, not in logs. - At runtime. The user exports the raw string as
CFGIT_IDENTITY_TOKEN. - On every command. cfgit hashes the env-var value right then and compares it to the configured hashes. A match verifies the author; a mismatch is rejected. There is no login step and no session — it re-verifies per command, statelessly.
So it is deliberately decentralized: the hash is shared (in the config), the raw token stays local, and cfgit never has to hold a secret or phone home to a server. Anyone can read Alice's hash; that does not let them act as Alice.
What this buys you, and what it doesn't:
- It is attribution / accountability, not a hard security boundary. It proves "this caller knows Alice's token," which makes cfgit's history trail hard to spoof casually. It does not stop someone who steals Alice's raw token from her environment, and public hashes over short phrases can be brute-forced offline (hence: long, private phrases for production).
- For a real security boundary, prefer
db_principalidentity (below): the database itself authenticates the connection, and cfgit maps that verified DB user to an author — no shared secret to leak. Combine withenforcedmode and locked-down DB write credentials so cfgit/CI is the only writer.
db_principal uses the authenticated database connection identity:
- Postgres returns
current_user. - Mongo uses
connectionStatusauthenticated users, or the URI username when that is all the driver can expose.
Map database principals to author names when needed:
[env.prod.identity]
mode = "authenticated"
sources = ["db_principal"]
principal_map = { "alice_db" = "alice@example.com" }This is often the cleanest route when each person already has their own DB credential.
[env.<name>.permissions] still controls what a resolved identity can do:
[env.prod.permissions]
mode = "restricted"
admins = ["owner@example.com"]
writers = ["*@example.com"]
admin_actions = ["init", "restore_system"]In open identity mode, role checks match the self-asserted author string. This
is a guardrail.
In authenticated or enforced identity mode, role checks use the verified
identity. If --author does not match the token or DB principal identity, cfgit
refuses the operation.
Every new history entry includes:
{
"author": "alice@example.com",
"meta": {
"identity": {
"mode": "authenticated",
"author": "alice@example.com",
"source": "token",
"authenticated": true,
"fingerprint": "abc12",
"principal": "alice-main",
"credential": "alice-main"
}
}
}The top-level author stays simple for logs and compatibility. The nested
identity object tells you how trustworthy that author is.