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>
62 lines
2.8 KiB
Markdown
62 lines
2.8 KiB
Markdown
# 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
|
|
`string`s 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):**
|
|
|
|
```sh
|
|
age -d backup.incredigo.age > bundle.bin # prompts for the passphrase
|
|
```
|
|
|
|
**hmac (`--sealer hmac`)** — verify integrity *before* decrypting:
|
|
|
|
```sh
|
|
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):**
|
|
|
|
```sh
|
|
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).
|