Add optional AES-GCM encryption for the disk cache tier - #309
Merged
Merged
Conversation
Encrypts response.record (whole-blob AES-GCM) and data.record (chunked STREAM-construction AES-GCM, matching how the response body is already written incrementally) when a DataCache.EncryptionKey is supplied via .cache(encryptionKey:) or DataCache.encryptionKey. Key management stays the app's responsibility; a decrypt failure is always a cache miss, never a crash. Follows up on the design discussed in #252. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…sis-5e3dce # Conflicts: # Package.swift
Matches this manifest's from: convention used for every other dependency, instead of the explicit 3.0.0..<5.0.0 range mirrored from swift-nio-extras. Resolves to the same already-locked 4.5.1. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…sis-5e3dce # Conflicts: # Tests/RequestDLTests/Properties/Sources/Cache/Cached Request/CachedRequestTests.swift
o-nnerb
marked this pull request as ready for review
August 29, 2026 10:24
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #309 +/- ##
=======================================
Coverage 98.49% 98.49%
=======================================
Files 4 4
Lines 995 995
=======================================
Hits 980 980
Misses 15 15 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
4 tasks
o-nnerb
added a commit
that referenced
this pull request
Sep 3, 2026
…e.resource (#314) ## Summary Two independent, self-contained changes surfaced by a feature-scout pass over the codebase post-main (SPKI pinning #248, SSLKeyLogger/SSLPSKIdentityResolver #312, disk cache work #309/#311, CacheHeader #310): - **SPKI pinning × `enableNetworkFramework(_:)`** — this combination already behaved safely (RequestDL silently falls back to plain SwiftNIO instead of Network.framework when SPKI pinning is configured, since AsyncHTTPClient's NIOTransportServices bridge never consults `SPKIPinningConfiguration`), but it was undocumented and had zero test coverage. Cross-references the two call sites (`Session.enableNetworkFramework(_:)`, `SPKIPinning`) and adds 3 regression tests locking in the actual fallback behavior. - **`Timeout.Source.resource`** — a total end-to-end request deadline mirroring `URLSessionConfiguration.timeoutIntervalForResource` (connect + redirects + entire body transfer, as one budget), which neither AsyncHTTPClient nor `URLSessionConfiguration`'s own per-phase timeouts offer as a single knob. Deliberately excluded from `.all`, since it means something different (a ceiling on the whole request) than the existing per-phase `.connect`/`.read` cases — folding it in would silently give every existing `.all` caller a resource-wide deadline they never asked for. - New `Internals.ResourceDeadline` races an operation against the deadline via `withThrowingTaskGroup`, cancelling the request's `Internals.TaskSeed` (the same mechanism that already cancels when a caller drops or breaks out of a response stream) and throwing the new public `ResourceTimeoutError`. - `RawTask` races the initial connect/redirect phase; `AsyncResponse.Iterator`/`AsyncBytes.AsyncIterator` race every subsequent read against the same shared deadline, so the budget covers the request's full lifetime, not just the initial `result()` call. - Uses `DispatchTime.now().uptimeNanoseconds` rather than `ContinuousClock` on Darwin, matching `Internals.Storage`/`Internals.ClientManager`'s existing pattern — `ContinuousClock` needs macOS 13/iOS 16, newer than this package's macOS 12/iOS 15 floor. ## Test plan - [x] `swift build` — clean - [x] `swift test` — 900 + 366 tests passing, no regressions - [x] `swift format lint --recursive --strict Sources Tests` — clean - [x] New coverage: 3 tests for the SPKI/Network.framework fallback; 9 tests for `.resource` (5 unit tests on the race mechanism itself including seed-cancellation, 2 wiring tests, 2 real-network `LocalServer` tests — one proving an already-elapsed deadline actually cancels a live in-flight request) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: brennobemoura <37243584+brennobemoura@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2 tasks
o-nnerb
added a commit
that referenced
this pull request
Sep 4, 2026
## Summary - The cache encryption doc only linked to the API with no worked example, so it wasn't clear how to actually turn it on or generate a key — this fills that gap. - Adds a full walkthrough: what is/isn't encrypted (disk tier only), the shared-key gotcha where omitting `encryptionKey` on any `.cache(...)` call silently clears it for the whole directory, and decrypt-failure/key-rotation behavior (silent miss, never a crash). - Adds three concrete ways to generate the key: on-device random bytes, deriving via HKDF from a secret issued elsewhere (backend/KMS/UUID), and why a user password needs a slow KDF instead of HKDF. - Fixes the `cache(...)` symbol links in this doc, which were missing `encryptionKey:` since it was added to the signature in #309. ## Test plan - [x] Docs-only change; reviewed rendered Markdown by eye for correctness against the actual `DataCache`/`DiskStorage` implementation. - [ ] Optionally build the DocC catalog to confirm the fixed symbol links resolve. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: brennobemoura <37243584+brennobemoura@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
DataCache's disk tier, following up on the design discussed in Feature Proposal: Encrypted Disk Cache #252 (and revised through that discussion afterdata.record's incremental, chunk-by-chunk write pattern turned out to be incompatible with the original whole-blob proposal).response.record(small JSON metadata) uses whole-blob AES-GCM.data.record(the response body) uses a chunked STREAM-construction scheme (4 MiB chunks, counter-derived nonces, AAD binding chunk index + finality) so peak memory stays bounded regardless of body size, matching the existing incremental-write design.DataCache.EncryptionKey(raw bytes orCrypto.SymmetricKey), settable via.cache(encryptionKey:)orDataCache.encryptionKey.nil(default) leaves the disk tier exactly as unencrypted as before. A decrypt failure — wrong/rotated key, corrupted or tampered file — is always a cache miss, never a crash.swift-cryptodependency (already resolved transitively viaswift-nio-extras, same version range, so the locked version doesn't move).Test plan
Internals.EncryptedFileStreamBuffer/Internals.EncryptedFileBufferURL: multi-chunk round-trip, empty body, plaintext-size accounting, tamper detection (byte flip, chunk reorder, truncation), wrong key, concurrency stress test.DiskStorageTestswith encryption-enabled coverage: ciphertext-on-disk, round-trip, key rotation, corrupted metadata, eviction accounting.CachedRequestTestswith an end-to-end test proving a real cache hit occurs through the encrypted disk tier (verified this test actually catches the critical plaintext-vs-ciphertext size regression by deliberately breaking it and confirming failure, then reverting).RequestDLlibrary product is clean.🤖 Generated with Claude Code