425c299359
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>
2.8 KiB
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[]bytethat outlive immediate use. One documented exception: theagesealer's library API takes the passphrase as astring(thehmacsealer 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).