https://github.com/coredipper/enclaude
Encrypted git-like sync for ~/.claude/ session data
https://github.com/coredipper/enclaude
Last synced: 2 months ago
JSON representation
Encrypted git-like sync for ~/.claude/ session data
- Host: GitHub
- URL: https://github.com/coredipper/enclaude
- Owner: coredipper
- License: mit
- Created: 2026-04-06T18:44:48.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-04-11T22:03:24.000Z (3 months ago)
- Last Synced: 2026-04-11T23:25:48.566Z (3 months ago)
- Language: Go
- Size: 88.9 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# enclaude
Encrypted, git-backed, cross-device sync for `~/.claude/`.
## The Problem
Claude Code stores everything in plaintext at `~/.claude/`:
- **`history.jsonl`** — every prompt you've ever typed, timestamped
- **Session JSONL files** — full conversation transcripts including tool calls, tool results, and any file content Claude read during the session
- **Memory files** — project-specific context Claude remembers between sessions
- **Settings and stats** — your configuration, usage patterns, plugin list
This means your `~/.claude/` directory contains a detailed record of your work: code snippets, error messages, file paths, environment variables, and anything else that appeared in a session. It sits on disk as readable text with no encryption, no signatures, and no tamper detection.
This isn't a bug — it's how every AI coding assistant works today (Cursor, Copilot, Windsurf all store history in plaintext too). But it means:
1. **Anyone with access to your disk can read your full Claude history.** If your laptop is lost, stolen, or accessed by another user, all session data is exposed.
2. **There's no way to sync sessions across devices.** Your history on your work laptop and your personal machine are completely separate.
3. **There's no version history.** If a session file is corrupted or a memory file is overwritten, there's no way to recover a previous state.
`enclaude` addresses all three.
## What It Does
`enclaude` sits between Claude Code and your filesystem. It doesn't modify Claude Code — it works alongside it using a two-directory architecture:
```
~/.claude/ plaintext (what Claude Code reads/writes)
|
| seal (encrypt)
v
~/.enclaude/ encrypted git repo
manifest.json file index: path -> SHA-256 hash, merge strategy
seal.toml config: include/exclude patterns, device ID
key.age.backup passphrase-encrypted key backup
objects/ content-addressed age-encrypted blobs
|
| git push/pull
v
remote repo synced across devices
```
**Seal** encrypts changed files from `~/.claude/` into content-addressed objects using [age](https://age-encryption.org/) (ChaCha20-Poly1305 + X25519). Each file is hashed (SHA-256) and encrypted individually. Only changed files are re-encrypted — unchanged files are skipped by comparing hashes.
**Unseal** decrypts the objects back to `~/.claude/` so Claude Code can use them.
**Git** provides the transport layer. The encrypted objects are committed to a git repository, giving you full version history, branching, and remote sync — all on encrypted data. Your plaintext never leaves your machine; only encrypted blobs are pushed.
### Why This Works Well for Claude Data
Claude Code's session files have a property that makes sync trivial: **they're immutable after completion**. Once a session ends, its JSONL file is never modified again. This means:
- Two devices that both ran sessions produce different files with different hashes — no conflicts, just union both sides
- `history.jsonl` is append-only — merging two diverged copies means deduplicating lines and sorting by timestamp
- Memory files are small markdown — standard 3-way text merge handles them
The only files that need real merge logic are `settings.json` (last-write-wins) and `history.jsonl` (line-level dedup). Everything else is either immutable or trivially mergeable.
## Quick Start
```bash
# Install
go install github.com/coredipper/enclaude@latest
# Initialize — generates an age key, stores it in your OS keychain,
# encrypts all ~/.claude/ data into ~/.enclaude/
enclaude init
# See what's changed since last seal
enclaude status
# Encrypt changes
enclaude seal
# Decrypt back to ~/.claude/
enclaude unseal
```
### Set Up Cross-Device Sync
```bash
# Create a private repo for your encrypted data
# (only encrypted blobs are pushed — your plaintext never leaves your machine)
enclaude remote add origin git@github.com:you/enclaude-data.git
enclaude push
# On another device — clone the encrypted repo and import your key
git clone git@github.com:you/enclaude-data.git ~/.enclaude
enclaude key import --from-backup # or: enclaude key import keyfile.txt
enclaude unseal
enclaude hooks install
```
### Auto-Sync with Hooks
```bash
enclaude hooks install
```
This adds `SessionStart` and `SessionEnd` hooks to `~/.claude/settings.json`. When a session starts, `enclaude` unseals the latest sealed data. When it ends, it seals changes locally. To enable automatic remote sync, set `auto_push = true` and `auto_pull = true` in `~/.enclaude/seal.toml`. Your existing hooks (peon-ping, notchi, etc.) are preserved — the installer appends to the hooks array, never overwrites.
## Commands
### Core
| Command | Description |
|---------|-------------|
| `init` | Generate age keypair, store in OS keychain, initial seal |
| `seal` | Encrypt changed files, commit to seal store |
| `unseal` | Decrypt seal store to `~/.claude/` |
| `status` | Show changes since last seal |
| `sync` | Seal + pull + push (the daily driver) |
| `push` | Seal + git push |
| `pull` | Git pull + merge + unseal |
### History & Recovery
| Command | Description |
|---------|-------------|
| `log` | Show seal history with commit messages |
| `diff [ref]` | Decrypt and diff between current state and a previous commit |
| `rollback ` | Restore `~/.claude/` to a previous commit (creates a safety seal first, so you can always undo) |
### Key Management
| Command | Description |
|---------|-------------|
| `key show` | Display public key and source |
| `key export` | Print private key to stdout (pipe to a password manager) |
| `key import ` | Import key from file, stdin (`-`), or `--from-backup` |
| `key rotate` | Generate new key, re-encrypt all objects, update keychain |
### Maintenance
| Command | Description |
|---------|-------------|
| `repair` | Verify integrity and fix missing objects by re-sealing from plaintext |
| `repair --check` | Verify-only mode (exit code 1 if issues found, useful for CI) |
| `repair --delete-orphans` | Also remove unreferenced object files |
| `hooks install` | Add auto-sync hooks to Claude Code settings |
| `hooks remove` | Remove auto-sync hooks |
| `hooks status` | Check if hooks are installed |
## Merge Strategies
When pulling from a remote, two devices may have diverged. `enclaude` uses a custom git merge driver that applies different strategies depending on the file type:
| Strategy | Used for | How it works |
|----------|----------|-------------|
| `immutable` | Session JSONL files | Union both sides — sessions never change after completion, so there are no conflicts |
| `jsonl_dedup` | `history.jsonl` | Parse each line as JSON, SHA-256 hash for dedup, sort by timestamp |
| `sessions_index` | `sessions-index.json` | Deduplicate entries by `sessionId`, preserving unique sessions from both sides |
| `last_write_wins` | `settings.json`, `stats-cache.json` | Keep whichever version has the later modification time |
| `text_merge` | Memory files (`.md`) | Standard 3-way text merge; conflict markers if both sides changed |
These strategies are configurable per path pattern in `seal.toml`.
## Configuration
`~/.enclaude/seal.toml` controls what gets synced and how:
```toml
[seal]
claude_dir = "~/.claude"
seal_dir = "~/.enclaude"
device_id = "macbook-ab12cd34"
[sync]
auto_seal_on_session_end = true
auto_unseal_on_session_start = true
auto_push = false
auto_pull = false
[include]
patterns = [
"history.jsonl",
"settings.json",
"projects/*/*.jsonl",
"projects/*/memory/**",
"projects/*/subagents/**",
]
[exclude]
patterns = [
"statsig/**", # feature flag caches — regenerated automatically
"plugins/**", # 200+ MB of cached plugin data — regenerated
"debug/**", # debug logs
"hooks/**", # your hook scripts — version these separately
"settings.local.json", # device-specific paths and permissions
]
[merge_strategies]
"history.jsonl" = "jsonl_dedup"
"projects/*/*.jsonl" = "immutable"
"settings.json" = "last_write_wins"
"projects/*/memory/**" = "text_merge"
```
## Security Model
| Property | Status |
|----------|--------|
| Encrypted at rest (between sessions) | Yes — completed session plaintext can be shredded after seal |
| Encrypted at rest (during active session) | No — Claude Code requires plaintext to function |
| Encrypted in transit (git push/pull) | Yes — only age-encrypted blobs are pushed |
| Key storage | OS keychain (macOS Keychain, Linux secret-service, Windows Credential Manager) |
| Key backup | Passphrase-encrypted `key.age.backup` travels with the repo |
| Tamper detection | SHA-256 content hashes in manifest; `repair --check` verifies integrity |
| Key never in git | Correct — only the passphrase-encrypted backup is committed |
### Honest Limitations
- **During an active Claude Code session, plaintext exists on disk.** This is unavoidable — Claude Code reads `~/.claude/` directly and cannot be modified to read encrypted data. Use OS-level disk encryption (FileVault, BitLocker, LUKS) for protection during sessions.
- **Application-level encryption does not protect against a malicious process running as your user.** If an attacker has code execution as your user, they can read decrypted files in memory or extract the key from the keychain. OS-level protections are the right defense layer here.
- **The encryption key must be shared across devices.** This is inherent to any cross-device sync scheme. Use `key export` to save your key in a password manager, or rely on the passphrase-encrypted `key.age.backup` that travels with the repo.
## How It Works Under the Hood
### Content-Addressed Storage
Like git itself, `enclaude` stores objects by their content hash. When you seal a file:
1. Read the plaintext from `~/.claude/`
2. Compute SHA-256 of the plaintext → this becomes the content address
3. Encrypt the plaintext with your age public key
4. Store the encrypted blob at `objects//.age`
5. Record the mapping in `manifest.json`
On the next seal, unchanged files produce the same hash and are skipped entirely. Only new or modified files are encrypted. This makes incremental seals fast — typically under 1 second after a normal session.
### Session Lifecycle
With hooks installed, the flow is:
```
Session starts → hook fires → pull latest + unseal
↓
Claude Code runs (reads/writes ~/.claude/ as normal)
↓
Session ends → hook fires → seal changes + push
```
The hook handler acquires a file lock (`~/.enclaude/.seal.lock`) to prevent concurrent seal/unseal operations. If the lock can't be acquired within 5 seconds, the hook exits silently — it never blocks Claude Code.
### New Device Onboarding
```
1. Install enclaude
2. Clone your encrypted data repo
3. Import your key (from password manager, file, or backup passphrase)
4. Pull + unseal
5. Install hooks
```
Your full Claude Code history, memory, and settings appear on the new device, encrypted in transit and at rest.
## License
MIT