Skip to content

Releases: jyshnkr/claudesync

v0.3.0 — Security hardening, reliability fixes, pair & autostart

01 Mar 21:36

Choose a tag to compare

What's New in v0.3.0

Security

  • Allowlist sanitizer.claude.json sync switched from blocklist to allowlist: only explicitly safe fields are transferred; unknown future fields stay local by default, preventing silent data exfiltration
  • Dedicated known_hosts — SSH host keys stored in ~/.claudesync/known_hosts, separate from ~/.ssh/known_hosts; rogue re-keys are caught on subsequent connections
  • XML-escaped plist generation — All user-supplied values are XML-escaped before being written to launchd plists
  • Absolute launchctl path/bin/launchctl used throughout to prevent CWE-426 PATH hijacking
  • Recursive sensitive-key strippingenv, apiKey, token, secret, password, etc. are recursively stripped from mcpServers and projects in .claude.json sanitization, including from lists
  • autostart input validation — Remote name validated with strict regex; rejects .., /, and empty values to prevent path traversal and XML injection

Added

  • claudesync pair --name <n> --address user@host — One-command two-machine setup: tests SSH, auto-detects remote home, saves config, and runs an initial push
  • claudesync autostart enable/disable <remote> (macOS) — Installs/removes a launchd plist (~/Library/LaunchAgents/com.claudesync.<remote>.plist) to auto-pull every N seconds (default 5 min)
  • Human-readable conflict output — Conflict resolution now shows ← LOST / ← WON labels with relative timestamps (3 days ago, 2 hours ago)

Fixed

  • Manifest file lockingfcntl.LOCK_EX prevents lost updates when two concurrent syncs race on the manifest file
  • Versioned remote agent — SSH Python one-liner replaced with a standalone remote_agent.py deployed to ~/.claudesync/ on the remote; eliminates shell quoting edge cases and SSH banner pollution
  • history.jsonl config parsingbool("false") was True; replaced with _parse_bool() helper
  • include_history forwarded to post-pull manifest rebuild
  • pair SSH probe hardenedecho $HOME probe wrapped in try/except for TimeoutExpired/OSError
  • pair celebration guard — "✓ Paired!" shown only when push has no errors
  • uninstall_plist — Catches FileNotFoundError when launchctl binary is missing
  • merge_pulled_claude_json — Strips sensitive nested keys from remote overlay before merging
  • CLI autostart handlers — Catch ValueError alongside CalledProcessError
  • Exception chainingraise ... from e used throughout for correct tracebacks
  • remote_agent.hash_files — Skips unreadable files rather than crashing
  • remote_agent.__main__ — Validates JSON arg is list[str]; exits 1 for bad input

Changed

  • history.jsonl is opt-in — Conversation history no longer syncs by default. Enable with sync.include_history = true in ~/.claudesync/config.toml
  • include_history is keyword-only on Engine.push, Engine.pull, Engine._sync, Engine.dry_run to prevent positional misuse

Stats

  • Tests: 108 → 155 (+47 new tests)

v0.2.0 — Security fixes, bug fixes, documentation

01 Mar 16:50
16b724d

Choose a tag to compare

Security

  • Fix bulk restore path traversal bypass in restore_backup() — the restore-all branch now checks both that source files resolve inside the backup archive and that destinations resolve inside $HOME
  • Preserve file permissions on atomic replace in merge_pulled_claude_json(), save_manifest(), and save_config()
  • Validate backup_id as a single safe path segment; rejects .., ., multi-segment values like ../../etc that could escape BACKUP_DIR
  • Atomic restore writes via _atomic_copy helper: writes to a temp file in the same directory then os.replace() (POSIX-atomic rename), blocking symlink write races
  • is_dir() guard on backup entry lookup — rejects a regular file at BACKUP_DIR/<backup_id> that would silently pass exists()
  • Close TOCTOU window in _atomic_copy via dirfd inode verification: opens dest.parent with O_NOFOLLOW|O_DIRECTORY, compares (st_ino, st_dev) to the preceding lstat, then creates the temp file against the held fd via os.openat

Fixed

  • Rebuild local manifest after pull to record post-sync file state
  • Translate local paths to remote equivalents in _build_manifests() before querying remote file hashes
  • Handle FileNotFoundError when SSH binary is missing → SyncError
  • Validate JSON root is a dict in sanitize_claude_json(), merge_pulled_claude_json(), and load_manifest()
  • Count .claude.json transfers in SyncSummary.files_transferred

Changed

  • SyncedFileEntry.last_synced is now optional (TypedDict compat for legacy manifests)
  • dry_run is keyword-only on _rsync_global/_rsync_project
  • Validate sync.strategy at config load time

Added

  • README.md, CHANGELOG.md, updated SECURITY.md
  • 20 new tests (88 → 108 total), including 18 covering backup creation, listing, restore, path-traversal guards, backup_id validation, is_dir guard, and symlink rejection in _atomic_copy

Full changelog: https://github.com/jyshnkr/claudesync/blob/main/CHANGELOG.md

v0.1.0 — Initial release

01 Mar 17:02
42cb7a9

Choose a tag to compare

Added

  • Bi-directional Claude Code context sync over SSH
  • CLI commands: init, push, pull, status, diff
  • Remote management: remote add, remote list
  • Project registration: project add, project list
  • Conflict detection with last-write-wins resolution; automatic backup of the losing side with configurable rotation
  • Per-remote SHA-256 + mtime manifest for accurate change detection
  • ~/.claude.json sanitization — strips OAuth tokens, API keys, and other auth fields before transfer; merge on pull preserves local auth fields
  • Rsync-based file transfer with SSH key authentication
  • Atomic writes on all config/manifest updates (temp file + rename)
  • Path-traversal guards on backup restore
  • 88 tests

Full changelog: https://github.com/jyshnkr/claudesync/blob/main/CHANGELOG.md