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
2 changes: 1 addition & 1 deletion TASK_CACHE_PARITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ outside this tracker.
- [x] Add remote read-only, write-only, and read/write modes
- [x] Add authentication and repository or organization namespaces
- [x] Add timeouts, retries, request deduplication, and offline fallback
- [ ] Verify remote artifact integrity and authenticity
- [x] Verify remote artifact integrity and authenticity
- [ ] Document secret handling for cached logs and artifacts
- [ ] Provide a self-hosting reference or compatibility test suite

Expand Down
39 changes: 35 additions & 4 deletions src/task/task_cache_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -795,10 +795,8 @@ impl TaskCacheStore for HttpTaskCacheStore {
&manifest,
artifact.as_ref().map(TaskCacheStoreArtifact::path),
)?);
Ok(Some(TaskCacheStoreEntry {
manifest: serde_json::to_vec(&manifest)?,
artifact,
}))
let manifest = serde_json::to_vec(&manifest)?;
Ok(Some(TaskCacheStoreEntry { manifest, artifact }))
}

fn begin_write(&self, key: &str) -> Result<TaskCacheStoreWrite> {
Expand Down Expand Up @@ -1361,6 +1359,7 @@ mod tests {
let insecure: Url = "http://cache.example.com".parse().unwrap();
assert!(validate_remote_url(&insecure, true).is_err());
validate_remote_url(&insecure, false).unwrap();
assert!(validate_remote_url(&"ftp://localhost/cache".parse().unwrap(), false).is_err());
}

struct FailingTaskCacheStore {
Expand Down Expand Up @@ -1726,6 +1725,38 @@ mod tests {
metadata_get.assert_async().await;
}

#[tokio::test]
async fn http_store_rejects_corrupt_cas_blobs() {
let mut server = mockito::Server::new_async().await;
let digest = CacheDigest::blake3(b"expected bytes");
let blob_path = format!("/v1/blobs/blake3/{}/{}", digest.hash, digest.size);
let blob_get = server
.mock("GET", blob_path.as_str())
.match_header("mise-cache-protocol", "1")
.match_header("mise-cache-namespace", "test-namespace")
.with_status(200)
.with_body("substituted bytes")
.expect(1)
.create_async()
.await;
let staging = tempfile::tempdir().unwrap();
let store = HttpTaskCacheStore {
base_url: normalized_base_url(server.url().parse().unwrap()),
namespace: "test-namespace".into(),
staging_dir: staging.path().to_path_buf(),
client: reqwest::Client::new(),
authorization: None,
};

let err = store
.get_blob(&digest, REMOTE_CACHE_BLOB_MEDIA_TYPE)
.await
.unwrap_err();

assert!(err.to_string().contains("failed digest verification"));
blob_get.assert_async().await;
}

#[tokio::test]
async fn composite_store_local_removal_preserves_and_repromotes_remote_entry() {
let local_root = tempfile::tempdir().unwrap();
Expand Down
Loading