Skip to content

Azure Blob: all file reads fail on a private container (unauthenticated getAzureFileStream, no SAS download URL) #14955

Description

@mihidumh

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.

  • saveBufferToAzure uses the SDK client built from AZURE_STORAGE_CONNECTION_STRING (or Managed Identity) — api/server/services/Files/Azure/crud.js.
  • getAzureFileStream does 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/preview and /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

  1. Create a storage account with allowBlobPublicAccess: false (or a container with private access level).
  2. Set fileStrategy: azure_blob and a valid AZURE_STORAGE_CONNECTION_STRING, with AZURE_STORAGE_PUBLIC_ACCESS=false.
  3. 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:

  1. getAzureFileStream should download through the authenticated client.
  2. The strategy should implement getDownloadURL with 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

  1. added
    🗺️ File Storagecodegraph: the taxonomy area this belongs to (classifier, confidence ≥ 0.9)
    🗺️ Server Corecodegraph: the taxonomy area this belongs to (classifier, confidence ≥ 0.9)
    🗺️ Backend Infracodegraph: the taxonomy area this belongs to (classifier, confidence ≥ 0.9)
    on Oct 1, 2026
  2. Vidminas commented on Oct 9, 2026

    @Vidminas

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    🐛 bugSomething isn't working🗺️ Backend Infracodegraph: the taxonomy area this belongs to (classifier, confidence ≥ 0.9)🗺️ File Storagecodegraph: the taxonomy area this belongs to (classifier, confidence ≥ 0.9)🗺️ Platform Securitycodegraph: the taxonomy area this belongs to (classifier, confidence ≥ 0.9)🗺️ Server Corecodegraph: the taxonomy area this belongs to (classifier, confidence ≥ 0.9)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions