# Svault 0.5.0 — Independent Security Review (Gemini-3.5-Flash)

**Project**: Svault (nim444/Svault)
**Version reviewed**: 0.5.0 (from Cargo.toml)
**Review date**: May 29, 2026
**Reviewed by**: Gemini 3.5 Flash (Google/OpenRouter)
**Review location**: Cloned locally at `/Users/nk/Projects/nk-os/repos/svault`

---

## Executive Summary

**Overall risk rating for corporate use**: **Moderate — Conditionally acceptable for development environments on Unix with the daemon enabled; highly unsuitable for production servers, shared CI/CD runners, or Windows/non-Unix environments.**

Svault 0.5.0 implements a strong cryptographic foundation, featuring AES-256-GCM, Argon2id key derivation, HMAC-SHA256 metadata signing with constant-time verification, and a policy engine that handles structured agent requests with rate-limiting and burst-detection. 

The headline feature of version 0.5.0—the Unix daemon—is a major security milestone. It transitions the application from a highly vulnerable "passphrase-on-disk" model to an "in-memory only" derived key architecture. With robust idle and hard-max auto-lock timers, the exposure window of secrets is significantly minimized.

However, several critical limitations prevent recommendation for enterprise production or multi-tenant environments:
1. **Lack of Socket Authentication**: Any process running under the same UID (or root) can query the Unix socket with zero credentials, reading all unlocked secrets.
2. **Cleartext Passphrase Transit**: Unlocking transmits the raw master passphrase in plaintext JSON over the local socket.
3. **Insecure Windows Fallback**: On non-Unix systems, the lack of daemon support means the tool falls back to caching the raw passphrase on disk in plaintext with inadequate permission controls.

---

## Scope & Methodology

- **Full Codebase Inspection**: Reviewed all Rust source files (~7,370 LOC), with dedicated focus on `src/daemon.rs`, `src/client.rs`, `src/crypto.rs`, `src/vault.rs`, `src/policy.rs`, and `src/session.rs`.
- **Configuration & Infrastructure Review**: Evaluated all 7 CI/CD workflows under `.github/workflows/` and on-disk file permission structures.
- **Dynamic Verification**: Executed `cargo test` (all 74 unit/integration tests successfully passed).
- **Threat Analysis**: Focused on key derivation, memory lifecycle, socket-level boundary separation, and the impact of the new daemon on the threat model.

---

## Security Findings

### Critical / High Severity

| # | Severity | Finding | File(s) | Evidence / Impact |
|---|----------|---------|---------|-------------------|
| H1 | **High** | **No socket-level authentication or caller identity validation** | `src/daemon.rs:290-308` | The Unix Domain Socket (`.svault/daemon.sock`) relies purely on OS file-system permissions (`0600`). The daemon does not verify caller process identities (via `SO_PEERCRED` or `getpeereid`) or require any token/challenge authentication. On shared developer machines or shared/persistent CI/CD runners, any process running under the same UID can read all cached secrets. |
| H2 | **High** | **Raw master passphrase transmitted in plaintext over Unix socket** | `src/daemon.rs:133`, `src/daemon.rs:26-41` | The `Request::Unlock` command sends the master passphrase as a cleartext string inside JSON. If an adversary gains access to the socket or intercepts IPC communications (e.g., via socket forwarding or permission misconfiguration), the root-of-trust passphrase is instantly compromised. |
| H3 | **High** | **File-session fallback caches raw master passphrase on disk** | `src/session.rs:15-29` | When the daemon is not running (or on non-Unix environments like Windows), `session::unlock` falls back to writing the raw passphrase in plaintext to `.svault/<vault>/.session`. While Unix limits this to `0600`, Windows uses default DACL inheritance, exposing the master credentials to on-disk leakage, side-channel attacks, or backup exposure. |

### Medium Severity

| # | Severity | Finding | File(s) | Evidence / Impact |
|---|----------|---------|---------|-------------------|
| M1 | **Medium** | **Heap-allocated secret values are not zeroized** | `src/daemon.rs:58-60`, `src/vault.rs:142-147` | Although `VaultKey` implements `ZeroizeOnDrop`, secret values are processed and returned as standard heap-allocated `String` types. They are not zeroized on drop. Decrypted secret payloads remain scattered in physical RAM indefinitely until overridden, exposing them to memory dump/inspection techniques. |
| M2 | **Medium** | **Static salt reuse during secret database writes** | `src/vault.rs:149-156` | The `save_secrets` method reads the existing salt from `vault.enc` and uses it to re-encrypt the updated database. Although GCM utilizes a cryptographically random 96-bit nonce on each call, reusing the same key and salt repeatedly slightly degrades cryptographic posture over time. Generating a fresh salt for each file write is the industry standard. |
| M3 | **Medium** | **Unbounded connection thread spawning (Denial of Service)** | `src/daemon.rs:293-302` | The daemon's accept loop spawns a new OS thread for every incoming connection without an active pool, rate limiter, or maximum connection ceiling. A malicious script can exhaust system descriptors and threads, causing a Denial of Service. |
| M4 | **Medium** | **Recursive policy lookup traversal to system root** | `src/policy.rs:134-145` | `find_policy_file()` recursively pops paths up to `/` to find `svault.policy.yaml`. If an administrator or developer runs a command inside a multi-user environment where a parent or shared directory is writable, an attacker can place a permissive `svault.policy.yaml` in an ancestor directory to bypass access controls. |
| M5 | **Medium** | **No static analysis (SAST) or dependency auditing in CI** | `.github/workflows/` | There are no automated security scanning gates (such as `cargo audit`, `cargo-deny`, or specialized SAST) in the project's CI pipelines. Known-vulnerable dependencies or unsound transitives (e.g., `lru` in ratatui) can easily bypass static checks. |

### Low / Informational

| # | Severity | Finding | File(s) | Evidence / Impact |
|---|----------|---------|---------|-------------------|
| L1 | **Low** | **Passphrase strength validation is advisory only** | `src/passphrase.rs` | Strength checks are displayed as warnings but are not strictly enforced. Weak or easily brute-forceable passphrases can be committed by end-users. |
| L2 | **Low** | **No checksum/parity digit in recovery codes** | `src/recovery.rs:30-39` | The 160-bit random recovery code is split into 10 groups of 4 hex characters, but it lacks a checksum character. Typing mistakes are not caught early before executing Argon2id, leading to potential UX frustration and sluggish brute-force attempts. |
| L3 | **Low** | **Vault decodes entire database to verify passphrase** | `src/vault.rs:57-73` | To verify a passphrase, `Vault::open` attempts to decrypt the entire secret database block. A more performant and secure design is to use a separate key verification block (e.g., decrypting a static known payload or validating a separate HMAC). |

---

## Positive Security Properties Observed

1. **Robust Key Derivation**: Standard-compliant Argon2id (64MB, 3 iterations, 1 thread) provides optimal resistance against GPU/ASIC brute-force attacks.
2. **Authenticated Encryption**: Direct use of AES-256-GCM via `aes-gcm` with cryptographically secure random nonces ensures strong confidentiality and integrity.
3. **Metadata Signature Enforcement**: Metadata configurations (`meta.yaml`) are signed using HMAC-SHA256, verified using constant-time comparison checks to mitigate timing side-channels.
4. **Scrubbing Key Material**: `VaultKey` implements `Zeroize` and `ZeroizeOnDrop`, safely cleaning cryptographic material from memory.
5. **Real-time Ticker Auto-Lock**: The background daemon monitors activity to automatically evict and zeroize keys based on idle-timeout and hard-max limits.
6. **Isolated Socket Permissions**: Sockets are created with exact `0600` owner-only permissions.
7. **Clean CLI Compilation**: Built cleanly under the strict standard Rust compilation framework.

---

## The Unix Daemon (0.5.0+) — Detailed Analysis

### Architecture & Attack Surface
The 0.5.0 Unix Daemon is a major evolution. It runs a local loop over `.svault/daemon.sock`, storing derived key material as an `Arc<Mutex<HashMap<String, Held>>>` with each `Held` wrapper using `Zeroizing<[u8; 32]>`.

### Threat Posture: Session File vs. Daemon
- **Memory Safety**: The daemon successfully solves the "credential-at-rest" vulnerability on Unix. Because the master passphrase is never stored on disk, the risk of disk leakage is eliminated.
- **Auto-Lock Verification**: The dual-timer model is exceptionally clean. Idle limits (default 15 mins) and hard caps (default 8 hours) ensure that even if a workstation is physically compromised, the window of vulnerability is finite.
- **Vulnerabilities**: The daemon creates a new IPC attack vector. Since Unix socket access is governed strictly by filesystem DAC (owner UID), any compromised agent or tool executing under the same developer user can access the socket. This undermines the policy engine's intent when multiple automated processes share a user environment.

---

## Infrastructure, Server & CI/CD Deployment Considerations

### CI/CD Suitability
1. **Single-Tenant Containers (Highly Suitable)**: On isolated, ephemeral, single-build container runtimes (e.g., GitHub-hosted runners), using the daemon is safe as there are no co-tenant processes sharing the environment.
2. **Persistent Multi-Tenant Build Servers (Unsuitable)**: On persistent build servers (e.g., self-hosted Jenkins nodes running concurrent tasks under a shared `jenkins` user), Svault's daemon model is highly vulnerable. Any runner can issue a `Get` command to another's daemon socket, reading private credentials.

### Non-Unix Environments (Windows)
The daemon is entirely missing on Windows. It falls back to the file-session (`.session`), caching the master passphrase in plaintext on disk. Windows DACLs are not configured or managed by Svault, making corporate adoption on Windows machines an unacceptable risk.

---

## Recommendations for Corporate Adoption

1. **Implement Daemon Peer-Credential Verification** (High Priority)
   Verify the PID/UID of any process connecting to `.svault/daemon.sock` using platform-native APIs (e.g., `SO_PEERCRED` on Linux, `getpeereid` on macOS). Reject any connections that do not match the daemon owner's precise PID or authorization profile.
2. **Introduce Ephemeral Socket Tokens** (High Priority)
   Upon daemon initialization, generate a single-use high-entropy token, save it to an owner-only file, and require all client requests to include this token in the JSON envelope.
3. **Zeroize Decrypted Secret Material** (High Priority)
   Replace plain heap-allocated `String` storage for secret values with `zeroize::Zeroizing<String>` or custom byte arrays that scrub themselves upon dropping.
4. **Disable File Fallbacks on Windows or Non-Daemon Modes** (Medium Priority)
   In corporate configurations, allow disabling the insecure `.session` file fallback entirely, throwing a hard error if the daemon is unavailable.
5. **Add Automated Audit Pipelines** (Medium Priority)
   Incorporate `cargo audit` and `cargo-deny` checks directly into the GHA pipeline to block dependency-chain security regressions.

---

## Overall Risk Assessment by Context

| Deployment Context | Risk Rating | Operational Posture & Impact |
|--------------------|-------------|------------------------------|
| **Personal Use (Unix + Daemon)** | **Low** | Highly secure; memory-only keys with real auto-lock minimize physical and logical exposure. |
| **Small Unix Teams (Isolated Dev Boxes)** | **Low** | Recommended; safe so long as developers do not share user accounts on local workstations. |
| **Ephemeral Unix CI/CD (GitHub Actions)** | **Moderate** | Acceptable; isolated environments prevent inter-process snooping. |
| **Multi-Tenant CI/CD (Shared Runners)** | **High** | Unacceptable; lack of socket authentication allows job-to-job credential hijacking. |
| **Mixed Windows Environments** | **High** | Unacceptable; plaintext passphrase caches on disk without access-control enforcement. |

---

## References

- **Daemon Code**: `src/daemon.rs`
- **Client Router**: `src/client.rs`
- **Crypto Engine**: `src/crypto.rs`
- **Security Policy Documentation**: `docs/security.md`, `docs/daemon.md`
