Skip to content

Security Notes

Jake Paine edited this page May 23, 2026 · 3 revisions

Security Notes

What Keyseal actually protects

The add workflow ensures secrets reach the repo as ciphertext - the starter document is encrypted via SOPS before being written to the final path. Schema validation catches malformed documents early. Doctor detects unencrypted files at .enc.yaml paths before they get committed.

That is the scope. Everything else is on you and on SOPS.

What Keyseal does not protect

Access control. There is no concept of permissions, roles, or users inside Keyseal. Anyone with the age private key can decrypt all files encrypted with that key. If you need to restrict access to specific secrets per environment or service, that is a key management problem - manage it at the age key level, not at the Keyseal level.

Secret rotation. Keyseal does not detect or enforce expiry. It does not know whether a credential is old or compromised.

Audit trail. Keyseal does not log who decrypted what or when. Git history records who committed changes to ciphertext, but not who decrypted it.

Protection from the operating system. If someone has access to your machine and your age private key, they can decrypt your secrets. Keyseal does not add a layer on top of OS-level access.

SOPS handles the cryptography

Keyseal does not reimplement SOPS cryptography. Read-only decryption is performed through the official SOPS Go decrypt library. Mutating operations still shell out to the SOPS CLI:

  • sops encrypt --filename-override <target> <tempfile> for keyseal add
  • SOPS Go decrypt library for render, exec, doctor, and verify
  • sops <file> for edit
  • sops updatekeys for updatekeys

The security properties of your encrypted secrets are entirely determined by how SOPS is configured and what keys you use. Keyseal does not change or validate SOPS's encryption choices. A production server with Keyseal, encrypted files, and the age private key can decrypt secrets even without external sops or age binaries. Servers need the age key, not the age CLI.

The temporary plaintext file during add

When keyseal add encrypts a new secret, it writes the plaintext starter document to a temporary file (keyseal-plaintext-*.yaml) in the OS temp directory, with mode 0600. This is the only time plaintext exists on disk during the add flow. The file is removed via defer os.Remove(tempPath) immediately after SOPS reads it.

On most systems os.TempDir() is /tmp, which is on a local filesystem. On Linux with tmpfs, this is in memory. On macOS it is typically under /var/folders/... on disk.

If the process is killed between writing the temp file and removing it, the plaintext temp file may persist. It will be world-accessible by root and readable only by the owning user (mode 0600). The file name includes a random suffix. This is a narrow window but it is worth knowing about.

Rendered output files

keyseal render --out <path> writes decrypted secret values to a file. Once that file exists, it is a plaintext copy of your secrets. The default mode is 0600 (owner-readable only). Doctor warns if the configured default mode has group or world bits.

Rendered files should not be committed to the repository. Keep them in /run/secrets/ or equivalent memory-backed paths on production systems. Avoid writing them to directories that are synced, backed up, or otherwise persisted beyond the lifetime of the process that needs them.

exec does not write files

keyseal exec injects secrets as environment variables into a subprocess. The values exist in the subprocess's environment but are not written to any file. On Linux, /proc/<pid>/environ exposes a process's environment to root and to the process owner - this is a standard OS behavior, not a Keyseal-specific exposure.

Repo hygiene

Git history is permanent. If a plaintext secret is ever committed - even in a commit that is later reverted - it remains in the repository's history. Doctor's plaintext detection (schema.ParseYAMLDocument succeeds + no SOPS metadata) helps catch this before it happens, but it only works for files currently on disk. It does not scan Git history.

keyseal rollback restores a historical encrypted file into the working tree, but it does not rewrite history or remove a bad commit from the repository. If the problem was a plaintext commit, rollback is not enough on its own.

If you suspect a plaintext commit has occurred, rotate the credentials, re-encrypt with new values, and consider whether a history rewrite is necessary for your threat model.

Doctor also checks that placeholder recipients are not used in .sops.yaml. Encrypting with placeholder recipients produces ciphertext that cannot be decrypted - the data is effectively lost, not protected.

Plaintext mode in render

render --out with --force allows writing to a file with permissive modes (e.g. 0644). This is an override for environments where group-readable secrets are acceptable (e.g. a container with a shared group). Use it deliberately. The check exists because group-readable rendered files are a common misconfiguration.

Scope

Keyseal is appropriate for teams that want structured, encrypted, version-controlled secrets with no additional infrastructure. If your requirements include access control, dynamic credentials, audit logs, or secret rotation, a dedicated secrets platform is the right choice. Those systems solve different problems.

Clone this wiki locally