fix(cloud): handle unsigned S3 metadata headers in presigned URL uploads#1314
fix(cloud): handle unsigned S3 metadata headers in presigned URL uploads#1314
Conversation
Signed-off-by: Kent Huang <kent@infuseai.io>
There was a problem hiding this comment.
Pull request overview
Fixes the staging Recce Cloud smoke test workflow to use the correct GitHub token secret and standardizes YAML quoting in the workflow triggers.
Changes:
- Update the staging smoke test job to use
secrets.RECCE_CLOUD_TOKENinstead ofsecrets.RECCE_CLOUD_TOKEN_STAGING. - Normalize YAML string quoting for the scheduled cron expression and workflow dispatch input description.
…loads The cloud backend may generate presigned URLs that don't sign x-amz-tagging and x-amz-meta-* headers. Sending unsigned headers causes S3 to reject with AccessDenied. This adds filter_headers_for_presigned_url() which parses X-Amz-SignedHeaders from the presigned URL and drops only unsigned metadata headers. SSE-C encryption headers are never filtered — stripping them partially causes 'InvalidArgument: must provide an appropriate secret key'. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Kent Huang <kent@infuseai.io>
Codecov Report✅ All modified and coverable lines are covered by tests.
... and 4 files with indirect coverage changes 🚀 New features to boost your workflow:
|
Signed-off-by: Kent Huang <kent@infuseai.io>
Add integration-level tests for the two uncovered call sites: - upload_dbt_artifacts() in artifact.py - RecceCloudStateManager._upload_state_to_recce_cloud() in cloud.py Both verify that unsigned metadata headers are dropped while SSE-C headers are preserved when the presigned URL doesn't sign them. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Kent Huang <kent@infuseai.io>
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. 🤖 Generated with Claude Code |
Code Review — PR #1314SummaryAdds FindingsNo critical issues found. Verification results:
VerdictApproved. |
Code Review: PR #1314Files reviewed: 7 (5 in PR diff + 2 valuediff changes on branch) Validation ResultsPass A: Correctness & Logic — PASSThe header filtering logic is sound:
Pass B: Security — PASS
Pass C: Cross-Reference Consistency — PASSVerified all upload call sites:
All three paths that construct metadata headers now apply the filter. All paths that don't use metadata headers are correctly left unchanged.
Pass D: Error Handling & Edge Cases — PASSEdge cases handled correctly:
Pass E: Test Coverage & Quality — PASSStrong test coverage:
The existing Pass F: Diff-Specific Checks — PASS
Verification ResultsAll CI checks pass (21/21):
Verdict: GOClean, well-designed fix. The filter correctly targets only metadata headers by prefix, preserving SSE-C and other non-metadata headers. The fallback behavior ensures backward compatibility. Test coverage is thorough with both unit tests and call-site integration tests. Notes (informational only)
|
even-wei
left a comment
There was a problem hiding this comment.
Claude Code Review: No critical issues found. Clean fix with thorough test coverage.
PR checklist
What type of PR is this?
Bug fix
What this PR does / why we need it:
Fixes two S3 upload errors encountered on staging:
AccessDenied – headers not signed— The OSS client sendsx-amz-taggingandx-amz-meta-*headers with every S3 PUT, but the cloud backend's presigned URL may not sign them. S3 rejects unsigned headers.InvalidArgument – must provide an appropriate secret key— A naive fix that filters all unsigned headers also strips SSE-C encryption headers, causing S3 to receive the algorithm header without the key.Root cause: The OSS client always adds metadata headers (
x-amz-tagging,x-amz-meta-commit,x-amz-meta-dbt_version) to S3 uploads regardless of whether the presigned URL was signed for them. The metadata is already sent to the cloud API in the POST body, so these client-side S3 headers are optional.Fix: Add
filter_headers_for_presigned_url()which:X-Amz-SignedHeadersfrom the presigned URLx-amz-tagging,x-amz-meta-*)This is backward-compatible: if the server signs metadata headers, they're still sent.
Changes:
recce/state/cloud.pyget_signed_headers(),filter_headers_for_presigned_url(). Applied in_upload_state_to_url()and_upload_state_to_recce_cloud()recce/state/__init__.pyrecce/artifact.pyupload_dbt_artifacts()tests/state/test_cloud.pyAlso includes the CI fix for staging cloud smoke test token (
integration-tests-cloud.yaml).Which issue(s) this PR fixes:
Special notes for your reviewer:
The filter only targets metadata headers by prefix. SSE-C headers are always passed through to avoid partial-stripping errors.
Does this PR introduce a user-facing change?:
NONE