Registry.pullBlob accepts resumed 206 responses for the wrong byte range
Target: openai/tart at 6ada2b955d5af1724751056753f22a69f9fe00d1
Lane: Tart registry/OCI/fetcher/push/pull/credential/network error handling
Summary
When Registry.pullBlob resumes a blob request with rangeStart != 0, it sends Range: bytes=<rangeStart>- and requires HTTP 206, but it never verifies the response Content-Range header. A server can return 206 Partial Content for a different range, such as bytes 0-2/6, and Tart streams those bytes to the caller as if they start at the requested offset.
For disk-layer resume, this can feed duplicate or wrong compressed bytes into the LZ4 decompressor and turn a registry protocol error into a later decompression/retry failure. The registry layer should reject the mismatched range before handing any bytes to downstream consumers.
Root Cause
Sources/tart/OCI/Registry.swift changes the expected status to 206 and sends a Range header when rangeStart != 0, but after status validation it immediately streams the body:
if rangeStart != 0 {
expectedStatusCode = HTTPCode.PartialContent
headers["Range"] = "bytes=\(rangeStart)-"
}
...
for try await part in channel {
try await handler(part)
}
There is no check that Content-Range starts at the requested offset.
Red Evidence
Proof worktree: /tmp/tart-source-bug-range-content
Added a focused local-registry test:
func testPullBlobRejectsPartialContentWithWrongStartOffset() async throws
The test calls pullBlob(digest, rangeStart: 3) while the fake registry returns:
HTTP/1.1 206 OK
Content-Range: bytes 0-2/6
Red command:
swift test --filter RegistryRangeVerificationTests/testPullBlobRejectsPartialContentWithWrongStartOffset
Red result:
failed - pullBlob should reject a 206 response whose Content-Range starts at the wrong offset
Executed 1 test, with 1 failure
The failure proves Tart accepted a resumed response for the wrong byte range.
Minimal Proof Fix
After the status-code check, require a Content-Range header whose start offset matches rangeStart before streaming the body:
+ case UnexpectedContentRange(expectedStart: Int64, actual: String)
+ if rangeStart != 0 {
+ guard let contentRange = response.value(forHTTPHeaderField: "Content-Range"),
+ contentRange.starts(with: "bytes \(rangeStart)-") else {
+ throw RegistryError.UnexpectedContentRange(...)
+ }
+ }
Green Evidence
Focused green:
swift test --filter RegistryRangeVerificationTests/testPullBlobRejectsPartialContentWithWrongStartOffset
Result:
Executed 1 test, with 0 failures
Broader non-live suite:
swift test --skip LayerizerTests --skip RegistryTests
Result:
Executed 42 tests, with 0 failures
Classification
accepted_source_bug
Registry.pullBlob accepts resumed 206 responses for the wrong byte range
Target: openai/tart at
6ada2b955d5af1724751056753f22a69f9fe00d1Lane: Tart registry/OCI/fetcher/push/pull/credential/network error handling
Summary
When
Registry.pullBlobresumes a blob request withrangeStart != 0, it sendsRange: bytes=<rangeStart>-and requires HTTP 206, but it never verifies the responseContent-Rangeheader. A server can return206 Partial Contentfor a different range, such asbytes 0-2/6, and Tart streams those bytes to the caller as if they start at the requested offset.For disk-layer resume, this can feed duplicate or wrong compressed bytes into the LZ4 decompressor and turn a registry protocol error into a later decompression/retry failure. The registry layer should reject the mismatched range before handing any bytes to downstream consumers.
Root Cause
Sources/tart/OCI/Registry.swiftchanges the expected status to206and sends aRangeheader whenrangeStart != 0, but after status validation it immediately streams the body:There is no check that
Content-Rangestarts at the requested offset.Red Evidence
Proof worktree:
/tmp/tart-source-bug-range-contentAdded a focused local-registry test:
The test calls
pullBlob(digest, rangeStart: 3)while the fake registry returns:Red command:
swift test --filter RegistryRangeVerificationTests/testPullBlobRejectsPartialContentWithWrongStartOffsetRed result:
The failure proves Tart accepted a resumed response for the wrong byte range.
Minimal Proof Fix
After the status-code check, require a
Content-Rangeheader whose start offset matchesrangeStartbefore streaming the body:Green Evidence
Focused green:
swift test --filter RegistryRangeVerificationTests/testPullBlobRejectsPartialContentWithWrongStartOffsetResult:
Broader non-live suite:
swift test --skip LayerizerTests --skip RegistryTestsResult:
Classification
accepted_source_bug