Repository navigation
Accept a SAS scoped to a blob version - #2893
Andrew Gaul (gaul) wants to merge 4 commits into
Conversation
A service SAS for one blob version carries signed resource "bv" and signs the version ID in the field where a snapshot SAS signs the snapshot time. Azurite refused the resource type outright, so no SAS URL for a version could read it, and Copy Blob and Put Blob From URL, which fetch their source through that same SAS path, failed with CannotVerifyCopySource. Accept "bv", read the value it signs from the request's versionid query parameter, sign the blob name in its canonicalized resource, and judge it by the blob permissions. The signature then binds the version, so the SAS authorizes no other version and not the current blob. The user delegation builders left that field empty for every resource, which refused a user delegation SAS for a version and for a snapshot alike, and two of them omitted the blob name for anything but "b". They now sign both, as the service SAS builders already did for snapshots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pJPwDB8sUdbqTgi5xEtRx
There was a problem hiding this comment.
🟡 Changes recommended
Critical SAS authorization flaws allow version-scoped tokens to access or modify the current blob and permit version deletion with d while rejecting the required x permission.
2 open issues
What changed in this PR
This PR adds version-scoped SAS support to Azurite’s Blob authentication, addressing a limitation of the existing Loki-backed versioning implementation.
Changes:
- Accepts
bvresources and includes the version ID in signature validation. - Corrects snapshot and version signing for user delegation SAS.
- Adds read, isolation, and copy tests and updates support documentation.
-
Impact analysis
- Package: Azurite Blob service; no package version change shown.
- Usage: Reading and copying blob versions through SAS URLs.
- Impact and risk: Authorization boundaries remain sensitive.
-
Build and conflict resolution
- Commands run: None; verification was limited to source inspection.
- Result: Build and merge-conflict status were not independently verified.
-
Tests
- Added: Version reads, selector tampering, copy operations, and user delegation reads.
- Coverage: Additional authorization regression tests are needed.
-
Test suite
- Commands run: None.
- Passing: The PR description reports a passing Loki Blob suite, lint, and TypeScript checks.
- Pending: Independent execution and follow-up regression coverage.
-
Changelog
- Entry added: Version SAS support and corrected snapshot user delegation signing.
- Location:
ChangeLog.md, Blob section.
-
PR comments
- Threads reviewed: Authentication and authorization feedback.
- Actions taken: Clarified required changes and fix guidance.
- Unresolved items: Two actionable comments remain.
-
Self-rating
- Score: Not assigned.
- Reasoning: Source evidence supports the comments; runtime validation was not performed.
-
Iteration
- Improvements made after self-review: Tightened explanations and checked proposed fixes against existing permission handling.
-
Summary
- Why the update matters: Enables SDK-generated SAS URLs for individual versions.
- Risk if not applied: Version-SAS reads and copies remain unsupported.
- Final status: Follow-up authorization changes and tests are needed.
| File | Description |
|---|---|
tests/blob/apis/blockblob.versioning.test.ts |
Adds version-SAS read, isolation, copy, and delegation tests. |
src/blob/authentication/IBlobSASSignatureValues.ts |
Updates canonical resource and snapshot/version signing. |
src/blob/authentication/BlobSASResourceType.ts |
Defines the bv resource type. |
src/blob/authentication/BlobSASAuthenticator.ts |
Accepts bv, reads versionid, and selects blob permissions. |
README.md |
Removes version-SAS support limitations. |
docs/designs/2025-12-blob-versioning.md |
Documents version-SAS signing and updates limitations. |
ChangeLog.md |
Records version and snapshot SAS support changes. |
Review effort: Balanced
A version SAS was judged by the blob permission table, which lists every blob operation. Most of their handlers ignore versionid, so a version SAS reached the base blob through them: with r it read the base blob's block list, and with w it set the base blob's metadata or overwrote it. It also let d delete the version, where Azure takes the Delete version permission, x, to delete one, and refused x. Judge a version SAS by a table of its own that lists only the operations acting on the version the request names -- Get Blob, Get Blob Properties, Get and Set Blob Tags, Set Blob Tier and Delete Blob -- with Delete Blob taking x. Anything else answers AuthorizationResourceTypeMismatch rather than the internal error an operation missing from a table produces. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pJPwDB8sUdbqTgi5xEtRx
There was a problem hiding this comment.
🟡 Changes recommended
Critical SAS isolation flaws allow legacy version tokens to access other blob versions and writable user delegation snapshot tokens to modify the current blob, requiring authorization fixes and regression tests.
2 open issues
2 resolved since last review
Review effort: Balanced
A snapshot SAS was judged by the container permission table, and most of the operations in it ignore the snapshot parameter, so a writable one reached the base blob through them: it could set the base blob's metadata. That held for a service SAS before, and the user delegation SAS for a snapshot, accepted since the previous commits, inherited it. Give the snapshot SAS a table of its own, as the version SAS has, listing the operations that act on the named snapshot -- Get Blob, Get Blob Properties, Delete Blob, and Get and Set Blob Tags. Set Blob Tier stays out, its handler acting on the base blob whatever snapshot is named. Signing formats before 2018-11-09 sign neither the signed resource nor the snapshot time or version ID, so a snapshot or version SAS in one of them binds nothing: its snapshot or versionid could be changed or dropped and the signature still verified. The bs and bv resources date from 2018-11-09, so refuse them in older formats. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pJPwDB8sUdbqTgi5xEtRx
There was a problem hiding this comment.
🟡 Changes recommended
The snapshot SAS allowlist rejects valid Get Page Ranges and committed block-list reads; restoring access also requires guarding against exposure of the base blob’s uncommitted blocks and adding regression tests.
1 open issue
2 resolved since last review
Review effort: Balanced
The snapshot permission table left out Get Block List and Get Page Ranges, which a snapshot SAS was allowed before through the container table, and whose handlers read the snapshot named. Restore both, at Read. Get Block List on a snapshot also listed the base blob's uncommitted blocks for blocklisttype=all or uncommitted, whatever the authorization: both stores look staged blocks up by blob name alone, and they belong to the base blob. A snapshot or version is committed and has none, so list them only for the base blob. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014pJPwDB8sUdbqTgi5xEtRx


A service SAS for a single blob version has signed resource
bvand signs the version ID in the string-to-sign field where a snapshot SAS signs the snapshot time. The SDKs generate one whenever a SAS is requested for a client bound to a version, for exampleblobClient.withVersion(id).generateSasUrl(...). Azurite refused the resource type outright, so such a SAS URL could not read the version it named. Copy Blob and Put Blob From URL fetch their source over that same SAS path, so a copy from a version SAS URL failed withCannotVerifyCopySource. #2735 lists this among its limitations.This change accepts
bvand reads the signed value from the request'sversionidquery parameter. It signs the blob name in the canonicalized resource. Because the version ID is in the signature, the SAS authorizes the version it names and nothing else: another version, or the current blob, gets 403.A version SAS is checked against a permission table of its own. The table lists only the operations whose handlers act on the version that
versionidnames: Get Blob, Get Blob Properties, Get/Set Blob Tags, Set Blob Tier and Delete Blob. Any other operation would act on the base blob, so it getsAuthorizationResourceTypeMismatch. Deleting the version takes the Delete version permission (x), as Azure requires, rather thand.A snapshot SAS (
bs) gets the same treatment. It used to be checked against the container table, so a writable one could set the base blob's metadata. Its table lists Get Blob, Get Blob Properties, Get Block List, Get Page Ranges, Delete Blob and Get/Set Blob Tags. Set Blob Tier is left out because its handler ignores the snapshot selector. AbsorbvSAS with a signed version before2018-11-09is refused, because those signing formats bind neither the resource nor the snapshot or version.Get Block List on a snapshot listed the base blob's uncommitted blocks for
alloruncommitted, under any authorization, because both stores looked staged blocks up by blob name alone. A snapshot has no uncommitted blocks, so they are now listed only for the base blob.The user delegation string-to-sign builders left the snapshot-time field empty for every resource, and two of them signed the blob name only for
b. So a user delegation SAS was refused for a version and for a snapshot alike. They now sign both, as the shared-key builders already did for snapshots.Tests in
blockblob.versioning.test.ts:x, but not withdalone;2017-11-09format is refused, while the same token for the blob itself is accepted;blockblob.test.ts,@loki @sql);Every new test except the one about other versions fails without the change. That one also passed before, when every version SAS was refused. The full
@lokiblob suite passes, and lint andtscare clean. I could not run the@sqlsuite locally, so the SQL store change is covered only by CI.I found this while testing S3Proxy's Azure versioning support (gaul/s3proxy#1139), which copies a version by its SAS URL, against Azurite main.
References #665.