Skip to content

☁️ feat: Serve Azure Blob Files Through LibreChat With Stable Links - #16915

Open
Vidminas wants to merge 3 commits into
LibreChat-AI:devfrom
Vidminas:feat/stored-file-links-azure
Open

Vidminas wants to merge 3 commits into
LibreChat-AI:devfrom
Vidminas:feat/stored-file-links-azure

Conversation

@Vidminas

@Vidminas Vidminas commented Oct 9, 2026 •

Copy link
Copy Markdown

Pull Request

Before submitting, please review the Contributing Guide.

Documentation changes belong in the LibreChat documentation repository.

👍

Summary

With fileStrategy: "azure_blob", LibreChat stores plain blob URLs, so browsers can only show images from a public container. A private container breaks every image and preview (#13340, #14955, discussion #6704).

This adds the Azure adapter for the stored-file route. With STORAGE_PROXY_FILES=true:

  • Stored links: uploads, saved buffers and avatars in the configured container store /api/stored-files/azure_blob/<blob path>.
  • Reading them back: getAzureFileStream and deleteFileFromAzure resolve those links to blob paths.
  • Access: the route serves blobs under the same rules as S3.

Unlike SAS URLs (#11137, #13245, #14956), these links don't expire, so nothing needs refreshing. Off by default.

Addresses #16628 for files saved with the setting on: uploads, generated images and outputs, avatars and downloads. Links already stored in conversation history are rewritten by #16916.

Related to #16912 and #14955. Depends on #16914

How it works

packages/api/src/storage/azure/source.ts
  getAzureFileLink(blobURL, blobPath, container?)  # stored link when on and in the configured container
  getAzureBlobPath(link)                           # blob path behind a stored link
  azureFileSource                                  # adapter: parseKey, read (download / getProperties), isNotFound
api/server/services/Files/Azure/crud.js            # save, upload, link, delete and stream use the helpers
  • Avatars: Azure keeps them beside other images, so the adapter marks avatar-<ts> and agent-<id>-avatar-<ts> files as avatars, which any signed-in user may see.
  • Other containers: a stored link names no container, so blobs in any container but the configured one keep their URLs.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Performance improvement
  • Breaking change
  • Documentation
  • Translation
  • Tests / tooling / CI

Testing

  1. Set fileStrategy: "azure_blob", AZURE_STORAGE_PUBLIC_ACCESS=false and STORAGE_PROXY_FILES=true, with a private container.
  2. Upload an image and attach a document. Both display and download; their links are /api/stored-files/azure_blob/....
  3. Upload an avatar. It displays for other signed-in users.
  4. Delete the file. The blob is removed.

Tested environments/configuration:

This is a proof-of-concept adapter for Azure, I'm focusing on the S3 side. These changes are not yet tested with a real Azure account. Feedback from Azure users is welcome (cc @mihidumh, @JorgeCosta87, @adamfisher)

Automated tests:

  • npm run static-checks -- --against upstream/dev --full passes.
    New tests:
  • storage/azure/__tests__/source.test.ts:
    • links in both modes, and the other-container fallback;
    • avatar detection;
    • downloads, and HEAD from blob properties;
    • missing blobs.
  • services/Files/Azure/crud.spec.js: uploads store stored links, and stream and delete resolve them in the configured container. The existing blob-URL cases pass unchanged.

Caveat:

  • No route-level Azure test: the built package loads @azure/storage-blob with a native import(), which Jest can't mock. The adapter's unit tests cover reading instead.

Screenshots / recordings

No user-facing change. Images and files look the same; only their URLs change.

Risk / compatibility

Checklist

  • I reviewed my own changes
  • Relevant tests have been added or updated
  • Existing relevant tests pass
  • The change does not introduce new warnings or errors
  • User-facing or complex behavior is documented where necessary
  • Required dependency changes have been merged/published
  • Required documentation PR: ☁️ docs: Document Serving Azure Blob Files Through LibreChat docs#818

Vidminas and others added 3 commits October 9, 2026 09:50
The fileAccess middleware decided inline whether a user may access a
file: same tenant for tenant-scoped files, then ownership, then access
through the agents the file is attached to. The decision now lives in
canAccessFile(user, file), which the middleware calls and exports, so a
route that already holds a file record can apply the same rules without
going through req.params.file_id.

Behaviour is unchanged; the cross-tenant denial is still logged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With fileStrategy s3, browsers load images, previews and downloads
straight from presigned URLs. Those links expire after
S3_URL_EXPIRY_SECONDS and are stored in messages and file records, so
images break once they expire, and the bucket has to answer requests
from the internet, so it cannot be kept private to the deployment's
network (for instance with a bucket policy on aws:SourceVpce).

STORAGE_PROXY_FILES=true makes storage strategies link files to
LibreChat instead: /api/stored-files/<source>/<key>, which never expires.
The route reads the object through the strategy's StoredFileSource
adapter and streams it to viewers who may read it. This commit adds the
storage-agnostic route and links, and the S3 adapter. With the proxy on,
getS3URL returns stored file links, extractKeyFromS3Url reads them back
to keys, presigned links are replaced where the file list and avatars
already renew links, and getS3DownloadURL returns no direct link, so the
download and shared-link routes stream through their own checks. With it
off, stored links go back to presigned ones the same way. Off by
default.

The route signs the viewer in from the session cookie, as /images does,
since an image tag sends no bearer token; authenticateViewer carries the
image route's account checks (retired tokens, required 2FA enrollment).
Within the viewer's tenant it serves the owner named in the key, avatars
to any signed-in user (as CloudFront avatar cookies do), and anyone the
stored file's own rules admit through canAccessFile, such as a file on
an agent shared with the viewer; everyone else gets a 404. The file
lookup is scoped to the key's owner and source. Uploads are now served
from the app's own origin, so only raster images are sent inline; every
response carries a sandboxing CSP and nosniff.

Related to LibreChat-AI#16912

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With fileStrategy azure_blob, LibreChat stores each blob's plain URL, so
browsers can only display images from a container with public blob
access. A private container (AZURE_STORAGE_PUBLIC_ACCESS=false, or an
account that disallows public access) breaks every image and preview.

This adds the Azure adapter for the stored-file route. With
STORAGE_PROXY_FILES=true, uploads, saved buffers and avatars in the
configured container store /api/stored-files/azure_blob/<blob path>
instead of the blob URL, getAzureFileStream and deleteFileFromAzure read
those links back to blob paths, and the route streams blobs to viewers
who may read them under the same rules as S3. Azure keeps avatars beside
other images, so the adapter marks avatar-<ts> and agent-<id>-avatar-<ts>
files as avatars. Blobs in any other container keep their URLs, since a
stored link names no container. Off by default.

Related to LibreChat-AI#16912

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 9, 2026 10:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

This branch has not been deployed

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

Labels

🗺️ Backend Infra codegraph: the taxonomy area this belongs to (classifier, confidence ≥ 0.9) 🗺️ Platform Security codegraph: the taxonomy area this belongs to (classifier, confidence ≥ 0.9) 🛡️ security review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants