Conversation
Greptile SummaryThis WIP PR adds request-time data encryption to the S2 platform, primarily targeting S2 Lite. Clients supply an encryption header containing an algorithm identifier and a 32-byte hex key; the server encrypts records at append time and decrypts at read time using the per-request key with the basin and stream name as AEAD additional-authenticated data. An Confidence Score: 5/5Safe to merge as a WIP; all remaining findings are P2 style/hygiene items that do not block correctness. The encryption logic is sound: authenticated encryption with stream-bound AAD, version and algorithm bytes embedded in ciphertext, and a comprehensive test suite covering wrong-key, wrong-AAD, truncated ciphertext, and bit-flip scenarios. The only unaddressed findings are a P2 zeroization hygiene issue and a misleading test name, neither of which affects runtime correctness or security in a meaningful way for a WIP branch.
Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant LiteServer as S2 Lite Server
participant Storage
Note over Client,Storage: Append with server-side encryption
Client->>LiteServer: POST /records (encryption header with alg + key)
LiteServer->>LiteServer: encrypt_append_input()
LiteServer->>Storage: Persist ciphertext records
LiteServer-->>Client: AppendAck
Note over Client,Storage: Read with server-side decryption
Client->>LiteServer: GET /records (encryption header with key)
LiteServer->>Storage: Fetch ciphertext records
LiteServer->>LiteServer: decrypt_read_batch()
LiteServer-->>Client: Plaintext records
Note over Client,Storage: Attest mode
Client->>LiteServer: POST /records (attest header, pre-encrypted body)
LiteServer->>Storage: Persist as-is
LiteServer-->>Client: AppendAck
Prompt To Fix All With AIThis is a comment left during a code review.
Path: common/src/encryption.rs
Line: 177-192
Comment:
**Key bytes copied to unzeroized stack array**
`hex::decode` writes into `key_bytes` (a `Vec`), which is correctly zeroized before returning. However, the `try_into()` call at line 180 **copies** those bytes into `key_array: [u8; 32]`, a plain stack-allocated array. This copy is never zeroized — a Rust move does not clear the source location, so the raw key material can linger on the stack until the frame is overwritten.
A cleaner approach is to decode directly into a heap-allocated `KeyBytes` struct, avoiding the intermediate stack copy entirely:
```rust
let mut key_box = Box::new(KeyBytes([0u8; 32]));
hex::decode_to_slice(key_hex, &mut key_box.0)
.map_err(|e| EncryptionError::MalformedHeader(format!("key is not valid hex: {e}")))?;
Ok(SecretBox::new(key_box))
```
This keeps the key bytes only in the heap-allocated box, which `KeyBytes`'s `Zeroize` impl will clear on drop.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: common/src/encryption.rs
Line: 654-663
Comment:
**Misleading test name**
The test is named `parse_header_malformed_no_semicolon`, but the header value `"alg=aegis-256"` does not fail due to a missing semicolon. Splitting by `;` yields one part — `"alg=aegis-256"` — which is parsed successfully as `alg=Some(Aegis256)`. The error is actually `MalformedHeader("missing 'key=' parameter")`. Consider renaming to something like `parse_header_alg_only_missing_key` to accurately reflect which validation fails.
How can I resolve this? If you propose a fix, please make it concise.Reviews (2): Last reviewed commit: "chore: deduplicate resolve_encryption/re..." | Re-trigger Greptile |
…r layer Drop seq_num from AAD per team decision. AAD is now stream_id (BLAKE3 hash of basin+stream), matching StreamId::new in lite. This allows encryption to happen in the HTTP handler before the backend, removing all encryption plumbing from the streamer pipeline. - Replace effective_aad_v1(base, seq_num) with stream_id_aad(basin, stream) - Replace encrypt_sequenced_records with encrypt_append_input (pre-sequencing) - Remove AppendEncryption struct from streamer/backend - Encrypt in handler before backend.append, decrypt after backend.read Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These tests manually encrypted before backend.append() and decrypted after backend.read(), but encryption now happens in the handler layer. The roundtrip logic is already covered by unit tests in common/src/encryption.rs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Compute stream_id AAD lazily (only when encryption header present) - Remove unused secrecy dev-dependency from lite Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously, --encryption-algorithm was silently ignored when no key was provided, which could lead users to believe encryption was enabled. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The algorithm is auto-detected from the alg_id byte in the ciphertext envelope during decryption, so specifying it on reads is unnecessary and confusing. Split EncryptionArgs into EncryptionArgs (append, with algorithm) and DecryptionArgs (read/tail, key only). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…roup Replace runtime check with clap's `requires = "encryption_key_source"` group constraint, so clap rejects the invalid combination at parse time. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Algorithm is only needed for encryption (appends). For decryption (reads/tail), the alg_id byte in the ciphertext envelope is authoritative. Making alg Optional removes the need to hardcode a dummy algorithm on the decrypt path. - EncryptionDirective::Key.alg: Option<EncryptionAlgorithm> - SDK EncryptionConfig::Key.alg: Option<EncryptionAlgorithm> - Header format supports key-only: "key=<hex>" (no alg) - Lite handler requires alg on append, ignores on read - CLI resolve_decryption passes alg: None Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… helpers - Extract shared resolve_encryption_config to eliminate duplication - Rename make_key_fn/make_wrong_key_fn to test_key/wrong_test_key Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4729624 to
36d8109
Compare
No description provided.