Medium

ApostropheCMS uploadfs Azure backend leaks account-level SAS token into every public attachment URL (sas: true)

  • Disclosed: 20.10.2026
  • Updated: 20.10.2026 21:30

Summary

When uploadfs is configured with the Azure backend and sas: true (a supported configuration introduced in uploadfs 1.25.0), createContainer() builds the BlobServiceClient from a URL that embeds the full SAS query string, and Azure SDK v12 derived clients inherit that query string. As a result, getUrl() returns a base URL carrying the complete account/container SAS signature (?sv=...&sig=...), and Apostrophe's attachment.url() prepends it to every public attachment link — broadcasting a container-level SAS credential (commonly sp=rwdlac, full read/write/delete/list) to every site visitor, including anonymous ones. Anyone who can see an attachment URL can read, overwrite, delete, and list every blob in the container for the SAS lifetime. A companion functional bug (blob path appended after the query string, producing broken URLs) shows this code path was never exercised end to end. Shared-key mode (default) and non-Azure backends are not affected.

Verified at commit 5a3746aaed49761e171c2cbfe793267c959829fd (uploadfs 1.27.0 in the apostrophe monorepo).

Details

Root cause: a credential-bearing client URL is reused as the public base URL. The chain:

  • Credential-embedded client: azure.js#L123-L125 — new BlobServiceClient("https://<account>.blob.core.windows.net?<SAS query string>") where cluster.key is the full SAS string including sig
  • Leak point: azure.js#L513-L520 — getUrl() takes getContainerClient(...).getBlobClient('').url as baseUrl; SDK v12 derived clients inherit the SAS query string, so baseUrl already contains ?sv=...&sig=...
  • Public exposure: attachment/index.js#L694 — attachment.url() = uploadfs.getUrl() + path for every rendered attachment link; attachment/index.js#L1503 — getBrowserData() also ships uploadsUrl to every logged-in editor session
  • Companion bug: the blob path is appended after the query string (/container/?sv=...&sig=XXX/attachments/id-name), so these URLs are also broken — evidence the 1.25.0 code path was never tested

Attack surface: every public page rendering attachment URLs (images, download links), plus the uploadsUrl delivered to editor sessions. The leak is passive and unconditional — no action required from the victim beyond serving a page. The credential further propagates through HTML source, logs, Referer headers, and CDN/proxy access logs.

Suggested remediation:

  1. Strip the query string before building public URLs: const u = new URL(blob.svc.getContainerClient(...).getBlobClient('').url); u.search = ''; (or compose accountUrl + '/' + container manually), and append the blob path to the pathname, not after the query.
  2. Document that SAS mode must use least privilege (read-only, ideally sr=b per-blob), and prefer generating short-lived per-blob read-only signed URLs via generateBlobSASQueryParameters instead of embedding a service-level SAS in permanent public URLs.

PoC

Full PoC posted as the first comment below (poc.js + README; offline, no Azure account needed). It replicates the library's exact two steps with the declared dependency @azure/storage-blob@12:

  1. npm install @azure/storage-blob@12 && node poc.js
  2. The script constructs new BlobServiceClient("https://myaccount.blob.core.windows.net?sv=...&sig=FAKESIGNATURE") exactly like createContainer(), then evaluates the getUrl() logic (getContainerClient('uploads-container').getBlobClient('').url) and asserts the resulting base URL still contains the sig parameter.
  3. Observed output (exit code 0):
baseUrl: https://myaccount.blob.core.windows.net/uploads-container/?sv=2021-06-08&ss=b&srt=co&sp=rwdlac&se=2099-01-01&sig=FAKESIGNATURE
attachment URL as served to visitors: https://myaccount.blob.core.windows.net/uploads-container/?sv=2021-06-08&ss=b&srt=co&sp=rwdlac&se=2099-01-01&sig=FAKESIGNATURE/attachments/abc123-photo.jpg

The produced URL both leaks the token (sv…sig) and demonstrates the broken path placement. Cross-checking attachment/index.js confirms this URL reaches public pages unmodified.

Impact

  • What it is: insufficiently protected credentials (CWE-522) leading to information exposure (CWE-200). A container-level SAS token — commonly granted sp=rwdlac (read/write/delete/list/ACL) — is disclosed to every site visitor including anonymous users, valid for the SAS lifetime (often years). Holders can read, overwrite, delete, and enumerate every attachment in the container; the library applies no narrowing.
  • Who is impacted: Apostrophe sites using uploadfs with the Azure backend and sas: true (introduced in 1.25.0; present through 1.27.0). Default shared-key mode and other backends are unaffected. When the SAS is provisioned read-only, the impact reduces to unauthenticated read/download of all attachments.

Reported by Kimi Security Team (bug-report@moonshot.ai).

What to do

Upgrade to uploadfs version 1.28.0 or later.

Affected software

Click a technology to see all vulnerabilities affecting it.

PlatformPackageVulnerable versionsFixed in
npm package uploadfs >= 1.25.0, < 1.28.0 1.28.0

References

CVSS score 5.6/10
CVSS vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L
Weakness (CWE)
CWE-200, CWE-522
Platform
npm
Package
uploadfs
Disclosed
20.10.2026
Updated
20.10.2026 21:30

Sources and licenses · Edited or translated by DockRay; source rights and applicable licenses are retained.

OSV / GitHub Advisory Database - GHSA-hhph-8536-gfq5 - CC BY 4.0

Vulnerability history: uploadfs

All published vulnerabilities of this package (npm package), newest first. Shows whether this is a one-off slip or a package that needs watching.

This is the only known vulnerability of this package.

Know which vulnerabilities affect your application

With the DockRay integration and software inventory enabled, DockRay checks reported component versions against known vulnerabilities and notifies you about confirmed matches. Coverage depends on the supported technology and available advisory data.

Connect your application
DockRay newsletter

Application security in your inbox

Important vulnerabilities, actionable guidance and practical advice for your applications. Choose your technologies and follow what matters to your project.

  • A daily digest of new vulnerabilities, critical ones included.
  • Notifications about important updates.
  • Practical advice on application security and monitoring.

Leave empty to receive updates for every technology.

Free to subscribe. Unsubscribe with one click in any email.