Summary
When using DuckDB-Wasm, any S3 data-file write (COPY (...) TO 's3://...', or an Iceberg INSERT/CREATE TABLE) fails with 403 SignatureDoesNotMatch on the PUT. Reads with the same credentials work. SignatureDoesNotMatch is returned at S3's auth step before any policy eval, so this is purely request-signing — not creds/IAM/CORS/region.
I am building a web app that uses duckDB wasm to attach to a Polaris catalog backed by S3 and while reads work fine, I get the following error on writes:
Root cause
I've already root caused this: s3fs.cpp::CreateS3Header signs content-type:application/octet-stream into the canonical request + SignedHeaders for every PUT, but the != "application/octet-stream" guard suppresses emitting it. Native curl/httplib re-add the default (signed==sent); WASM's http_wasm.cc::HTTPWasmClient::Put sends a Uint8Array body via sync XHR with the setRequestHeader("Content-Type", …) lines commented out → per the XHR spec, no Content-Type on the wire. Signed application/octet-stream vs. sent nothing ⇒ 403.
I did some tracking in the issue and interestingly it was solved and then reborken — #101 added the emission (broke native) → #105 #ifdef EMSCRIPTEN-guarded it (WASM worked; @samansmink: "Won't this still break things on wasm?") → #234 replaced the guard with != "application/octet-stream", silently re-breaking WASM. No open issue tracks this.
Reproduction
minimal COPY (SELECT 1) TO 's3://…' on duckdb-wasm ≥ v1.30.0 → 403; DevTools shows the canonical request signing content-type:application/octet-stream with no Content-Type sent.
Proposed fix
emit the signed Content-Type under EMSCRIPTEN, native unchanged (the s3fs.cpp diff). Restores #105 for WASM, keeps #234's native behavior. PR to follow.
Summary
When using DuckDB-Wasm, any S3 data-file write (COPY (...) TO 's3://...', or an Iceberg INSERT/CREATE TABLE) fails with 403 SignatureDoesNotMatch on the PUT. Reads with the same credentials work. SignatureDoesNotMatch is returned at S3's auth step before any policy eval, so this is purely request-signing — not creds/IAM/CORS/region.
I am building a web app that uses duckDB wasm to attach to a Polaris catalog backed by S3 and while reads work fine, I get the following error on writes:
Root cause
I've already root caused this:
s3fs.cpp::CreateS3Headersignscontent-type:application/octet-streaminto the canonical request + SignedHeaders for every PUT, but the!= "application/octet-stream"guard suppresses emitting it. Native curl/httplib re-add the default (signed==sent); WASM'shttp_wasm.cc::HTTPWasmClient::Putsends a Uint8Array body via sync XHR with thesetRequestHeader("Content-Type", …)lines commented out → per the XHR spec, no Content-Type on the wire. Signed application/octet-stream vs. sent nothing ⇒ 403.I did some tracking in the issue and interestingly it was solved and then reborken — #101 added the emission (broke native) → #105 #ifdef EMSCRIPTEN-guarded it (WASM worked; @samansmink: "Won't this still break things on wasm?") → #234 replaced the guard with != "application/octet-stream", silently re-breaking WASM. No open issue tracks this.
Reproduction
minimal COPY (SELECT 1) TO 's3://…' on duckdb-wasm ≥ v1.30.0 → 403; DevTools shows the canonical request signing content-type:application/octet-stream with no Content-Type sent.
Proposed fix
emit the signed Content-Type under EMSCRIPTEN, native unchanged (the s3fs.cpp diff). Restores #105 for WASM, keeps #234's native behavior. PR to follow.