feat: add native AzureBlob upload handler and receiver - #193
Conversation
Port the serverless AzureBlob handler and receiver into in-process native adapters, with collision-safe filenames, batch/folder limits, and a server-side copy for move-on-delete.
📝 WalkthroughWhat changed:
Risk: risk:medium Security-sensitive areas touched:
Test coverage impact:
Deployment / migration / rollback / operational concerns:
WalkthroughThis PR adds Azure Blob Storage native adapters for receiving and uploading blobs, plus their configuration models, DI registration, and the Azure.Storage.Blobs package dependency. ChangesAzure Blob native adapters
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@SW.Bitween.NativeAdapters/AzureBlobReceiver/NativeAzureBlobReceiver.cs`:
- Around line 56-75: The DeleteFile move-on-delete flow in
NativeAzureBlobReceiver is not retry-safe because it always attempts
SyncCopyFromUriAsync before deleting, even when the source blob was already
moved by a prior successful call. Update DeleteFile so it treats an
already-missing source fileId as a no-op when _options.DeleteMovesFileTo is set,
using the existing sourceBlob/targetBlob path logic to detect and skip the
copy-and-delete if the blob is gone. Keep the behavior localized to
NativeAzureBlobReceiver.DeleteFile and preserve the relative-path handling based
on _options.FolderName.
- Around line 24-36: The ListFiles method in NativeAzureBlobReceiver is using
_options.FolderName as a raw prefix for GetBlobsAsync, which can match sibling
blobs outside the intended virtual folder. Update the prefix used in ListFiles
so it is folder-scoped with a trailing slash before calling GetBlobsAsync, and
keep the batching logic in place so only blobs under the intended folder are
returned.
- Around line 67-71: The move logic in NativeAzureBlobReceiver should not use
the synchronous copy API with a bare source URI. Update the copy path in the
receiver method that builds sourceBlob and targetBlob to use
StartCopyFromUriAsync instead of SyncCopyFromUriAsync, and make sure the source
URI is authorized for private blobs (for example by using a SAS-backed URI or
equivalent authorization) before starting the copy.
In `@SW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csproj`:
- Line 17: The Azure.Storage.Blobs package reference is outdated, so update the
PackageReference in the project file to the current NuGet version. Adjust the
existing Azure.Storage.Blobs entry in SW.Bitween.NativeAdapters.csproj from
12.23.0 to 12.29.1, keeping the same package name and location.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: simplify9/coderabbit/.coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 1f36a5a6-63cb-4149-b11c-d2054f7e48bf
📒 Files selected for processing (6)
SW.Bitween.NativeAdapters/AzureBlobReceiver/AzureBlobReceiverInput.csSW.Bitween.NativeAdapters/AzureBlobReceiver/NativeAzureBlobReceiver.csSW.Bitween.NativeAdapters/AzureBlobUploadHandler/AzureBlobUploadHandlerInput.csSW.Bitween.NativeAdapters/AzureBlobUploadHandler/NativeAzureBlobUploadHandler.csSW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csprojSW.Bitween.NativeAdapters/ServiceCollectionExtensions.cs
📜 Review details
🔇 Additional comments (7)
SW.Bitween.NativeAdapters/AzureBlobReceiver/AzureBlobReceiverInput.cs (1)
1-25: LGTM!SW.Bitween.NativeAdapters/AzureBlobReceiver/NativeAzureBlobReceiver.cs (2)
13-22: LGTM!
38-54: LGTM!Also applies to: 77-85
SW.Bitween.NativeAdapters/AzureBlobUploadHandler/AzureBlobUploadHandlerInput.cs (1)
1-18: LGTM!SW.Bitween.NativeAdapters/AzureBlobUploadHandler/NativeAzureBlobUploadHandler.cs (2)
30-38: LGTM!
11-28: 🩺 Stability & AvailabilityEnsure the blob container is available before upload. If this handler can run without external provisioning, add
CreateIfNotExistsAsync; otherwise document the pre-created container contract.SW.Bitween.NativeAdapters/ServiceCollectionExtensions.cs (1)
2-3: LGTM!Also applies to: 51-56
| // Server-side copy: Azure moves the blob internally, so no bytes are | ||
| // downloaded or re-uploaded through this process. | ||
| var sourceBlob = _container.GetBlobClient(fileId); | ||
| var targetBlob = _container.GetBlobClient(targetName); | ||
| await targetBlob.SyncCopyFromUriAsync(sourceBlob.Uri); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify current Azure.Storage.Blobs SyncCopyFromUriAsync size limits / auth model
curl -s "https://raw.githubusercontent.com/MicrosoftDocs/azure-docs/main/articles/storage/blobs/storage-blob-copy.md" | grep -i -A3 "size limit\|same storage account"Repository: simplify9/Bitween-api
Length of output: 1994
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== repo root =="
pwd
echo
echo "== locate NativeAzureBlobReceiver.cs =="
fd -a 'NativeAzureBlobReceiver.cs' . || true
echo
echo "== search for SyncCopyFromUriAsync usage =="
rg -n "SyncCopyFromUriAsync|StartCopyFromUriAsync|CopyFromUriAsync" . || true
echo
echo "== Azure docs excerpts =="
python3 - <<'PY'
import urllib.request, re
url = "https://raw.githubusercontent.com/MicrosoftDocs/azure-docs/main/articles/storage/blobs/storage-blob-copy.md"
text = urllib.request.urlopen(url, timeout=20).read().decode("utf-8", "replace")
for pat in [
r"(?is).{0,200}size limit.{0,500}",
r"(?is).{0,200}same storage account.{0,500}",
r"(?is).{0,200}private source.{0,500}",
r"(?is).{0,200}StartCopyFromUriAsync.{0,500}",
]:
m = re.search(pat, text)
print("MATCH" if m else "NO MATCH", pat.split(".{0,200}")[-1][:40])
if m:
snippet = m.group(0)
print(snippet[:1200])
print("---")
PYRepository: simplify9/Bitween-api
Length of output: 1965
🌐 Web query:
Azure Storage blob copy SyncCopyFromUriAsync size limit same storage account private source URI StartCopyFromUriAsync
💡 Result:
When copying blobs in Azure Storage using the.NET SDK, there is a critical distinction between SyncCopyFromUriAsync and StartCopyFromUriAsync regarding source blob size limits and operation behavior [1][2][3]. SyncCopyFromUriAsync This method wraps the Copy Blob From URL REST API [2][3]. It operates synchronously and is subject to a strict source blob size limit of 256 MiB [1][2]. If the source blob exceeds this size, the operation will fail with a 409 (Conflict) error [2][4]. Additionally, this method typically requires the source blob to be public or authorized via a Shared Access Signature (SAS), even when the source is in the same storage account [2][3][5]. StartCopyFromUriAsync This method is designed for asynchronous, long-running copy operations [6][7]. It does not have the same 256 MiB size limitation as the synchronous version [8][6][7]. When using this method, the copy process is initiated and runs in the background; you can track its progress using the returned CopyFromUriOperation object [6][9][7]. Summary Recommendation For source blobs larger than 256 MiB, you must use StartCopyFromUriAsync [2][6]. If you have a requirement to move data within the same storage account and prefer synchronous-like behavior or need to handle larger files, consider using methods that support partial updates (such as StageBlockFromUri combined with PutBlockList), which provide greater flexibility and avoid the size constraints of the single-request Copy Blob From URL operation [10].
Citations:
- 1: https://learn.microsoft.com/en-us/rest/api/storageservices/copy-blob-from-url
- 2: https://learn.microsoft.com/en-us/dotnet/api/azure.storage.blobs.specialized.blobbaseclient.synccopyfromuriasync?view=azure-dotnet
- 3: [BUG] SyncCopyFromUri works differently than StartCopyFromUri for copies within the same container. Azure/azure-sdk-for-net#32523
- 4: https://learn.microsoft.com/en-us/dotnet/api/azure.storage.blobs.specialized.blobbaseclient.synccopyfromuri?view=azure-dotnet
- 5: [BUG] SyncCopyFromUriAsync() requires a SAS uri while StartCopyFromUriAsync() does not Azure/azure-sdk-for-net#32028
- 6: https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blob-copy-async-dotnet
- 7: https://learn.microsoft.com/en-us/dotnet/api/azure.storage.blobs.specialized.blobbaseclient.startcopyfromuri?view=azure-dotnet
- 8: https://learn.microsoft.com/en-us/answers/questions/1287651/max-size-of-source-file-in-startcopyfromuriasync
- 9: https://learn.microsoft.com/en-us/dotnet/api/azure.storage.blobs.models.copyfromurioperation?view=azure-dotnet
- 10: https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blob-copy-url-dotnet
🌐 Web query:
Azure Storage Copy Blob From URL same account source URI SAS public access SyncCopyFromUriAsync authorization docs
💡 Result:
When performing a synchronous copy operation using SyncCopyFromUriAsync (or related methods like SyncUploadFromUriAsync) within the same Azure storage account, the operation essentially uses the Copy Blob REST API underneath [1][2]. Authorization Requirements: The underlying REST API requires that the source blob be accessible to the copy service [3]. If the source blob is not public, the URI provided to the operation must be authorized [3][4]. Even within the same account, simply providing the base URI of a private source blob will often fail with a 409 (CannotVerifyCopySource) or similar authorization error [3]. To authorize the copy, you should typically append a Shared Access Signature (SAS) token to the source blob URI [5][3]. The SAS token must grant at least Read ('r') permissions [4]. While Microsoft Entra ID (RBAC) is recommended for overall service client authorization, the copy operation specifically requires that the source URL itself provides the necessary access for the storage service to perform the read [3][4]. Key Considerations: 1. SAS Token: Generating a SAS URI using the source blob client is the standard approach to provide the required authorization for the operation [5]. 2. Public Access: If the container or blob is configured for public access, a SAS token may not be strictly required, though using one is considered a best practice for security [3]. 3. Sync vs. Async: SyncCopyFromUriAsync is intended for synchronous copies of smaller blobs (up to 256 MiB) [6][7]. For larger operations or scenarios where asynchronous scheduling is preferred, other methods like StartCopyFromUriAsync should be used [4]. In summary, to successfully use SyncCopyFromUriAsync for a private blob within the same account, you must ensure the source URI includes a valid SAS token with at least read permissions [5][3].
Citations:
- 1: [BUG] SyncCopyFromUriAsync() requires a SAS uri while StartCopyFromUriAsync() does not Azure/azure-sdk-for-net#32028
- 2: https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blob-copy-url-dotnet
- 3: [BUG] Azure Blob Storage - When using a Shared Access Key on BlobServiceClient, BlobClient throws RequestFailedException during SyncCopyFromUriAsync Azure/azure-sdk-for-net#16882
- 4: https://github.com/MicrosoftDocs/azure-docs/blob/main/articles/storage/blobs/storage-blob-copy-async-dotnet.md
- 5: https://learn.microsoft.com/en-us/samples/azure-samples/azure-cxp-developer-support/csharp-sample-code-to-call-synccopyfromuriasync-method-to-perform-the-copy-blob-operation-with-same-storage-account/
- 6: https://learn.microsoft.com/en-us/dotnet/api/azure.storage.blobs.specialized.blobbaseclient.synccopyfromuriasync?view=azure-dotnet
- 7: https://learn.microsoft.com/en-us/rest/api/storageservices/copy-blob-from-url
Use StartCopyFromUriAsync here
SyncCopyFromUriAsync is limited to 256 MiB and will fail for private source blobs unless the source URI is authorized. sourceBlob.Uri is a bare private URL, so this move path will break unless the blob/container is public or you append a SAS; the synchronous call also ties up the request thread for the copy duration.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SW.Bitween.NativeAdapters/AzureBlobReceiver/NativeAzureBlobReceiver.cs`
around lines 67 - 71, The move logic in NativeAzureBlobReceiver should not use
the synchronous copy API with a bare source URI. Update the copy path in the
receiver method that builds sourceBlob and targetBlob to use
StartCopyFromUriAsync instead of SyncCopyFromUriAsync, and make sure the source
URI is authorized for private blobs (for example by using a SAS-backed URI or
equivalent authorization) before starting the copy.
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" /> |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
curl -s https://api.nuget.org/v3-flatcontainer/azure.storage.blobs/index.json | jq '.versions[-5:]'
gh api graphql -f query='
{
securityVulnerabilities(first: 5, ecosystem: NUGET, package: "Azure.Storage.Blobs") {
nodes {
advisory { summary severity publishedAt }
vulnerableVersionRange
firstPatchedVersion { identifier }
}
}
}'Repository: simplify9/Bitween-api
Length of output: 1994
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== latest versions =="
curl -fsSL https://api.nuget.org/v3-flatcontainer/azure.storage.blobs/index.json \
| jq -r '.versions[-10:][]'
echo
echo "== vulnerability feed metadata =="
curl -fsSL https://api.nuget.org/v3/vulnerabilities/index.json \
| jq '{vulnerabilities: (.vulnerabilities // .packageVulnerabilities // .data // .)}' | sed -n '1,160p'Repository: simplify9/Bitween-api
Length of output: 1965
🌐 Web query:
Azure.Storage.Blobs NuGet latest version vulnerability advisories
💡 Result:
As of July 8, 2026, the latest version of the Azure.Storage.Blobs NuGet package is 12.29.1 [1][2][3]. Regarding security and vulnerability advisories: Official Security Reporting: Microsoft manages security vulnerabilities for the Azure SDK for.NET through the Microsoft Security Response Center (MSRC) [4][5]. Users are advised not to report vulnerabilities via public GitHub issues but to use the official MSRC channels [4][6]. Historical Vulnerabilities: The package has had historical security disclosures, such as CVE-2022-30187, which involved potential information exposure when using client-side encryption [7][8][9]. This was addressed in version 12.13.0 [7]. Additionally, broader vulnerabilities affecting the Azure SDK for.NET, such as information disclosure (CVE-2022-26907) [10] or issues in related dependencies like Azure Identity (e.g., CVE-2023-36414) [11], have historically impacted the ecosystem. Recommended Best Practices: 1. Always Use the Latest Version: Microsoft strongly recommends keeping client library versions up to date to receive the latest security patches, bug fixes, and performance improvements [12]. 2. Monitor Official Sources: For the most accurate and up-to-date vulnerability information, consult the Microsoft Security Update Guide (MSRC) [5] or the official Azure SDK for.NET GitHub repository security page [4]. 3. Changelog Review: You can review specific release notes and changes in the project's official CHANGELOG.md on GitHub [3].
Citations:
- 1: https://www.nuget.org/packages/azure.storage.blobs/
- 2: https://learn.microsoft.com/en-us/dotnet/api/overview/azure/storage.blobs-readme?view=azure-dotnet
- 3: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/storage/Azure.Storage.Blobs/CHANGELOG.md
- 4: https://github.com/Azure/azure-sdk-for-net/security
- 5: https://msrc.microsoft.com/update-guide/vulnerability
- 6: https://www.microsoft.com/en-us/msrc
- 7: https://security.snyk.io/vuln/SNYK-DOTNET-AZURESTORAGEBLOBS-2949177
- 8: https://advisories.gitlab.com/pkg/nuget/azure.storage.blobs/
- 9: https://advisories.gitlab.com/nuget/azure.storage.blobs/CVE-2022-30187/
- 10: https://isc.sans.edu/vuln.html?cve=2022-26907
- 11: https://nvd.nist.gov/vuln/detail/cve-2023-36414
- 12: https://github.com/Azure/azure-docs-sdk-dotnet/blob/master/api/overview/azure/latest/storage.md
Bump Azure.Storage.Blobs 12.23.0 is several releases behind the current NuGet version (12.29.1); update it to keep the dependency current.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csproj` at line 17, The
Azure.Storage.Blobs package reference is outdated, so update the
PackageReference in the project file to the current NuGet version. Adjust the
existing Azure.Storage.Blobs entry in SW.Bitween.NativeAdapters.csproj from
12.23.0 to 12.29.1, keeping the same package name and location.
Trim whitespace from connection settings, scope folder listing to a true prefix boundary, and make move-on-delete safe to retry.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
SW.Bitween.NativeAdapters/AzureBlobReceiver/NativeAzureBlobReceiver.cs (1)
42-48: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winDispose the
Contentstream after copy.
download.Value.Contentis never disposed, leaking the download stream. Useawait usingto ensure cleanup even on exception.🔧 Proposed fix
var blobClient = _container.GetBlobClient(fileId); var download = await blobClient.DownloadAsync(); - using var memoryStream = new MemoryStream(); - await download.Value.Content.CopyToAsync(memoryStream); - var bytes = memoryStream.ToArray(); + await using var content = download.Value.Content; + using var memoryStream = new MemoryStream(); + await content.CopyToAsync(memoryStream); + var bytes = memoryStream.ToArray();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@SW.Bitween.NativeAdapters/AzureBlobReceiver/NativeAzureBlobReceiver.cs` around lines 42 - 48, The GetFile method in NativeAzureBlobReceiver leaves download.Value.Content undisposed after CopyToAsync, which can leak the download stream. Update the download handling in GetFile to wrap download.Value.Content in await using (or otherwise ensure it is disposed in a finally block) while preserving the existing MemoryStream copy and XchangeFile construction.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@SW.Bitween.NativeAdapters/AzureBlobReceiver/NativeAzureBlobReceiver.cs`:
- Around line 42-48: The GetFile method in NativeAzureBlobReceiver leaves
download.Value.Content undisposed after CopyToAsync, which can leak the download
stream. Update the download handling in GetFile to wrap download.Value.Content
in await using (or otherwise ensure it is disposed in a finally block) while
preserving the existing MemoryStream copy and XchangeFile construction.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: simplify9/coderabbit/.coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: df15e195-e20c-405d-9a18-4e880c724fe8
📒 Files selected for processing (1)
SW.Bitween.NativeAdapters/AzureBlobReceiver/NativeAzureBlobReceiver.cs
📜 Review details
🔇 Additional comments (3)
SW.Bitween.NativeAdapters/AzureBlobReceiver/NativeAzureBlobReceiver.cs (3)
78-81: 🩺 Stability & Availability | 🏗️ Heavy lift
SyncCopyFromUriAsyncissue still unresolved.This was flagged in a prior review:
SyncCopyFromUriAsyncis limited to 256 MiB source blobs and will fail for private source blobs when passed a bare URI (sourceBlob.Uri) without SAS authorization. The code still uses this API unchanged. Consider switching toStartCopyFromUriAsync(no size limit, async) or appending a read-scoped SAS token to the source URI.
24-30: LGTM!
60-67: LGTM!
Port the serverless AzureBlob handler and receiver into in-process native adapters, with collision-safe filenames, batch/folder limits, and a server-side copy for move-on-delete.