Files
incredigo/docs/SECURITY.md
T
leetcrypt 425c299359 Initial commit: Incredigo — local-first RAM-only credential custody
v1: discover → migrate → expiry tracking → sealed export/import.

- vault: memguard mlock/DONTDUMP arena, handle indirection, zeroize-on-drop
- discover: registry + 8 read-only scanners (aws, env, netrc, ssh, docker,
  kube, git) and a file/dir harvester (--path)
- sink: gopass streaming insert; length-prefixed bundle framing; Sealer
  interface with three impls — age (default, authenticated), hmac
  (authenticated, openssl-only encrypt-then-MAC), openssl (CBC fallback,
  unauthenticated; OpenSSL 3.x enc refuses AEAD)
- policy: local expiry engine, 60d/8w threshold parser
- audit: redacted append-only JSONL, injectable clock
- export/import: passphrase from no-echo prompt or $INCREDIGO_PASSPHRASE into
  locked memory; secrets stream gopass<->Sealer as bytes, never Go strings
- tests: scanner leak-checks, vault zeroize, bundle round-trip via fake gopass;
  go test ./... green, -race clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 07:57:34 -07:00

2.8 KiB

Security model

incredigo's job is to minimize the footprint and lifetime of plaintext secrets. It is not a substitute for a trustworthy machine.

Guarantees

Threat Mitigation
Plaintext leaks to disk Never written. gopass GPG blobs or sealed bundles (age/hmac/openssl) only; no temp files.
Plaintext leaks via swap mlock on every secret buffer (memguard).
Plaintext via core dump / process snapshot MADV_DONTDUMP, guard pages, zeroize on drop; memguard.CatchInterrupt.
Source files corrupted by the tool Scanners are strictly read-only (O_RDONLY).
Old cred destroyed before replacement verified v1 deletes nothing. (v2 rotation verifies-new before revoke-old.)
Secrets in logs Audit log records only redacted metadata; identities are pre-redacted by scanners.
Secrets in ps/argv Secrets are piped via stdin (gopass); the backup passphrase reaches openssl over an inherited fd (-pass fd:3). Never argv, never env.
Network exfiltration No network calls in v1.

Honest limits

  • incredigo cannot protect plaintext from a compromised OS/kernel or a malicious gpg-agent while it runs — those already see your secrets in normal use.
  • The sealed bundle's strength is its passphrase. Use a strong one; the KDF is scrypt (age, default) or PBKDF2 @ 600k iterations (hmac/openssl), but a weak passphrase is still a weak passphrase.
  • Go's runtime can in principle copy bytes during GC compaction. memguard buffers live outside the Go heap to avoid this; do not copy secret bytes into Go strings or heap []byte that outlive immediate use. One documented exception: the age sealer's library API takes the passphrase as a string (the hmac sealer does not). The vaulted secrets themselves never become strings.

Restoring a bundle by hand

If incredigo itself is unavailable, a bundle is recoverable with standard tools. The decrypted stream is the length-prefixed record format in internal/sink/bundle.go (uint16 pathLen | path | uint32 chunkLen | chunk | …).

age (default):

age -d backup.incredigo.age > bundle.bin      # prompts for the passphrase

hmac (--sealer hmac) — verify integrity before decrypting:

head -c 32 backup.incredigo.age > mac.bin     # bundle = HMAC-SHA256(32) || ciphertext
tail -c +33 backup.incredigo.age > ct.bin
openssl dgst -sha256 -hmac "$PASS" -binary ct.bin | cmp - mac.bin   # must match
openssl enc -d -aes-256-ctr -pbkdf2 -iter 600000 -pass pass:"$PASS" -in ct.bin > bundle.bin

openssl (--sealer openssl, unauthenticated):

openssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 -in backup.incredigo.age > bundle.bin

Reporting

Report vulnerabilities privately to the maintainer (do not open a public issue).