Skip to content

Commit b7c01fd

Browse files
johnzillaclaude
andcommitted
chore: archive v1.1 milestone — Security Hardening & Code Review Fixes
5 phases, 9 plans, 11/11 requirements satisfied. Archived roadmap, requirements, and audit to milestones/. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d2b65d3 commit b7c01fd

8 files changed

Lines changed: 304 additions & 311 deletions

File tree

.planning/MILESTONES.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,28 @@
2525

2626
---
2727

28+
## v1.1 Security Hardening & Code Review Fixes (Shipped: 2026-02-22)
29+
30+
**Phases:** 6-10 (9 plans) | **Rust LOC:** 2,633 (src) + 590 (tests) | **Timeline:** 1 day
31+
32+
**Delivered:** Comprehensive security hardening: signed metadata envelopes, key permission enforcement, PIN-protected handoffs, structured error handling, and migration from homeserver to direct PKARR Mainline DHT transport with full metadata encryption.
33+
34+
**Key accomplishments:**
35+
- Ed25519-signed burn and recipient fields with tamper detection (clean break from v1.0)
36+
- Key file permission enforcement (0600) at load and write time
37+
- PIN-protected handoffs with Argon2id+HKDF-SHA256 key derivation
38+
- Structured error handling (CclinkError::RecordNotFound, dead variant cleanup)
39+
- Replaced homeserver transport with PKARR Mainline DHT (no accounts, no tokens, no signup)
40+
- Encrypted all sensitive metadata (hostname, project path, session ID) into blob -- zero cleartext metadata on DHT
41+
42+
**Git range:** `49c9586``d2b65d3` (97 files, 6,140 insertions, 11,880 deletions)
43+
44+
**Known tech debt:**
45+
- LatestPointer struct is dead code after DHT migration
46+
- Age ciphertext size non-deterministic (budget relies on skip_serializing_if)
47+
- QR code content wrong when `--share` + `--qr` combined
48+
49+
**Archive:** `milestones/v1.1-ROADMAP.md`, `milestones/v1.1-REQUIREMENTS.md`
50+
51+
---
52+

.planning/PROJECT.md

Lines changed: 64 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## What This Is
44

5-
A single Rust CLI binary (`cclink`) that publishes cryptographically signed, encrypted Claude Code session handoff links via the Pubky protocol. Run `cclink` on one machine to publish your session, `cclink pickup` on another to resume it no central relay, no accounts, your PKARR key is your identity.
5+
A single Rust CLI binary (`cclink`) that publishes cryptographically signed, encrypted Claude Code session handoff records directly to the PKARR Mainline DHT. Run `cclink` on one machine to publish your session, `cclink pickup` on another to resume it -- no central relay, no accounts, no signup tokens. Your PKARR key is your identity.
66

77
## Core Value
88

@@ -12,102 +12,91 @@ Effortless, secure session handoff between devices: `cclink` on one machine, `cc
1212

1313
### Validated
1414

15-
- ✓ Generate and manage PKARR/Ed25519 keypairs (`cclink init`, `cclink whoami`) — v1.0
16-
- ✓ Discover Claude Code session IDs from `~/.claude/projects/` with cwd scoping — v1.0
17-
- ✓ Build and sign handoff payload (session ID, hostname, project, timestamps) — v1.0
18-
- ✓ Encrypt session ID with age (self-encrypt via Ed25519-to-X25519 derivation) — v1.0
19-
- ✓ Publish encrypted handoff record to Pubky homeserver — v1.0
20-
- ✓ Retrieve and decrypt own handoff (`cclink pickup`) — v1.0
21-
- ✓ Share-mode encryption to a specific recipient's public key (`--share`) — v1.0
22-
- ✓ Burn-after-read mode (`--burn`) — delete record after first retrieval — v1.0
23-
- ✓ TTL-based expiry (`--ttl`, default 24h) — v1.0
24-
- ✓ Terminal QR code rendering after publish and on pickup — v1.0
25-
-`cclink list` — show active handoff records with comfy-table — v1.0
26-
-`cclink revoke` — delete specific or all handoff records — v1.0
27-
- ✓ Auto-execute `claude --resume <id>` after pickup (default behavior) — v1.0
28-
- ✓ Colored terminal output with status indicators — v1.0
29-
- ✓ Ed25519 signature verification on all retrieved records — v1.0
30-
- ✓ Atomic key write (write-to-temp + rename) — v1.0
31-
- ✓ CI/CD with 4-platform release builds and curl installer — v1.0
32-
- ✓ Round-trip encryption tests and plaintext leak detection in CI — v1.0
15+
- ✓ Generate and manage PKARR/Ed25519 keypairs (`cclink init`, `cclink whoami`) -- v1.0
16+
- ✓ Discover Claude Code session IDs from `~/.claude/projects/` with cwd scoping -- v1.0
17+
- ✓ Build and sign handoff payload (session ID, hostname, project, timestamps) -- v1.0
18+
- ✓ Encrypt session ID with age (self-encrypt via Ed25519-to-X25519 derivation) -- v1.0
19+
- ✓ Publish encrypted handoff record to DHT -- v1.0 (homeserver), v1.1 (direct DHT)
20+
- ✓ Retrieve and decrypt own handoff (`cclink pickup`) -- v1.0
21+
- ✓ Share-mode encryption to a specific recipient's public key (`--share`) -- v1.0
22+
- ✓ Burn-after-read mode (`--burn`) -- delete record after first retrieval -- v1.0
23+
- ✓ TTL-based expiry (`--ttl`, default 24h) -- v1.0
24+
- ✓ Terminal QR code rendering after publish and on pickup -- v1.0
25+
-`cclink list` -- show active handoff records with comfy-table -- v1.0
26+
-`cclink revoke` -- delete/revoke handoff records -- v1.0
27+
- ✓ Auto-execute `claude --resume <id>` after pickup (default behavior) -- v1.0
28+
- ✓ Colored terminal output with status indicators -- v1.0
29+
- ✓ Ed25519 signature verification on all retrieved records -- v1.0
30+
- ✓ Atomic key write (write-to-temp + rename) -- v1.0
31+
- ✓ CI/CD with 4-platform release builds and curl installer -- v1.0
32+
- ✓ Round-trip encryption tests and plaintext leak detection in CI -- v1.0
33+
- ✓ Sign burn + recipient fields in handoff payload (clean break from v1.0 format) -- v1.1
34+
- ✓ Enforce key file permissions (0600) explicitly in cclink code -- v1.1
35+
- ✓ PIN-protected handoffs (`--pin`) with Argon2id+HKDF-derived key -- v1.1
36+
- ✓ Make `--burn` + `--share` mutually exclusive -- v1.1
37+
- ✓ Fix pickup CLI help text -- v1.1
38+
- ✓ Structured error handling (CclinkError variants) -- v1.1
39+
- ✓ Remove dead CclinkError variants -- v1.1
40+
- ✓ Optimize list command -- v1.1
41+
- ✓ Lazy signin / session reuse -- v1.1 (superseded by DHT)
42+
- ✓ Update PRD stale path references -- v1.1
43+
- ✓ Encrypt all sensitive metadata into blob (no cleartext hostname/project on DHT) -- v1.1
44+
- ✓ Direct PKARR Mainline DHT transport (no homeserver dependency) -- v1.1
3345

3446
### Active
3547

36-
- [ ] Sign burn + recipient fields in handoff payload (clean break from v1.0 format)
37-
- [ ] Enforce key file permissions (0600) explicitly in cclink code
38-
- [ ] PIN-protected handoffs (`--pin`) with Argon2id+HKDF-derived key
39-
- [ ] Make `--burn` + `--share` mutually exclusive
40-
- [ ] Fix pickup CLI help text (self-publish suggests wrong command)
41-
- [ ] Lazy signin / session reuse in HomeserverClient
42-
- [ ] Deduplicate `human_duration` into shared utility
43-
- [ ] Structured error handling (replace string matching with CclinkError variants)
44-
- [ ] Remove dead CclinkError variants
45-
- [ ] Optimize list command (reduce N+1 HTTP requests)
46-
- [ ] Update PRD stale path references (~/.cclink → ~/.pubky)
48+
(None -- next milestone will define new requirements)
4749

4850
### Out of Scope
4951

50-
- Team/shared namespace handoffs — v2, not needed for single-user flow
51-
- Web UI at cclink.dev — optional polish, CLI-first
52-
- Claude Code hook/plugin integration — future consideration
53-
- Mobile app — terminal-only
54-
- Notifications/push — out of scope entirely
55-
- Session preview/summary — would require accessing session content
56-
- 3GS integration — future consideration
57-
- Override inferred project label via `--project` — deferred, not in code review scope
58-
- Burn-after-read for shared records — homeserver can't support delegated delete; --burn + --share now mutually exclusive
59-
60-
## Current Milestone: v1.1 Security Hardening & Code Review Fixes
61-
62-
**Goal:** Address all findings from external code review — fix security gaps, resolve functional discrepancies, and improve code quality.
63-
64-
**Target features:**
65-
- Signed metadata (burn + recipient in payload)
66-
- Key file permission enforcement
67-
- PIN-protected handoffs
68-
- Lazy authentication
69-
- Structured error handling
70-
- List command optimization
52+
- Team/shared namespace handoffs -- v2, not needed for single-user flow
53+
- Web UI at cclink.dev -- optional polish, CLI-first
54+
- Claude Code hook/plugin integration -- future consideration
55+
- Mobile app -- terminal-only
56+
- Session preview/summary -- would require accessing session content
57+
- Override inferred project label via `--project` -- deferred, not in code review scope
58+
- Burn-after-read for shared records -- DHT can only be revoked by key owner
7159

7260
## Context
7361

74-
Shipped v1.0 with 2,851 LOC Rust.
75-
Tech stack: Rust, pkarr 5.0.3, age (x25519), reqwest (rustls), clap, owo-colors, comfy-table, qr2term.
62+
Shipped v1.1 with 2,633 LOC Rust (src) + 590 LOC tests.
63+
Tech stack: Rust, pkarr 5.0.3 (Mainline DHT), age (X25519), clap, owo-colors, comfy-table, qr2term, argon2.
7664

7765
- Claude Code stores sessions in `~/.claude/projects/` as directories with JSONL progress records
7866
- `claude --resume <sessionID>` resumes a session from any device with filesystem access
79-
- Pubky is a decentralized protocol using PKARR (Public Key Addressable Resource Records) for identity
67+
- Records published directly to PKARR Mainline DHT as DNS TXT records inside Ed25519-signed packets
68+
- One handoff per identity (DHT stores one SignedPacket per public key)
8069
- Ed25519 keys birationally map to X25519, enabling age encryption with the same keypair
81-
- The pickup device still needs filesystem access to the session data (SSH, Tailscale, etc.) — cclink only transfers the session ID reference, not session content
82-
- Handoff records are published to `/pub/cclink/<token>` on the homeserver
83-
- A `latest` pointer tracks the most recent handoff
84-
- Key storage at `~/.pubky/secret_key` with 0600 permissions (reuses Pubky ecosystem path)
70+
- All sensitive metadata (hostname, project path, session ID) encrypted into blob -- DHT nodes see only ciphertext
71+
- Key storage at `~/.pubky/secret_key` with 0600 permissions
72+
- The pickup device still needs filesystem access to session data (SSH, Tailscale, etc.) -- cclink only transfers the session ID reference
8573

8674
## Constraints
8775

88-
- **Language**: Rust single binary distribution, pubky crate available
89-
- **Identity**: PKARR/Ed25519 reuse existing Pubky identity ecosystem
90-
- **Transport**: Pubky protocol (homeserver + DHT) — no custom relay
91-
- **Encryption**: age with x25519 — lightweight, Ed25519-compatible
76+
- **Language**: Rust -- single binary distribution, pkarr crate available
77+
- **Identity**: PKARR/Ed25519 -- reuse existing Pubky identity ecosystem
78+
- **Transport**: PKARR Mainline DHT -- no custom relay, no accounts, no homeserver
79+
- **Encryption**: age with X25519 -- lightweight, Ed25519-compatible
9280
- **Key storage**: `~/.pubky/secret_key` with 0600 permissions
9381
- **No session content transit**: Only encrypted session ID and metadata cross the network
82+
- **SignedPacket budget**: 912 bytes max JSON in DHT records (1000-byte DNS payload limit)
9483

9584
## Key Decisions
9685

9786
| Decision | Rationale | Outcome |
9887
|----------|-----------|---------|
99-
| Rust single binary | Performance, pubky crate available, easy distribution | ✓ Good — 2,851 LOC, compiles clean |
100-
| age encryption over NaCl box | Simpler API, well-audited, maps cleanly from Ed25519 | ✓ Good — round-trip verified, no plaintext leaks |
101-
| Pubky homeserver transport | No custom relay needed, censorship-resistant, reuses existing infra | ✓ Good — PUT/GET/DELETE all working |
102-
| Latest pointer pattern | Simple way to find most recent handoff without listing all records | ✓ Good — clean single-lookup pickup path |
103-
| ~/.pubky/ key storage | Reuse Pubky ecosystem directory instead of ~/.cclink/ | ✓ Good — consistent with pubky tooling |
104-
| 24h default TTL (not 8h) | More forgiving for cross-timezone handoffs | ✓ Good — context decision in Phase 3 |
105-
| Exec as default behavior | Pickup always runs claude --resume after confirm (not opt-in --exec) | ✓ Good — fewer flags, natural flow |
106-
| burn/recipient as unsigned metadata | Preserve Phase 3 signature compatibility | ✓ Good — backwards-compatible record format |
107-
| httpmock for sync integration tests | Works with reqwest::blocking without tokio runtime conflicts | ✓ Good — 7 integration tests, all #[test] |
108-
| PIN mode deferred to v2 | Low entropy (4 digits); --share provides real access control | — Deferred |
109-
| Sign burn+recipient (clean break) | Old v1.0 records expire via TTL, no migration needed | — Pending |
110-
| --burn + --share mutually exclusive | Recipient can't DELETE owner's record; silent skip is worse | — Pending |
88+
| Rust single binary | Performance, pkarr crate available, easy distribution | ✓ Good |
89+
| age encryption over NaCl box | Simpler API, well-audited, maps cleanly from Ed25519 | ✓ Good |
90+
| PKARR Mainline DHT transport | No accounts, no tokens, no homeserver -- true decentralization | ✓ Good |
91+
| ~/.pubky/ key storage | Reuse Pubky ecosystem directory | ✓ Good |
92+
| 24h default TTL | More forgiving for cross-timezone handoffs | ✓ Good |
93+
| Exec as default behavior | Pickup always runs claude --resume | ✓ Good |
94+
| Signed burn+recipient (clean break) | Old v1.0 records expire via TTL, no migration needed | ✓ Good |
95+
| --burn + --share mutually exclusive | Recipient can't revoke owner's record | ✓ Good |
96+
| PIN mode with Argon2id+HKDF | Real security feature, not just 4-digit PIN | ✓ Good |
97+
| Replace homeserver with DHT | Eliminates signup tokens, accounts, authentication sessions | ✓ Good |
98+
| Encrypt metadata into blob | No hostname/project leakage on DHT | ✓ Good |
99+
| skip_serializing_if on defaults | Saves ~71 bytes in common case for SignedPacket budget | ✓ Good |
111100

112101
---
113-
*Last updated: 2026-02-22 after v1.1 milestone start*
102+
*Last updated: 2026-02-22 after v1.1 milestone*

.planning/REQUIREMENTS.md

Lines changed: 0 additions & 77 deletions
This file was deleted.

0 commit comments

Comments
 (0)