Skip to content

Key-only buckets: strengthen browseable:false to deny listing - #22

Merged
krokicki merged 5 commits into
mainfrom
unbrowseable
Jul 24, 2026
Merged

Key-only buckets: strengthen browseable:false to deny listing#22
krokicki merged 5 commits into
mainfrom
unbrowseable

Conversation

@krokicki

@krokicki krokicki commented Jul 24, 2026

Copy link
Copy Markdown
Member

Summary

Supports publishing files without allowing browsing: a target with browseable: false is now key-only — objects can be fetched only by exact key, and nothing about the bucket's contents can be listed or probed.

The behavior mirrors a real AWS S3 bucket policy that grants s3:GetObject but denies s3:ListBucket, so S3-aware clients (aws cli, boto3, rclone, Neuroglancer) already understand the responses:

  • ListObjectsV2, trailing-slash XML listings, and the HTML browse UI return 403 AccessDenied
  • GetBucketAcl / GetObjectAcl return 403
  • HeadBucket returns 403 (requires s3:ListBucket in real S3)
  • GET/HEAD of a missing key returns 403 instead of 404 NoSuchKey, so key existence can't be enumerated (matches S3's documented masking when ListBucket is denied)
  • GET/HEAD of an existing key works unchanged, including range requests
  • The bucket remains hidden from the root index (existing behavior)

All guards live in the dispatch layer (x2s3/app.py), so every client backend is covered; no client or config changes.

Breaking change: deployments using browseable: false as "hidden from the index but listable if you know the name" will now get 403 on listing — that is the point of this change.

Test plan

  • New tests in tests/test_file.py (deny list/browse/ACL, 404-masking, HeadBucket, XML root exclusion, browseable buckets unaffected)
  • New aioboto-backend tests in tests/test_awss3.py (list denied, missing-key masking)
  • Full suite: 69/69 passing (including network-gated S3 tests)

@StephanPreibisch

krokicki and others added 5 commits July 24, 2026 14:26
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… masking tests

- Move browseable guard before list-type validation in target_dispatcher
  so invalid list-type on unbrowseable buckets returns 403, not 400
- Add test_unbrowseable_hidden_from_xml_root: verify unbrowseable buckets
  excluded from root XML ListAllMyBuckets response
- Add test in test_unbrowseable_list_denied: verify list-type=1 gets 403
- Add test_hidden_missing_key_masked on aioboto backend: verify missing
  keys are masked as 403 AccessDenied on unbrowseable buckets

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@krokicki
krokicki merged commit 02f8886 into main Jul 24, 2026
5 checks passed
@krokicki
krokicki deleted the unbrowseable branch July 24, 2026 20:37
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.

1 participant