Skip to content

feat(artifact-cas): verify downloads against the requested digest via disk staging - #3461

Merged
migmartri merged 2 commits into
mainfrom
gh-3460-cas-download-spill-verify
Sep 22, 2026
Merged

migmartri merged 2 commits into
mainfrom
gh-3460-cas-download-spill-verify

Conversation

@migmartri

@migmartri migmartri commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

Closes #3460

What

The Artifact CAS now verifies downloads the same way #3365 made it verify uploads: the backend content is staged on local disk under staging_dir, hashed in the same pass and compared against the requested digest, and only then streamed to the client.

This is not a security patch. Digest verification of downloads already happened before this change and a tampered or corrupted blob was already detected on every path: the HTTP handler verified before writing the response, the gRPC Read handler returned an error after the last chunk, and the CLI re-hashes what it receives. What this PR changes is where the server-side check happens and what it costs:

  • Normalization. All three transfers (upload, gRPC download, HTTP download) now go through one shared stageAndVerify helper and verify before forwarding a single byte. The gRPC download previously streamed content to the client while hashing and only reported the mismatch at the end; it now behaves like the HTTP path and like uploads.
  • Memory. The HTTP download handler no longer buffers the whole artifact in memory. It streams the verified staging file and sets Content-Length from the verified size, so CAS memory during downloads is bounded regardless of artifact size, matching what feat(artifact-cas): verify uploaded content against the declared digest #3365 achieved for uploads.

Other changes:

  • The upload path is refactored onto the shared helper with no behaviour change.
  • A download digest mismatch is now reported as gRPC DataLoss / HTTP 500 with a stored content does not match the requested digest: got=…, want=… message, replacing the previous Unauthorized / 401. The fault is backend content, not the caller.
  • Staging files are unlinked as soon as they are created, so an interrupted download cannot leave anything on the staging volume.

Operator notes

  • The staging emptyDir now also holds in-flight downloads. Size ephemeral-storage requests for concurrent uploads and downloads times the largest artifact. Comments in conf.proto, the CAS dev config and the Helm chart were updated accordingly and the chart is patch-bumped.
  • Large gRPC downloads (e.g. chainloop artifact download) show no progress until the server has finished fetching and verifying the artifact, then stream at full speed.

AI disclosure

This change was developed with the assistance of Claude Code.

🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri

… disk staging

Downloads now go through the same integrity gate #3365 introduced for
uploads. The backend content is spilled to an unlinked file under
staging_dir, hashed in the same pass and compared with the requested
digest; only a verified file is streamed to the client, so no unverified
byte leaves the CAS in either direction.

- Add a shared stageAndVerify helper (spill / hash / compare / rewind) and
  refactor the upload path onto it with no behaviour change.
- gRPC ByteStream.Read verifies before the first Send instead of streaming
  while hashing and reporting a mismatch after the last byte.
- HTTP GET /download no longer buffers the whole artifact in memory; it
  streams the verified staging file with Content-Length from the verified
  size, so CAS memory is bounded regardless of artifact size.
- A download mismatch is reported as gRPC DataLoss / HTTP 500 with both
  digests, replacing Unauthorized / 401: the fault is backend content, not
  the caller.
- Update staging_dir docs and the Helm chart comments to cover downloads;
  bump the chart patch version.

Closes #3460

Assisted-by: Claude Code
Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev>

Chainloop-Trace-Sessions: 4b106940-b636-4b56-bfb9-d325464483ca
@chainloop-platform

chainloop-platform Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

AI Session Checks — 🟢 86% · ✅ 0 failing

Avg score Sessions Failing policies Attribution Files Lines Total Duration
🟢 86% 1 ✅ 0 100% AI / 0% Human 16 +579 / -281 7h16m52s

🟢 86% — 100% AI — ✅ All policies passing

Sep 22, 2026 06:59 UTC · 7h16m52s · $29.84 · 8.3k in / 294.4k out · claude-code 2.1.278 (claude-fable-5-1)

View session details ↗

Change Summary

  • Adds server-side staging and digest verification for artifact-cas downloads.
  • Refactors upload verification onto shared staging logic so gRPC and HTTP verify before sending bytes.
  • Replaces HTTP in-memory buffering with staged-file streaming, updates rollout comments/config docs, and incorporates follow-up review fixes.

AI Session Overall Score

🟢 86% — Well-planned, on-task session with strong validation and no user-friction signals.

AI Session Analysis Breakdown

🟢 93% · context-and-planning

🟢 Spec, approval, and TDD guidance were in place before implementation. · High Impact

🟢 90% · alignment

No notes.

🟢 90% · user-trust-signal

🟢 The user advanced through design, implementation, and review follow-up without correction spikes. · High Impact

🟢 88% · scope-discipline

No notes.

🟢 87% · solution-quality

No notes.

🟡 76% · verification

🟢 AI ran failing targeted tests, then broader suites cleanly. · High Impact

🟠 The user stayed active but never confirmed the new download behavior worked. · Medium Severity

💡 When the user is present, ask for one explicit runtime confirmation before calling the change done.


File Attribution

████████████████████ 100% AI / 0% Human

Status Attribution File Lines
modified ai app/artifact-cas/internal/service/bytestream.go +56 / -132
created ai app/artifact-cas/internal/service/staging.go +187 / -0
created ai app/artifact-cas/internal/service/staging_test.go +152 / -0
modified ai app/artifact-cas/internal/service/bytestream_download_test.go +38 / -38
modified ai app/artifact-cas/internal/service/download.go +33 / -38
modified ai app/artifact-cas/internal/service/download_test.go +51 / -14
modified ai app/artifact-cas/cmd/main.go +14 / -14
modified ai deployment/chainloop/values.yaml +12 / -9
modified ai app/artifact-cas/internal/service/service.go +9 / -8
modified ai app/artifact-cas/internal/service/bytestream_test.go +6 / -8
modified ai app/artifact-cas/internal/conf/conf.proto +5 / -5
modified ai deployment/chainloop/templates/cas/deployment.yaml +5 / -5
modified ai deployment/chainloop/templates/_helpers.tpl +4 / -4
modified ai app/artifact-cas/configs/config.devel.yaml +4 / -3
modified ai deployment/chainloop/templates/cas/configmap.yaml +2 / -2
modified ai deployment/chainloop/Chart.yaml +1 / -1

Policies (4)

Status Policy Material Messages
✅ Passed ai-config-ai-agents-allowed ai-coding-session-4b1069 -
✅ Passed ai-config-no-dangerous-commands ai-coding-session-4b1069 -
✅ Passed ai-config-no-secrets ai-coding-session-4b1069 -
✅ Passed ai-config-mcp-servers-allowed ai-coding-session-4b1069 -

Security Checks — ⚠️ 1 failing

✅ secret-scan

Status Policy Messages
✅ Passed secrets-detection -

✅ sast-scan

Status Policy Messages
✅ Passed owasp-top10-2025 -
✅ Passed sast -
✅ Passed cwe-top25 -
✅ Passed cwe-top26-40-cusp -

⚠️ iac-scan — 1 failing

Status Policy Messages
⚠️ Failed iac-misconfiguration
  • Base64 High Entropy String in "app/artifact-cas/configs/config.devel.yaml" (error)
  • Base64 High Entropy String in "deployment/chainloop/values.yaml" (error)

PR info

Status Policy Messages
✅ Passed pr-min-approvals -
✅ Passed pr-description-required -
✅ Passed pr-user-story-linked -

⏭️ 2 scans not applied

Scan Reason
vulnerability-scan no manifest/lockfile changed
github-actions-scan no workflow files changed

View attestation ↗
View attestation ↗


Powered by Chainloop and Chainloop Trace

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 17 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread app/artifact-cas/internal/service/bytestream_download_test.go
@migmartri
migmartri requested a review from a team September 22, 2026 14:13
The download adapter was renamed from streamWriter to sendWriter in the
staging refactor; the test double's comment still used the old name.

Assisted-by: Claude Code
Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev>

Chainloop-Trace-Sessions: 4b106940-b636-4b56-bfb9-d325464483ca
@migmartri
migmartri requested a review from a team September 22, 2026 18:39
@migmartri
migmartri merged commit 59d1818 into main Sep 22, 2026
16 of 17 checks passed
@migmartri
migmartri deleted the gh-3460-cas-download-spill-verify branch September 22, 2026 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

artifact-cas: verify downloads against the requested digest via disk staging before sending to the client

2 participants