Skip to content

SNOW-3869839: Treat S3 HEAD 403 as file-not-found in preflight upload check - #2987

Draft
sfc-gh-turbaszek wants to merge 3 commits into
mainfrom
tomasz/SNOW-3869839-s3-head-403-not-found
Draft

SNOW-3869839: Treat S3 HEAD 403 as file-not-found in preflight upload check#2987
sfc-gh-turbaszek wants to merge 3 commits into
mainfrom
tomasz/SNOW-3869839-s3-head-403-not-found

Conversation

@sfc-gh-turbaszek

Copy link
Copy Markdown
Contributor

Summary

  • PUT uploads to S3-backed internal stages could fail with 403 Client Error: Forbidden on the pre-flight existence check (get_file_header()HEAD), even though the underlying PUT would have succeeded.
  • AWS S3 returns 403 (not 404) for HeadObject on a non-existent key when the caller lacks s3:ListBucket on the bucket — which is the case for the scoped STS credentials Snowflake issues for stage uploads (they grant PutObject/GetObject on the prefix but not bucket-level ListBucket).
  • get_file_header() (sync and async S3 storage clients) now treats 403 the same as 404 — i.e. "cannot confirm the file exists, proceed to upload" — instead of raising.

Test plan

  • Added test_get_header_403_treated_as_not_found to test/unit/test_s3_util.py and test/unit/aio/test_s3_util_async.py
  • Ran full test/unit/test_s3_util.py and test/unit/aio/test_s3_util_async.py suites — all passing
  • Updated DESCRIPTION.md changelog

Fixes SNOW-3869839.

🤖 Generated with Claude Code

sfc-gh-turbaszek and others added 3 commits August 4, 2026 08:24
… check

S3 returns 403 (instead of 404) for a HEAD request on a non-existent key
when the caller's credentials lack s3:ListBucket on the bucket. The scoped
credentials Snowflake issues for internal-stage PUTs grant PutObject/
GetObject on the prefix but not bucket-level ListBucket, so the connector's
pre-flight existence check was raising a hard 403 error even though the
subsequent PUT would succeed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The preflight HEAD result is only consulted when meta.overwrite is False,
so treating an ambiguous 403 as "file absent" in that case would upload
over a file the user asked us to keep, silently breaking
PUT ... OVERWRITE = FALSE. Restrict the 403 tolerance to the case where
the file is going to be overwritten anyway - where the answer cannot
change what happens next - and keep raising otherwise. This also leaves
the download preflight, where overwrite is False, reporting genuine
permission failures instead of "not found".

When the 403 is raised, log a warning naming the likely cause (missing
s3:ListBucket) and both remedies, since a bare "403 Client Error:
Forbidden" is what made this hard to diagnose in the first place. Also
include the status code in the not-found debug line so 404 ("absent") is
distinguishable from 403 ("assuming absent") in logs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 57.14286% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.13%. Comparing base (ba1b98e) to head (59ba389).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/snowflake/connector/aio/_s3_storage_client.py 0.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2987      +/-   ##
==========================================
+ Coverage   65.04%   65.13%   +0.09%     
==========================================
  Files         123      123              
  Lines       17413    17438      +25     
  Branches     2387     2394       +7     
==========================================
+ Hits        11326    11359      +33     
+ Misses       5648     5639       -9     
- Partials      439      440       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

2 participants