Discern, then decide — before you sign. Local key-material hygiene and readiness checks for humans and agents.
decernor inspects local signing and authentication material — GPG, minisign, and
SSH keys, revocation certificates, public counterparts, and checksum manifests — and
decides what is plaintext, what is protected, what is public, and what to remove,
retain, or inspect. It reads the state, never the secret.
Name note:
decernor(from Latin decernere, "to decide / determine / decree"; root cernere, "to sift, distinguish, discern"). Pronounced deh-SUR-nor. SeeNAMING.mdfor the decision record.
This tool helps humans and AI agents inspect local key-material hygiene without directly reading, copying, or disclosing private key contents.
Modern release and automation workflows depend on local signing and authentication assets: GPG keys, minisign keys, SSH keys, revocation certificates, public counterparts, and checksum/signature manifests. Those assets are powerful and easy to mishandle. A human operator may need to know whether a machine is clean enough, whether a secure archive contains the right encrypted materials, or whether an automation session can proceed without exposing secrets to a remote agent.
The core idea is simple:
Trusted local code inspects sensitive local state and emits structured findings. Humans and agents consume the findings, not the private key material.
The tool does not make a software-held key magically safe. It distinguishes between plaintext private material, encrypted/passphrase-protected material, public material, operationally sensitive artifacts such as revocation certificates, and copied keyring internals. It reports whether something should be removed, retained with controls, or inspected manually.
This is a general OSS trust utility, homed in the 3leaps collection org.
It is adjacent to tools like:
sfetch: verifies downloaded release artifacts before execution.shellsentry: inspects shell scripts before execution.decernor(this tool): inspects local key-material state before humans or agents act on it.
AI-assisted workflows are an important use case: agents can use this tool to avoid asking users to paste, upload, or describe private key files in a live session. Decernor uses Fulmen microtool foundations for config, schema validation, structured logging, identity, and release ceremony patterns.
- Release engineers preparing signed OSS releases.
- Operators creating or archiving GPG, minisign, and SSH assets.
- Less experienced users who need plain guidance on whether local key files are plaintext, encrypted, or public-only.
- AI agents that need to reason about key readiness without reading key contents.
- Enterprise teams that need policy-shaped output for local machine cleansing, secure handoff, and release signing readiness.
scan answers:
What risky or sensitive key material is present here?
Examples:
go run ./cmd/decernor scan /path/to/artifacts
go run ./cmd/decernor scan /path/to/artifacts --format json
go run ./cmd/decernor scan /path/to/artifacts --profile workstation
go run ./cmd/decernor scan /path/to/artifacts --fail-on warn
go run ./cmd/decernor scan /path/to/artifacts --allow-protected-secret-keys
go run ./cmd/decernor --log-level info scan /path/to/artifacts --format jsonScan reports are written to stdout. Operational logs are written to stderr when enabled with --log-level info, so CI can safely pipe JSON reports without log contamination.
guardread answers:
Can this one file be read to stdout without first exposing supported key-material bytes?
Example:
go run ./cmd/decernor guardread ./notes.txtThe command accepts one named regular file. It rejects symlinks, directories, special files, oversized files, binary/ambiguous input, and supported key-material detections before writing any file byte to stdout. On pass, stdout is the file content only. On refusal, stdout is empty and sanitized diagnostics are written to stderr with exit code 3. Input and usage errors exit 2.
guardread is not a prompt-injection filter or a general content-safety system.
It protects the supported detector classes Decernor can recognize; arbitrary
prose still needs whatever higher-level review applies to that workflow.
readiness answers:
Do I have enough usable, protected material for a capability?
Capabilities use a provider plus verb model:
gpg/signgpg/encryptminisign/signssh/auth
The current cut includes schema-backed config validation:
go run ./cmd/decernor readiness validate-config examples/github-org-bootstrap.readiness.jsonfingerprint answers:
What safe public identity fingerprints can be emitted for this key material?
Examples:
go run ./cmd/decernor fingerprint /path/to/artifacts
go run ./cmd/decernor fp /path/to/artifacts --kind ssh,minisign
go run ./cmd/decernor fingerprint /path/to/artifacts --format json
go run ./cmd/decernor fingerprint /path/to/artifacts --fail-on-empty
go run ./cmd/decernor fingerprint ./release.gpg.asc --class public --kind gpg \
--format json --path-mode none --gpg-role primaryThe default format is newline-delimited JSON. --format json emits an array of
the same records. Fingerprint records are written to stdout; diagnostics remain on
stderr. Output paths default to --path-mode relative, which emits paths relative
to the input root. Use --path-mode hash for artifacts leaving a repository
boundary, or --path-mode none to omit path metadata. The command does not
traverse symlinks or consult ambient keyrings, agents, home directories, hardware
tokens, or the network.
Records use schema_version:"v0" and the schema in
schemas/fingerprint-record.v0.schema.json. Config files use
schemas/fingerprint-config.v0.schema.json.
To compare a committed anchor pair with exported public key files:
decernor fingerprint verify --anchors keys/expected-fingerprints.txt \
--anchors-ndjson keys/expected-fingerprints.ndjson \
--gpg exported-public.asc --minisign exported.pubThe verifier accepts named regular files only. Materialize piped input first;
/dev/stdin and process substitution are refused. It checks the pair against
the embedded fingerprint schema, then compares the GPG primary and minisign
public-blob digest with the public files. Results are NDJSON by default or a
JSON array with --format json. They contain status and validity, without
fingerprints or paths. --as-of takes an RFC3339 time for repeatable expiry
evaluation. Expiry is fatal unless --allow-expired applies to an otherwise
matching pair; primary-key revocation present in the supplied file is fatal.
Revoked subkeys do not change the primary result. Exits are 0
for a match, 2 for input/helper failure, 3 for unsafe or ambiguous input, 4
for an invalid pair, 5 for mismatch, and 6 for fatal validity. The verification
contract defines
the full result and time semantics. A file literally named verify is passed
to the existing fingerprint command as ./verify.
GPG verification requires gpg on PATH; if it is missing, the command
exits 2. The helper runs GPG in an isolated temporary home without using
your keyring or agent, and makes no network calls.
For minisign public keys, fingerprint emits both the native
minisign-key-id-v1 identifier and the collision-resistant
minisign-public-blob-sha256-v1 fingerprint (lowercase 64-hex in the
record). Use the public-blob SHA-256 field for committed trust anchors; the
key ID remains useful for display and operator correlation. A GPG public
export uses --gpg-role primary when the caller needs exactly one primary
fingerprint.
Future readiness checks should support two levels:
- Static readiness: key material exists, is not plaintext, has expected public counterpart, revocation certificate is present where required.
- Proof readiness: local-only proof such as sign-and-verify, encrypt-and-decrypt, or derive public key. Proof mode may prompt for passphrases, but must not transmit key material.
Findings are structured for both humans and automation.
Fields include:
code: stable finding identifier, suitable for runbooks and allow/deny policy.priority: remediation priority fromP0toP5.rank: numeric sort rank derived from priority.classification: artifact category, such asprotected-secret,ssh-private-key, orminisign-secret.severity: current policy severity:info,warn, orunsafe.retention:allowed,retain-with-controls,inspect-manually, orremove.exposure:public,sensitive,secret, orunknown.sensitivity: 3 Leaps classifier value embedded in the scanner model.confidence:high,medium, orlow.evidence: short explanation without printing private material.recommendation: next action.
Example:
{
"code": "MINISIGN-ENCRYPTED-SECRET",
"priority": "P3",
"rank": 300,
"classification": "minisign-secret",
"severity": "warn",
"retention": "retain-with-controls",
"exposure": "secret",
"sensitivity": "5-privileged",
"confidence": "high",
"evidence": "encrypted minisign secret material detected",
"recommendation": "Potentially retainable with strong passphrase and local controls; keep out of artifact bundles unless policy allows it."
}- ASCII armor is encoding, not protection.
- Public keys and signatures are not private, but may still be operationally relevant.
- Passphrase-protected software keys are sensitive and potentially retainable with controls.
- Plaintext private keys are unsafe.
- Copied keyring internals are suspicious outside their expected home.
- Revocation certificates are operationally sensitive and should be archived intentionally.
- Hardware-backed keys should be preferred or required where policy calls for that.
- The tool should not decrypt user data, collect passphrases, or print private key material.
Decernor dogfoods the same trust practices it encourages. It fingerprints its own release-signing publics and pins those values into the signed set. The assets this tool scans are the same class of assets used to sign its releases.
A cut is two phases:
- The maintainer creates and verifies a signed
vX.Y.Ztag. CI packages unsigned archives and opens a GitHub release. - On an operator host,
make releasedownloads those archives, stages committed fingerprint pins and notes, checksums, signs the SUMS (minisign required; PGP optional), exports publics, verifies, and uploads onto the same release.
The signed payload is archives + notes +
expected-fingerprints.{txt,ndjson}. Exported .pub / .asc files ride
beside it; they do not vouch for themselves. Pins are generated by
decernor fingerprint on those exported publics
(make release-insert-anchors), never hand-typed. Bindings are
environment-variable identifiers only.
Reviewers:
- Obtain an independently trusted committed anchor pair and an already-trusted Decernor installation.
- Download archives, signed SUMS, exported publics, and the staged pin pair.
- Run
decernor fingerprint verifyagainst the trusted pair and downloaded public exports (see below). - Use the verified public exports to verify the signatures over the checksum manifests, then verify archive checksums. The staged pin files must be members of SUMS and match the trusted pair.
- Run the tool locally and consume structured findings, not key files.
Layout and signed-set membership:
docs/decisions/PDR-0001-committed-signing-anchors.md.
Inserter: keys/README.md.
Consume fingerprints, not secrets. Per-cut commands live in
docs/releases/v0.1.8.md.
Start with an independently trusted anchor pair, such as pins already committed in your consuming repository, and an already-trusted Decernor installation. For first use, establish the pins out of band, for example from a signed tag whose signature you checked with a key you already trust. Download the release assets (archives, signed SUMS, exported publics, staged pin pair). First compare the downloaded public exports with your trusted pins:
decernor fingerprint verify \
--anchors keys/expected-fingerprints.txt \
--anchors-ndjson keys/expected-fingerprints.ndjson \
--gpg decernor-release-signing-key.asc \
--minisign decernor-minisign.pubThen use the verified exports to check SUMS signatures, check the manifest
digests, and compare the staged anchor files with your trusted pair. The
downloaded anchors and keys cannot establish trust in each other. Do not use
the binary from this release to verify its own signing keys. fingerprint verify compares public files with anchors; it does not verify signatures.
For manual comparison against the trusted pair, print the two public fingerprints:
decernor fingerprint decernor-release-signing-key.asc \
--class public --kind gpg --format json --path-mode none --gpg-role primary
decernor fingerprint decernor-minisign.pub \
--class public --kind minisign --format json --path-mode noneThe GPG primary fingerprint and the minisign public-blob SHA-256 must
match the gpg and minisign lines in your trusted
keys/expected-fingerprints.txt.
Never hand-type hex into notes or a README.
go build ./...
make build
./bin/decernor versionInstall Decernor from the 3 Leaps package repositories:
brew install 3leaps/tap/decernorscoop bucket add 3leaps https://github.com/3leaps/scoop-bucket
scoop install decernorPackage managers are a convenience distribution path. For release provenance, continue to verify the signed GitHub release assets and fingerprint pins.
Readiness configs describe what capabilities an asset set must support.
The schema is in schemas/readiness-config.v0.schema.json. Example configs live in examples/.
Example capability:
{
"id": "gpg-release-sign",
"provider": "gpg",
"verb": "sign",
"accepted_material": ["protected-secret-key", "hardware-backed-key"],
"require_public_counterpart": true,
"require_revocation_certificate": true,
"static_checks": [
"material-present",
"not-plaintext",
"public-counterpart",
"revocation-present"
],
"proof_checks": ["sign-and-verify"]
}decernor was selected to suggest local trust, key-material hygiene, and readiness
before signing/authentication — without sounding like malware, spyware, a password
manager, a generic cleaner, or a destructive wipe tool, and without implying the tool
stores secrets or guarantees safety.
- Etymology: Latin decernere — "to decide, determine, decree, resolve" — on the root cernere ("to sift, distinguish, discern"). The tool discerns key-material state, then decides the verdict: remove, retain-with-controls, inspect, or allowed.
- Tagline shapes:
- Discern, then decide — before you sign. (CLI / repo subtitle)
- Know what's safe to keep — without reading the secret. (landing)
- ASCII armor is encoding, not protection. Decernor tells the difference. (technical overview)
- Surfaces: binary
decernor, config path~/.config/3leaps/decernor.yaml, env prefixDECERNOR_, default configdecernor.yaml. Finding codes and readiness-config shapes are name-agnostic and unchanged.
Full decision record, surfaces grid, and the considered-but-passed runner-up (cernor)
are in NAMING.md.
The repository avoids committed real keys and avoids full static secret-key fixtures. Tests generate small synthetic files at runtime and mock packet output so downstream secret scanners have less static material to flag.
Decernor was initially built from the public Fulmen microtool forge baseline forge-microtool-gimlet, then adapted as a 3 Leaps OSS tool.
This project is licensed under the MIT License. See LICENSE.