Shows path, type, status, mode, hash, timestamps, encryption,
lock state, and targeting labels for a single tracked file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tightened lint config (added copyloopvar, durationcheck, makezero,
nilerr, bodyclose). Added 3 combo tests: encrypted+locked files,
dir-only+locked entries, lock/unlock toggle on encrypted entries.
Fixed stale API signatures in ARCHITECTURE.md. All tests already
used t.TempDir() and AddOptions{} consistently.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RotateDEK generates a new DEK, re-encrypts all encrypted blobs, and
re-wraps with all existing KEK slots (passphrase + FIDO2). CLI wired
as `sgard encrypt rotate-dek`. 4 tests covering rotation, persistence,
FIDO2 re-wrap, and requires-unlock guard.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Locked files (--lock): repo-authoritative entries. Checkpoint skips
them (preserves repo version). Status reports "drifted" instead of
"modified". Restore always overwrites if hash differs, no prompt.
Use case: system-managed files the OS overwrites.
Directory-only entries (--dir): track directory itself without
recursing. Restore ensures directory exists with correct permissions.
Use case: directories that must exist but contents are managed
elsewhere.
Add refactored to use AddOptions struct (Encrypt, Lock, DirOnly)
instead of variadic bools.
Proto: ManifestEntry gains locked field. convert.go updated.
7 new tests. ARCHITECTURE.md and README.md updated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Slots are a map keyed by user-chosen label. One passphrase slot
(universal fallback), zero or more fido2/<label> slots (default to
hostname, overridable via --label).
FIDO2 slots carry credential_id to match connected devices without
prompting for touch. Unlock tries all fido2/* slots first, falls
back to passphrase.
CLI: add-fido2 [--label], remove-slot, list-slots, change-passphrase.
New FIDO2 slots propagate to server on next push.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wrapped DEKs and salts stored inline as base64 in the manifest's
encryption section. No separate files (encryption.yaml, salt files,
dek.enc.*) — the manifest is fully self-contained.
Pulling to a new machine gives you everything needed to decrypt.
Server never has the DEK. FIDO2 cross-machine note: device-bound
hmac-secret requires add-fido2 on each machine; passphrase fallback
enables cross-machine decryption.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Automatic unlock resolution: try FIDO2 first (no typing, just touch),
fall back to passphrase if device not present. User never specifies
which method — sgard reads encryption.yaml and walks sources in order.
encrypt init --fido2 creates both sources (FIDO2 primary + passphrase
fallback) to prevent lockout on FIDO2 key loss. Separate salt files
per KEK source.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Encryption is per-file (--encrypt flag on add), not per-repo. A repo
can have a mix of encrypted and plaintext blobs. Commands that only
touch plaintext entries never prompt for the DEK.
Manifest signing deferred — the trust model (which key signs, how do
pulling clients verify across multiple machines) needs proper design.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two-layer key hierarchy: DEK (random, encrypts blobs) wrapped by
KEK (derived from passphrase via Argon2id or FIDO2 hmac-secret).
XChaCha20-Poly1305 for both blob encryption and DEK wrapping.
Post-encryption hashing (manifest hash = SHA-256 of ciphertext).
Plaintext hash stored separately for efficient status checks.
Multiple KEK sources per repo. Server never sees the DEK.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two rejection paths: expired-but-valid tokens get a ReauthChallenge
with a server-generated nonce (fast path, saves a round trip).
Invalid/corrupted tokens get plain Unauthenticated (client falls back
to full self-generated auth flow).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two-layer auth: SSH key signing to obtain a 30-day JWT, then
token-based auth for all subsequent requests. Auto-renewal is
transparent — client interceptor catches Unauthenticated, re-signs,
caches new token, retries. Server is stateless (JWT signed with
repo-local secret key). Token cached at $XDG_STATE_HOME/sgard/token.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ARCHITECTURE.md: move mirror/prune to local command table, fix
remove description (prune cleans blobs, not checkpoint), fix
Phase 2 section to only list remote commands
- README.md: add --force to mirror down, fix prune --remote
description, build instructions include both binaries
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>