Files
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

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).