docs: add security audits and Plan 4 for blocker fixes
- 2026-04-18 initial audit verification (all fixed except H8) - 2026-05-01 audit with 8 new findings (B1-B4, I1-I6) - Plan 4: Security Blocker Fixes implementation plan Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
113
docs/superpowers/audits/2026-05-01-security-audit-opus-4-5.md
Normal file
113
docs/superpowers/audits/2026-05-01-security-audit-opus-4-5.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Verification: 2026-05-01 Security Audit
|
||||
|
||||
**Verified by:** Claude Opus 4.5
|
||||
**Date:** 2026-05-01
|
||||
**Methodology:** Code inspection of referenced file paths and line numbers
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Finding | File Exists | Lines Accurate | Vulnerability Real | Confidence |
|
||||
|---------|-------------|----------------|-------------------|------------|
|
||||
| 1 - Backup KDF NFC | ✅ | ✅ | ✅ | 10/10 |
|
||||
| 2 - Commit injection | ✅ | ✅ | ✅ | 10/10 |
|
||||
| 3 - WASM private key exposure | ✅ | ✅ | ✅ | 10/10 |
|
||||
| 4 - Test env vars in prod | ✅ | ✅ | ✅ | 10/10 |
|
||||
| 5 - AttachmentId 64-bit | ✅ | ⚠️ off-by-1 | ✅ | 9/10 |
|
||||
| 6 - Field history plaintext | ✅ | ✅ | ✅ | 10/10 |
|
||||
| 7 - Device keys non-functional | ✅ | ✅ | ✅ | 10/10 |
|
||||
| 8 - Path traversal restore | ✅ | ✅ | ✅ | 10/10 |
|
||||
|
||||
**Verdict:** All 8 findings are verified as real vulnerabilities in the current codebase.
|
||||
|
||||
---
|
||||
|
||||
## Finding-by-Finding Verification
|
||||
|
||||
### Finding 1 — Backup KDF missing NFC normalization
|
||||
|
||||
- **File:** `crates/relicario-core/src/backup.rs`
|
||||
- **Claimed lines:** 303-312
|
||||
- **Verified:** ✅ `derive_backup_key` at lines 303-312 passes `passphrase` directly to `argon.hash_password_into()` without NFC normalization. Compare to `derive_master_key` in `crypto.rs:224-227` which explicitly normalizes.
|
||||
- **Impact confirmed:** Cross-platform restore failure for non-ASCII passphrases.
|
||||
|
||||
### Finding 2 — Commit message injection via item titles
|
||||
|
||||
- **File:** `crates/relicario-cli/src/main.rs`
|
||||
- **Claimed lines:** 565, 899-901, 1110, 1327
|
||||
- **Verified:** ✅
|
||||
- Line 565: `format!("add: {} ({})", item.title, item.id.as_str())`
|
||||
- Line 1110: `format!("edit: {} ({})", item.title, item.id.as_str())`
|
||||
- Line 1327: `format!("trash: {} ({})", item.title, item.id.as_str())`
|
||||
- **Impact confirmed:** Newlines/control chars in titles corrupt git log output.
|
||||
|
||||
### Finding 3 — WASM `generate_device_keypair` crosses private key to JS
|
||||
|
||||
- **File:** `crates/relicario-wasm/src/lib.rs`
|
||||
- **Claimed lines:** 215-227
|
||||
- **Verified:** ✅ Function returns `{ "private_key_base64": "..." }` as `JsValue`, exposing ed25519 private key to JavaScript heap.
|
||||
- **Impact confirmed:** Key material accessible to any JS in service worker context.
|
||||
|
||||
### Finding 4 — Test env vars ship in production binary
|
||||
|
||||
- **File:** `crates/relicario-cli/src/main.rs`
|
||||
- **Claimed lines:** 445-446, 421-423, 1425-1426
|
||||
- **Verified:** ✅
|
||||
- Lines 421-423: `RELICARIO_TEST_ITEM_SECRET`
|
||||
- Lines 445-446: `RELICARIO_TEST_PASSPHRASE`
|
||||
- Lines 1425-1426: `RELICARIO_TEST_BACKUP_PASSPHRASE`
|
||||
- **Impact confirmed:** All checked in production code without `#[cfg(test)]`. Passphrase visible in `/proc/<pid>/environ`.
|
||||
|
||||
### Finding 5 — `AttachmentId` truncated to 64 bits
|
||||
|
||||
- **File:** `crates/relicario-core/src/ids.rs`
|
||||
- **Claimed lines:** 52-57
|
||||
- **Actual lines:** 51-56 (off by 1)
|
||||
- **Verified:** ✅ `&digest[..8]` = 8 bytes = 64 bits. Birthday collision at ~2³² work.
|
||||
- **Impact confirmed:** Attacker with attachment upload can cause silent overwrites.
|
||||
|
||||
### Finding 6 — `get_field_history` returns plaintext to JS
|
||||
|
||||
- **File:** `crates/relicario-wasm/src/lib.rs`
|
||||
- **Claimed lines:** 232-265
|
||||
- **Verified:** ✅ Returns historical `Password`/`Concealed` values as plaintext JSON via `v.as_str().to_owned()`.
|
||||
- **Impact confirmed:** Password history exposed to JS heap without Zeroizing.
|
||||
|
||||
### Finding 7 — Device key system is security theater
|
||||
|
||||
- **File:** `crates/relicario-cli/src/main.rs`
|
||||
- **Claimed lines:** 2151-2221
|
||||
- **Verified:** ✅ `cmd_device()` handles Add/List/Revoke but:
|
||||
- No `sign_commit` or `verify_signature` functions exist anywhere
|
||||
- `devices.json` is plaintext and unauthenticated
|
||||
- Revocation has no enforcement mechanism
|
||||
- **Impact confirmed:** Users falsely believe device revocation provides security.
|
||||
|
||||
### Finding 8 — Path traversal on backup restore
|
||||
|
||||
- **File:** `crates/relicario-cli/src/main.rs`
|
||||
- **Claimed lines:** 1619-1626
|
||||
- **Verified:** ✅
|
||||
```rust
|
||||
for item in &unpacked.items {
|
||||
fs::write(target.join("items").join(format!("{}.enc", item.id)), ...)?;
|
||||
}
|
||||
```
|
||||
`item.id` and `attachment_id` used directly in path construction with no validation.
|
||||
- **Impact confirmed:** Crafted `.relbak` with `id = "../../.bashrc"` escapes target directory.
|
||||
|
||||
---
|
||||
|
||||
## Blockers Assessment
|
||||
|
||||
The audit's "Path to Certifiable Safety" section is accurate:
|
||||
|
||||
| Blocker | Verified | Severity |
|
||||
|---------|----------|----------|
|
||||
| B1 - Device key theater | ✅ Real | High |
|
||||
| B2 - Backup KDF NFC | ✅ Real | Medium |
|
||||
| B3 - Test env vars | ✅ Real | Medium |
|
||||
| B4 - Path traversal | ✅ Real | Medium |
|
||||
|
||||
All four blockers are confirmed. B1 is the most dangerous as it misleads users about their security posture.
|
||||
Reference in New Issue
Block a user