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