-
Notifications
You must be signed in to change notification settings - Fork 0
Doctor
keyseal doctor inspects your repository and reports on its health. It is non-destructive - it never writes files or modifies configuration. Exit code is 0 if all checks pass or warn; exit code is 1 if any check fails.
keyseal doctorText output:
doctor summary: 9 ok, 0 warning(s), 0 failure(s), 0 skipped
[ok] keyseal config: keyseal.yaml loaded and valid
[ok] sops config: .sops.yaml loaded; 2 creation rules
[ok] sops creation rules: 2 of 2 rules have usable recipients
[ok] sops placeholder check: no placeholder recipients detected
[ok] repository root: .
[ok] output path safety: file mode 0600 is owner-only
[ok] output paths: no relative output paths detected
[ok] repo artifacts: no unexpected artifacts in repository root
[ok] sops binary: /usr/local/bin/sops (version: 3.8.1)
[ok] secret files: 3 encrypted file(s) discovered
...
For machine-readable output:
keyseal doctor --json{
"summary": {
"ok": 8,
"warn": 1,
"fail": 0,
"skip": 0,
"total": 9
},
"checks": [
{
"name": "keyseal config",
"status": "ok",
"severity": "informational",
"summary": "keyseal.yaml loaded and valid",
"details": ["root: .", "extension: .enc.yaml", "binary: sops"]
},
...
]
}Checks run in a fixed order. Some checks gate later checks - if config fails to load, secret validation is skipped.
What it checks: Whether keyseal.yaml exists in the current directory and loads without errors.
Fail conditions:
-
keyseal.yamldoes not exist - File exists but fails
config.Load(invalid YAML, wrong version, bad regexp inkey_pattern, etc.)
Details on pass: reports the resolved root, encrypted_extension, and sops.binary values.
Remediation:
# if missing:
keyseal init
# if invalid, review the error message from:
keyseal doctorWhat it checks: Whether .sops.yaml exists in the current directory and is parseable.
This check runs independently of the config check - even if keyseal.yaml fails to load, .sops.yaml is still inspected.
Fail conditions:
-
.sops.yamldoes not exist - File exists but cannot be parsed as YAML
Details on pass: reports the number of creation rules found.
What it checks: Whether the SOPS creation rules have usable recipients configured.
Fail conditions:
- No creation rules exist in
.sops.yaml - Rules exist but none have any usable recipient material (no
age,pgp,kms,gcp_kms,azure_keyvault, orhc_vault_transit_urikeys with non-empty content, and nokey_groupswith recipients)
Details on pass: reports how many rules have usable recipients.
What it checks: Whether .sops.yaml contains placeholder text that was not replaced after keyseal init.
The check scans the raw file text for the pattern:
\bage1[A-Za-z0-9_]*REPLACE_ME[A-Za-z0-9_]*\b|\bREPLACE_ME\b
Fail conditions: Any match found. The check reports each unique placeholder string found.
Why this matters: SOPS will accept these as age recipients syntactically but they do not correspond to any real key. Files "encrypted" with placeholder recipients cannot be decrypted by anyone.
Remediation: Replace every placeholder in .sops.yaml with a real age public key:
# generate a new key pair
age-keygen -o ~/.config/sops/age/keys.txt
# the public key is printed; paste it into .sops.yamlWhat it checks: Whether repository.root (from keyseal.yaml) exists as a directory.
Fail conditions:
- The path does not exist
- The path exists but is not a directory
If this check fails, secret discovery is skipped.
What it checks: The defaults.file_mode value for unsafe permissions.
Warn conditions: The mode has group or world bits set (anything other than owner-only). Example: 0644 produces a warning; 0600 passes.
Doctor warns rather than fails - having a non-strict default mode is a configuration choice, not necessarily an error. But render --out will refuse to use an unsafe mode unless --force is passed.
What it checks: Whether defaults.output_dir and any paths in profiles.*.renders[*].out are relative paths or use path traversal.
Fail conditions: Empty path, path traversal (../ prefix after filepath.Clean).
Warn conditions: A path that is valid but relative (relative paths make it easy to accidentally commit rendered output files).
Note: The profiles section is not currently executed by any command, but these paths are still validated so you do not have insecure or surprising defaults when profiles are used.
What it checks: Whether unexpected build artifacts are present in the repository root.
Warn conditions:
- A regular file named
keysealexists in<repository.root>(a compiled binary left in the repo) - A non-empty
dist/directory exists in<repository.root>(release archives left behind)
Neither of these causes a failure - they are hygiene warnings. Compiled binaries and dist archives should not be committed to the repository.
What it checks: Whether the configured SOPS binary is available and executable.
Fail conditions:
- Binary not found on
PATH(or not at the configured path)
Warn conditions:
- Binary is found and runs, but
sops --versionproduces no output
Details on pass: reports the resolved binary path and the version string from sops --version.
If the binary check fails, all decrypt-based secret validation is skipped.
What it checks: Counts the encrypted files discovered under repository.root.
Discovery walks the entire repository tree (skipping .git) and collects all files ending in the configured encrypted_extension.
Pass with zero files: reports "No encrypted secret files were discovered yet" - not a failure, just informational.
Pass with files: reports the count.
Per-file checks follow:
For each discovered file, doctor runs:
-
Logical name mapping: verifies
PathToLogicalNamesucceeds (path maps to a valid logical name that does not escape the repo root) -
Round-trip validation: verifies
LogicalNameToPathof the mapped name produces the original path -
Plaintext detection: if the file parses as a valid
EnvSecretDocumentAND does not contain SOPS metadata, it is flagged asFAIL: appears to be plaintext rather than SOPS-encrypted content -
Decrypt (if SOPS available): calls
sops --decrypt <file>; failure is reported asFAILwith up to 3 lines of SOPS error output - Schema parse: checks decrypted output is valid YAML with the expected document structure
- Validate: checks all fields and key patterns against config validation rules
-
Duplicate key check: if any duplicate keys in
values, reportsWARNlisting the duplicates
If SOPS is not available (binary check failed), decrypt and subsequent steps are skipped and reported as skip.
| Finding | Fix |
|---|---|
keyseal.yaml missing |
Run keyseal init
|
keyseal.yaml invalid |
Check the error message; usually a wrong version, bad file_mode, or invalid regexp |
.sops.yaml missing |
Run keyseal init or create .sops.yaml manually |
| Placeholder recipients | Replace age1REPLACE_ME values in .sops.yaml with real age public keys |
Plaintext at .enc.yaml path |
Re-encrypt: rm <file>; keyseal add <logical-name>, then edit with keyseal edit
|
| SOPS binary not found | Install sops or set sops.binary to the correct path in keyseal.yaml
|
sops decrypt fails |
Check that your age private key is at the expected path and you have access to the key |
| Unsafe file mode | Update defaults.file_mode in keyseal.yaml to "0600" or use --force deliberately |
| Relative output path | Use an absolute path in defaults.output_dir
|
keyseal binary in repo |
Delete it; add /keyseal to .gitignore
|
dist/ in repo |
Delete or move it; add /dist/ to .gitignore
|
# get all failing checks
keyseal doctor --json | jq '.checks[] | select(.status == "fail")'
# count failures
keyseal doctor --json | jq '.summary.fail'
# extract remediation steps for failures
keyseal doctor --json | jq '.checks[] | select(.status == "fail") | .remediation[]'Getting Started
Reference
Operations
Development