Skip to content

Add optional AES-GCM encryption for the disk cache tier - #309

Merged
o-nnerb merged 6 commits into
mainfrom
claude/proposal-analysis-5e3dce
Aug 29, 2026
Merged

o-nnerb merged 6 commits into
mainfrom
claude/proposal-analysis-5e3dce

Conversation

@o-nnerb

@o-nnerb o-nnerb commented Aug 27, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds optional, consumer-keyed AES-GCM encryption for DataCache's disk tier, following up on the design discussed in Feature Proposal: Encrypted Disk Cache #252 (and revised through that discussion after data.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.
  • New public API: DataCache.EncryptionKey (raw bytes or Crypto.SymmetricKey), settable via .cache(encryptionKey:) or DataCache.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.
  • Adds a direct swift-crypto dependency (already resolved transitively via swift-nio-extras, same version range, so the locked version doesn't move).

Test plan

  • New unit tests for 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.
  • Extended DiskStorageTests with encryption-enabled coverage: ciphertext-on-disk, round-trip, key rotation, corrupted metadata, eviction accounting.
  • Extended CachedRequestTests with 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).
  • Full suite: 838 + 357 tests passing, zero failures.
  • Release build of the RequestDL library product is clean.

🤖 Generated with Claude Code

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>
@o-nnerb o-nnerb added the breaking-changes This PR is a new major version release label Aug 27, 2026
o-nnerb and others added 3 commits August 29, 2026 06:07
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
o-nnerb marked this pull request as ready for review August 29, 2026 10:24
o-nnerb and others added 2 commits August 29, 2026 07:30
@codecov

codecov Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.49%. Comparing base (34b37e7) to head (ecf20b2).

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@o-nnerb
o-nnerb merged commit 4b73e54 into main Aug 29, 2026
19 checks passed
@o-nnerb
o-nnerb deleted the claude/proposal-analysis-5e3dce branch August 29, 2026 10:53
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>
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-changes This PR is a new major version release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant