Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [5.2.1] — Carousel polish (2026-02-28)

### Added
- CLI reference in `docs/API.md` for `git cas rotate` and `git cas vault rotate` flags.

### Changed
- Rotation helpers in `CasService` use native `#private` methods, matching the facade's style.
- `VAULT_CONFLICT` and `VAULT_METADATA_INVALID` error code docs now list `rotateVaultPassphrase()`.

### Fixed
- `rotateVaultPassphrase` now honours `kdfOptions.algorithm` instead of silently using the old algorithm.
- Rotation integration test no longer flaps under CI load (reduced test-only KDF iterations).

## [5.2.0] — Carousel (2026-02-28)

### Added
- **Key rotation without re-encrypting data** — `CasService.rotateKey()` re-wraps the DEK with a new KEK, leaving data blobs untouched. Enables key compromise response without re-storing assets.
- **`keyVersion` tracking** — manifest-level and per-recipient `keyVersion` counters track rotation history for audit compliance. Optional field, backward-compatible with existing manifests.
- **`git cas rotate` CLI command** — rotate a recipient's key via `--slug` (vault round-trip) or `--oid` (manifest-only). Supports `--label` for targeted single-recipient rotation.
- **`rotateVaultPassphrase()`** — rotate the vault-level encryption passphrase across all envelope-encrypted entries in a single atomic commit. Non-envelope entries are skipped with reporting.
- **`git cas vault rotate` CLI command** — rotate vault passphrase from the command line with `--old-passphrase` and `--new-passphrase`.
- **`ROTATION_NOT_SUPPORTED` error code** — thrown when `rotateKey()` is called on a manifest without envelope encryption (legacy/direct-key).
- 27 new unit tests covering key rotation, schema validation, and vault passphrase rotation.

## [5.1.0] — Locksmith (2026-02-28)

### Added
Expand Down
13 changes: 13 additions & 0 deletions COMPLETED_TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ Task cards moved here from ROADMAP.md after completion. Organized by milestone.

---

# M12 — Carousel (v5.2.0) ✅ CLOSED

**Theme:** Key rotation without re-encrypting data. Rotate recipient keys or vault passphrases by re-wrapping the DEK, leaving data blobs untouched.

**Completed:** v5.2.0 (2026-02-28)

- **Task 12.1:** Key rotation workflow — `CasService.rotateKey({ manifest, oldKey, newKey, label? })` unwraps DEK with `oldKey`, re-wraps with `newKey`. Data blobs never accessed. `keyVersion` counter tracks rotation history. Legacy (non-envelope) manifests throw `ROTATION_NOT_SUPPORTED`.
- **Task 12.2:** Key version tracking in manifest — `keyVersion` field (non-negative integer, default 0) at manifest-level and per-recipient. `rotateKey()` increments both counters. Old manifests without `keyVersion` treated as version 0 (backward compatible).
- **Task 12.3:** CLI key rotation commands — `git cas rotate --slug <slug> --old-key-file <path> --new-key-file <path> [--label <label>]`. Also supports `--oid <tree-oid>` for manifest-only rotation without vault round-trip.
- **Task 12.4:** Vault-level key rotation — `rotateVaultPassphrase({ oldPassphrase, newPassphrase, kdfOptions? })` re-wraps every envelope-encrypted entry's DEK in a single atomic commit. `git cas vault rotate` CLI command. CAS retry on `VAULT_CONFLICT`.

---

# M10 — Hydra (v5.0.0) ✅ CLOSED

**Theme:** Content-defined chunking for dramatically better dedup on versioned files.
Expand Down
68 changes: 68 additions & 0 deletions GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ along from first principles to full mastery.
9. [Observability](#9-observability)
10. [Compression](#10-compression)
11. [Passphrase Encryption (KDF)](#11-passphrase-encryption-kdf)
11b. [Multi-Recipient Encryption & Key Rotation](#11b-multi-recipient-encryption--key-rotation)
12. [Merkle Manifests](#12-merkle-manifests)
13. [Vault](#13-vault)
14. [Architecture](#14-architecture)
Expand Down Expand Up @@ -912,6 +913,73 @@ const manifest = await cas.storeFile({

---

## 11b. Multi-Recipient Encryption & Key Rotation

*New in v5.1.0 (recipients), v5.2.0 (rotation).*

### Envelope Encryption

Instead of encrypting with a single key, you can encrypt for multiple recipients. A random DEK encrypts the data; each recipient's KEK wraps the DEK:

```javascript
const manifest = await cas.store({
source, slug: 'shared', filename: 'shared.bin',
recipients: [
{ label: 'alice', key: aliceKey },
{ label: 'bob', key: bobKey },
],
});
```

Any recipient can restore independently:

```javascript
const { buffer } = await cas.restore({ manifest, encryptionKey: bobKey });
```

### Key Rotation

When a key is compromised, rotate it without re-encrypting data:

```javascript
const rotated = await cas.rotateKey({
manifest, oldKey: aliceOldKey, newKey: aliceNewKey, label: 'alice',
});
// Persist the updated manifest
const treeOid = await cas.createTree({ manifest: rotated });
```

The `keyVersion` counter increments with each rotation:

```javascript
console.log(rotated.encryption.keyVersion); // 1
console.log(rotated.encryption.recipients[0].keyVersion); // 1
```

### Vault Passphrase Rotation

Rotate the master passphrase for all vault entries at once:

```javascript
const { commitOid, rotatedSlugs, skippedSlugs } = await cas.rotateVaultPassphrase({
oldPassphrase: 'old-secret', newPassphrase: 'new-secret',
});
```

Non-envelope entries (direct-key encryption) are skipped — they require manual re-store.

### CLI

```bash
# Rotate a single recipient's key
git cas rotate --slug shared --old-key-file old.key --new-key-file new.key --label alice

# Rotate vault passphrase
git cas vault rotate --old-passphrase old-secret --new-passphrase new-secret
```

---

## 12. Merkle Manifests

*New in v2.0.0.*
Expand Down
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ We use the object database.
- **Chunked storage** big files become stable, reusable blobs. Fixed-size or content-defined chunking (CDC).
- **Optional AES-256-GCM encryption** store secrets without leaking plaintext into the ODB.
- **Multi-recipient encryption** envelope model (DEK/KEK) — add/remove access without re-encrypting data.
- **Key rotation** rotate keys without re-encrypting data blobs. Respond to compromise in seconds.
- **Compression** gzip before encryption — smaller blobs, same round-trip.
- **Passphrase encryption** derive keys from passphrases via PBKDF2 or scrypt — no raw key management.
- **Merkle manifests** large files auto-split into sub-manifests for scalability.
Expand All @@ -37,6 +38,32 @@ We use the object database.

<img src="./docs/demo.gif" alt="git-cas demo" />

## What's new in v5.2.0

**Key rotation without re-encrypting data** — Rotate a recipient's key by re-wrapping the DEK. Data blobs are never touched. Respond to key compromise in seconds, not hours.

```js
// Rotate a single recipient's key
const rotated = await cas.rotateKey({
manifest, oldKey: aliceOldKey, newKey: aliceNewKey, label: 'alice',
});

// Rotate the vault passphrase (all entries, atomic commit)
const { commitOid, rotatedSlugs, skippedSlugs } = await cas.rotateVaultPassphrase({
oldPassphrase: 'old-secret', newPassphrase: 'new-secret',
});
```

```bash
# Rotate a recipient key
git cas rotate --slug prod-secrets --old-key-file old.key --new-key-file new.key

# Rotate vault passphrase
git cas vault rotate --old-passphrase old-secret --new-passphrase new-secret
```

See [CHANGELOG.md](./CHANGELOG.md) for the full list of changes.

## What's new in v5.1.0

**Multi-recipient envelope encryption** — Each file is encrypted with a random DEK; recipient KEKs wrap the DEK. Add or remove team members without re-encrypting data.
Expand Down Expand Up @@ -225,6 +252,13 @@ git cas recipient list shared
git cas recipient add shared --label carol --key-file ./keys/carol.key --existing-key-file ./keys/alice.key
git cas recipient remove shared --label bob

# Key rotation (no re-encryption)
git cas rotate --slug shared --old-key-file old.key --new-key-file new.key
git cas rotate --slug shared --old-key-file old.key --new-key-file new.key --label alice

# Vault passphrase rotation
git cas vault rotate --old-passphrase old-secret --new-passphrase new-secret

# Encrypted vault round-trip (passphrase via env var or --vault-passphrase flag)
export GIT_CAS_PASSPHRASE="secret"
git cas vault init
Expand Down Expand Up @@ -254,7 +288,7 @@ That's git-cas. The orphan branch gives you none of:

| | Orphan branch | git-cas |
|---|---|---|
| **Encryption** | None — plaintext forever in history | AES-256-GCM + passphrase KDF + multi-recipient |
| **Encryption** | None — plaintext forever in history | AES-256-GCM + passphrase KDF + multi-recipient + key rotation |
| **Large files** | Bloats `git clone` for everyone | Chunked, restored on demand |
| **Dedup** | None | Chunk-level content addressing |
| **Integrity** | Git SHA-1 | SHA-256 per chunk + GCM auth tag |
Expand Down
Loading