feat: add clihost rsync sync command - #96
Conversation
…d RCE (#92) Cycle-review (PR #96) — both Codex and the Claude reviewer independently reproduced host RCE: printf %q guards SHELL injection but NOT OPTION injection. A target starting with '-' is parsed by ssh/rsync as a flag, so CLIHOST_SSH_TARGET=-oProxyCommand=<cmd> runs <cmd> LOCALLY on the host during the preflight ssh — before any mount/symlink guard can contain it. Same class as the dashboard ProxyCommand issue (#85). Two reachable sinks: the ssh preflight and the rsync source/dest operands. Fix: - reject a target matching `-*` up front (die before any ssh/rsync). - defence-in-depth: `--` end-of-options before the ssh host and before the rsync positional operands, so a `-`-leading value can never be parsed as a flag. Tests: test_option_like_target_is_rejected_before_ssh_or_rsync plants `-oProxyCommand=touch <canary>` and asserts the run aborts and the canary never appears (fails on old code — canary was created); test_ssh_and_rsync_calls_use_ end_of_options_separator pins the `--` separators. Verified behaviorally: the hostile target is rejected, no ProxyCommand runs. pytest tests/ -> 389 passed, 208 subtests. Refs #92 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vecu5xo9D9Cs2vmeohKVKv
🔍 Local review (cycle 1)Reviewed locally (Claude subagent + Codex companion), no bots pinged.
FIX applied ( Two regression tests: Note: the rest of the safety envelope was genuinely well-built and well-tested — the whole scheme hinged on this one unvalidated input. Cycle 1/3 complete: 1 FIX (fixed, both sinks). No remaining FIX → review cycle done. Merge is yours to trigger (local mode does not auto-merge). |
Closes #92
Summary
bin/clihost-sync.sh, a host-side rsync-over-SSH helper withpullas host -> container andpushas container -> host.~/.gitconfigand~/.config/ghonly./bin/clihost-sync.shand document usage in README, CLAUDE.md, and.env.example.Safety checks
--apply.--backup --backup-dir=....--deleteis never passed unless--allow-deleteis explicit./home/hapi./home/hapipaths and existing symlinks under the syncedghtree; rsync also uses--no-links.~/.claude, private~/.ssh, or Claudesettings.jsonsync.Validation
python -m pytest tests/-> 387 passeddocker build -t clihost:issue-92-sync .docker run --rm --entrypoint bash clihost:issue-92-sync -lc 'test -x /bin/clihost-sync.sh && /bin/clihost-sync.sh --help >/tmp/clihost-sync-help 2>&1 && grep -q "clihost-sync.sh pull" /tmp/clihost-sync-help'