Repository navigation
Azure Blob: all file reads fail on a private container (unauthenticated getAzureFileStream, no SAS download URL) #14955
Description
What happened?
With fileStrategy: azure_blob and a private blob container, every read of an uploaded file fails. Uploads succeed, so the blobs are all there — they just cannot be read back.
The visible symptom is a provider error that points nowhere near the cause. Pasting an image into a chat ends the turn with:
400 litellm.BadRequestError: BadRequestError - Failed to download image from
https://<account>.blob.core.windows.net/<container>/images/<userId>/<uuid>__image.png
and the server log right before it says:
[getAzureFileStream] Error getting blob stream: Request failed with status code 409
Error processing image from blob storage: Request failed with status code 409
(409 PublicAccessNotPermitted is what a storage account with allowBlobPublicAccess: false returns; a container whose access level is private returns 404 instead. Either way the read fails.)
Root cause
Uploads and downloads take different code paths, and only the upload authenticates.
saveBufferToAzureuses the SDK client built fromAZURE_STORAGE_CONNECTION_STRING(or Managed Identity) —api/server/services/Files/Azure/crud.js.getAzureFileStreamdoes a plain, credential-free HTTP GET on the blob URL:
async function getAzureFileStream(_req, fileURL) {
const response = await axios({ method: 'get', url: fileURL, responseType: 'stream' });
return response.data;
}That only works when the container allows anonymous access. AZURE_STORAGE_PUBLIC_ACCESS defaults to 'true' in the same file, so the happy path is a public container, and a private one is broken everywhere getDownloadStream is used:
- vision (
encodeAndFormat) /api/files/:file_id/previewand/api/files/download/...- image tools (
OpenAIImageTools.js) - code-interpreter re-priming
The failure is then downgraded rather than surfaced: encodeAndFormat catches it, logs, and falls through to prepareImagePayload, which for Azure returns the raw blob URL. That URL is sent to the provider, the provider cannot read it either, and the user sees a provider 400 instead of a storage error.
Second, related gap: the Azure strategy has no getDownloadURL, while S3 has a presigned one (api/server/routes/files/files.js). So /api/files/download-url/... answers 501 Not Implemented for Azure and the client falls back to the stream that just failed. There is no SAS code anywhere in the repository, so a private container has no working read path at all.
Steps to reproduce
- Create a storage account with
allowBlobPublicAccess: false(or a container with private access level). - Set
fileStrategy: azure_bloband a validAZURE_STORAGE_CONNECTION_STRING, withAZURE_STORAGE_PUBLIC_ACCESS=false. - Upload or paste an image and send it to any model.
Expected: the image is read back and sent to the model.
Actual: the read fails, the raw blob URL goes to the provider, and the turn dies with a provider "failed to download" 400. The image tile does not render either, because the client puts the same URL into <img src>.
What should happen
azure_blob should work against a private container, which is the configuration most Azure tenants require:
getAzureFileStreamshould download through the authenticated client.- The strategy should implement
getDownloadURLwith a short-lived read-only SAS URL, matching what S3 already does.
I have a PR ready for both.
Version Information
Reproduced on v0.8.8-rc1; the code path is unchanged on main.
Additional context
One detail worth keeping in a SAS implementation: Azure validates the SAS st/se window against its own clock, so a token minted with startsOn = now is rejected with "Signature not valid in the specified time frame" whenever the host clock runs slightly ahead. Backdating the start by a few minutes avoids a class of intermittent 403s. (Chainlit hits the same trap with generate_blob_sas.)
Activity
Vidminas commentedon Oct 9, 2026
I'd like to suggest an alternative for the second half (browser display and downloads from a private container) that needs no SAS: #16915
With
STORAGE_PROXY_FILES=true, the Azure strategy stores links to LibreChat (/api/stored-files/azure_blob/<blob path>), and LibreChat streams each blob to viewers who may read it, through the authenticated container client. Links don't expire, and the container can stay private. It's off by default, so SAS (#14956, #13245) still applies when the proxy is off.@mihidumh your feedback would be welcome, if you're willing to test it.
mihidumh commentedon Oct 11, 2026
@Vidminas thanks for the pointer. The proxy fixes the part that SAS cannot: a stored link that does not expire. #14956 and #13245 both sign a URL at read time, so each one needs refresh code for every place that stores a URL. Your route removes that problem.
I cannot give you a live Azure test. Our deployment moved from Azure Blob to local storage on a mounted share when we found #14955, so no container of ours holds LibreChat files today. I can read #16914 and #16915 against the cases that #14955 lists. The ones I would check first:
/api/files/downloadrule);One question: with the proxy off, a private container still fails as #14955 describes. Do you see the SAS PRs as the fix for that case, or should the proxy become the default for Azure?