Repository navigation
Closed
Labels
area:filesFiles — upload, download, signed URLs, access derived from the parent recordFiles — upload, download, signed URLs, access derived from the parent recordbugSomething isn't workingSomething isn't workingdomain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedpriority:p3
Description
Found while landing objectstack-ai/cloud#2533 (the attachments bucket CORS). Filed by the repo:cloud seat as an out-of-scope finding. The side observation came from the maintainer's reading in cloud#2533.
Reading
- Measured on hosted staging (R2), 2026-10-01. The presigned PUT URL the S3 adapter hands the browser carries
x-amz-sdk-checksum-algorithm=CRC32&x-amz-checksum-crc32=AAAAAA==.AAAAAA==is the CRC32 of an empty body, computed at presign time, before any bytes exist. R2 accepted a 98-byte PUT anyway, so R2 does not enforce it today. - Code,
origin/main.packages/services/service-storage/src/s3-storage-adapter.ts:233-242builds theS3Clientwithregion/endpoint/forcePathStyle/credentialsonly. It sets norequestChecksumCalculation.getPresignedUpload(:465-487) presigns aPutObjectCommandwith no body.- The lockfile resolves
@aws-sdk/client-s3and@aws-sdk/s3-request-presignerat3.1090.0. Since the SDK's flexible-checksum default (WHEN_SUPPORTED), that is what puts the empty-body checksum into the signed query.
Why it matters
- The adapter is the framework's generic S3 door: AWS S3, MinIO, R2 and others. NOT MEASURED: whether AWS S3 or MinIO enforce a query-signed
x-amz-checksum-crc32against the uploaded body. If one does, every presigned browser upload to that store fails with a checksum mismatch, while server-sideupload()keeps working. That is the same "presign 200, upload never lands" shape cloud#2533 just had, with a different cause. - Hosted cloud runs on R2, so cloud is not affected today. Self-hosted deployments on other S3 stores may be.
Fix sketch (not measured)
- Set
requestChecksumCalculation: 'WHEN_REQUIRED'(and, symmetrically,responseChecksumValidation: 'WHEN_REQUIRED') in the adapter'sS3Clientoptions, so presigned PUTs carry no body checksum. - A unit test that presigns an upload and asserts the URL has no
x-amz-checksum-*/x-amz-sdk-checksum-algorithmparameter. It is offline, with fake credentials and a fixed endpoint.
Acceptance
- The test above, red before and green after.
- Optional and live: a presigned PUT of a non-empty body to an enforcing store (MinIO in a test container, if the repo has a pattern for that) returns 200.
查重词
presigned PUT checksum CRC32 · x-amz-checksum-crc32 AAAAAA== · requestChecksumCalculation WHEN_REQUIRED · s3-storage-adapter presign checksum mismatch
Generated by Claude Code
Metadata
Metadata
Assignees
Labels
area:filesFiles — upload, download, signed URLs, access derived from the parent recordFiles — upload, download, signed URLs, access derived from the parent recordbugSomething isn't workingSomething isn't workingdomain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedpriority:p3
Activity
objectstack-fleet commentedon Oct 1, 2026
Claim: PM loop round 1 · 2026-10-01T10:40Z
Session:
session_01DiCSbmJrkzNhuEAier4VoJAccount:
os-bill(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-21147-s3-presign-checksumWorktree:
objectstack-issue-21147Domain:
domain:servicesSeat:
domain:services#2(seat post #21118)File surface:
packages/services/service-storage/src/s3-storage-adapter.ts(theS3Clientoptions,:233–:242atorigin/main, andgetPresignedUpload,:465), a test underpackages/services/service-storage/src/, and a changeset. Stop on breach; explain in the report.Container & model:
S, mechanical,mode:subagent,model: sonnet(dispatch-gates--tier: no path-derived mandate; floor tier, because the fix is a client option and an offline URL assertion)Clause-②: no
Thread-read: none
Serial constraints cleared: I diffed the pushed branches of every open
pm:dispatchedcard againstorigin/main. None touchespackages/services/service-storage/**. The only commit there today is840ec9da(a dev-dependency bump).Provenance note: this card's labels (
domain:services,priority:p3,area:files,pm:queue) were written at creation by the filer (therepo:cloudseat). There has been no triage first touch. The domain is the anchoring rule's answer (service-storageisdomain:services) and the card names its file, so this seat claims it. Triage may still re-grade it.Clause-②
no: presigned PUT URLs stop carrying an empty-body checksum parameter. No published accept set widens and no public surface grows.Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
os-dev-report
{ "issue": 21147, "status": "done", "branch": "claude/issue-21147-s3-presign-checksum", "pr": "https://github.com/objectstack-ai/objectstack/pull/21164", "session": "session_01DiCSbmJrkzNhuEAier4VoJ", "premise_still_valid": true, "summary": "Premise verified on origin/main (base 70dae533): the S3Client was built without requestChecksumCalculation, and with @aws-sdk/client-s3 3.1090.0 a presigned PUT carries x-amz-checksum-crc32=AAAAAA== plus x-amz-sdk-checksum-algorithm=CRC32, and a presigned GET carries x-amz-checksum-mode=ENABLED. The adapter's client now sets requestChecksumCalculation and responseChecksumValidation to WHEN_REQUIRED; they must be set as a pair, because the response option alone puts a CRC32 on the GET URL (measured 2x2 matrix in the PR body and the test header). New offline test drives the real S3Client and presigner (fake credentials, fixed endpoint, no socket); red on the base adapter, green after, and ablated three ways. Landing is the card's file surface: s3-storage-adapter.ts, one new test, one patch changeset; no other package builds an S3Client. Draft PR 21164 is open; its assignee is NOT set (label-write was denied by the classifier, see deviations).", "tests": "Base red (adapter unchanged, test at 8a58eadd): 'Tests 2 failed | 1 passed (3)'; upload case 'expected [ x-amz-checksum-crc32, (1 more) ] to deeply equal []' (received x-amz-checksum-crc32, x-amz-sdk-checksum-algorithm); download case 'expected [ x-amz-checksum-mode ] to deeply equal []'. The third case (descriptor headers) was green on base and was dropped as non-discriminating. After fix: 'Test Files 1 passed (1) Tests 2 passed (2)'. Whole package: pnpm --filter @objectstack/service-storage test -> 'Test Files 41 passed (41) Tests 629 passed (629)'; typecheck (tsc, tsconfig.scripts, check:test-typecheck) exit 0; closure build 'pnpm --workspace-concurrency=2 --filter @objectstack/service-storage^... build' exit 0 (upstream-dependency direction); package build exit 0 and require('./dist/index.cjs') loads; dist/index.js and dist/index.cjs both contain requestChecksumCalculation. Every heavy run went through os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-21147, VERDICT command-exit read each time. Ablation (committed fix, scripts/ablation-replace.mjs, anchor hit 1->0, blob hash moved, restore proven blob == HEAD 422e36c94d88 and empty git diff HEAD; subject resolves to src so no dist rebuild leg applies): A both options removed -> upload case RED (crc32, sdk-checksum-algorithm), download case RED (checksum-mode); B request option only removed -> upload RED, download RED (crc32 on GET URL); C response option only removed -> upload GREEN, download RED (checksum-mode). Direction observed: red, as expected, plus an inversion found in B (the GET URL gets worse than default). server-side effects measured on a loopback server over HTTP and over self-signed HTTPS (identical): upload() PutObject and UploadPart no longer send x-amz-sdk-checksum-algorithm / x-amz-checksum-crc32, x-amz-content-sha256 unchanged, request still succeeds; GetObject no longer sends x-amz-checksum-mode. None of the nine commands the adapter issues is requestChecksumRequired in 3.1090.0.", "gates": { "head": "33e30baf", "derive_command": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands", "derived": 63, "run_exit_0": 61, "not_measured": [ { "command": "pnpm check:dual-build-cjs-loads", "exit": 3, "reason": "PREREQUISITE NOT MET: reads dist of 54 packages outside the service-storage closure; CI builds them. Narrower substitute run: package build exit 0 and require of dist/index.cjs loads" }, { "command": "pnpm check:i18n", "exit": 3, "reason": "PREREQUISITE NOT MET: workspace CLI and the extract-config closures not built; diff touches no translation, extract config or metadata form (a reading of the diff, not a measurement)" } ], "unrun": 0, "reconcile_line": "dispatch-gates --ran: 63 derived famil(ies) accounted for - 61 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)", "ran_after_final_commit": true, "ci_state": "in_progress (not waited for, per contract)" }, "line_budget": "3 files, +164 / -1 (below the 5000-line human-merge threshold); no governed surface touched (no docs/adr, .claude, skills, AGENTS.md, CLAUDE.md); no skills/** diff so no skill line budget applies", "deviations": [ "label-write DENIED by the auto-mode classifier (External System Writes): command 'node scripts/pm/label-write.mjs --repo objectstack-ai/objectstack --issue 21164 --assign os-bill'. Not retried through the relay assign op, curl or MCP. Seat to set the assignee of PR 21164 to os-bill (the card's). No other label was in scope (dispatch named none; a changeset is present so skip-changeset does not apply).", "The fix was preceded by a separate WIP test commit (8a58eadd, red on base) pushed before the slow closure build; both commits land on one branch and can be squashed.", "One test case (descriptor headers equal content-type only) was written, found green on base, and removed as non-discriminating; the PM asked for one offline test.", "The foreground gate batch for lines 49-63 hit the 590s tool cap and was moved to background by the harness; I waited on its PID with tail --pid in the foreground and read the results from the recorded ran.list; no gate was re-run or skipped.", "A turbo-driven gate appended its managed block to AGENTS.md in the worktree; restored with git checkout HEAD -- AGENTS.md and never staged." ], "files_changed": [ "packages/services/service-storage/src/s3-storage-adapter.ts", "packages/services/service-storage/src/s3-storage-adapter.presign-checksum.test.ts", ".changeset/21147-s3-presign-no-checksum.md" ], "mcp_calls": "0 - no MCP tool called", "api_writes": "2 - relay dispatch fw-20261001T110813Z-79d784: pr_create (draft) -> POST /repos/objectstack-ai/objectstack/pulls (run 36853470105, read-back identical, PR 21164); this report -> POST /repos/objectstack-ai/objectstack/issues/21147/comments. label-write (PR assignee) was denied by the classifier, so no write was made. Not REST: 4 git pushes.", "open_questions": [], "out_of_scope_findings": [ "carrier: none (承接者:无) - noted, not filed: the SDK option-pair coupling (response option alone puts a CRC32 on a presigned GET URL, measured on 3.1090.0) is recorded only in the test header and the adapter comment; an SDK bump that changes it surfaces as the upload or download case going red. Optional live leg against an enforcing store is not done: the repo has no container pattern (no testcontainers or localstack dependency, no MinIO service in any workflow)." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
PM review — PR #21164: ACCEPT. Landing: ordinary queue once every check on head
33e30bafis greendomain:servicesseat 2 (#21118) ·session_01DiCSbmJrkzNhuEAier4VoJ· 2026-10-01T11:11Z. Reviewed-by: this seat. Implemented-by: os-dev subagent, same session (report5930117109).Checklist, read on GitHub rather than from the report:
main, first lineFixes #21147,Clause-②: no(matches the claim).s3-storage-adapter.ts, one new offline test and apatchchangeset for@objectstack/service-storage.check-governed-merges.mjs --pr 21164: not governed, 165 changed lines. All of it sits inside the claimed surface.label-write --assign os-billon the PR was denied by the session's permission classifier. This seat does not re-run a denied write on the dev's behalf. It is surfaced to the maintainer, and the field stays empty until then.S3Clientnow setsrequestChecksumCalculationandresponseChecksumValidationtoWHEN_REQUIRED, together. That is the card's own fix sketch.3.1090.0, the response option alone puts a CRC32 on the presigned GET URL. The request option alone leavesx-amz-checksum-modeon it.upload()no longer sends an SDK body checksum (x-amz-content-sha256is unchanged);download()no longer requests or validates a response checksum.dist/index.cjsloads;check:dual-build-cjs-loadsandcheck:i18nare NOT MEASURED (exit 3, workspace-wide prerequisites), and CI runs them.33e30bafat this write: 11 success, 3 skipped, 17 in progress, 0 failed.Out-of-scope findings: 1. dropped — the SDK option-pair coupling is recorded in the adapter comment and the test header, and an SDK bump that changes it turns a case red. The live leg against an enforcing store was not run, because the repo has no MinIO or LocalStack container pattern. This does not meet the filing gate.
Generated by Claude Code
fix(service-storage): presigned S3 PUT URLs no longer carry the CRC32…