VYPR
\n------WebKitFormBoundaryXXXXXXXXXXXXXXXX--\n```\n\n```json\nHTTP/1.1 200 OK\n\n[{\"size\":207,\"name\":\"xss.svg\",\"url\":\"http://target:10000/files/signed/.../.svg?X-Amz-...\",\"extension\":\"svg\",\"key\":\"workspace_id/attachments/.svg\"}]\n```\n### Impact\n* App end users - Stored XSS on any screen containing the attachment URL. Session cookie theft → full account takeover. |\n* Builder accounts - If malicious URL is shared within the workspace (table attachment, embedded image), XSS fires in builder's session → workspace takeover. \n\n\n\"image\"\n\n\n\"image\"\n\n\n\n\n\n\n\n--------\nDiscovered By:\nAbdulrahman Albatel\nAbdullah Alrasheed","additionalType":"https://schema.org/SoftwareApplication","sameAs":["https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2026-46426"]},"keywords":"CVE-2026-46426, high, Budibase Budibase, Budibase Budibase","mentions":[{"@type":"SoftwareApplication","name":"Budibase","applicationCategory":"SecurityApplication","publisher":{"@type":"Organization","name":"Budibase"}},{"@type":"SoftwareApplication","name":"Budibase","applicationCategory":"SecurityApplication","publisher":{"@type":"Organization","name":"Budibase"}}],"isAccessibleForFree":true},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://portal.vyprsec.ai/"},{"@type":"ListItem","position":2,"name":"CVEs","item":"https://portal.vyprsec.ai/cves"},{"@type":"ListItem","position":3,"name":"CVE-2026-46426","item":"https://portal.vyprsec.ai/cves/CVE-2026-46426"}]}]}
High severity7.6GHSA Advisory· Published May 19, 2026· Updated May 19, 2026

Budibase: Unrestricted Upload of File with Dangerous Type

CVE-2026-46426

Description

Summary

The file upload endpoint POST /api/attachments/process does not enforce active-content restrictions for authenticated users. The checks for dangerous file extensions (html, svg, js, php, etc.) are conditionally wrapped inside if (isPublicUser) or if (isPublicUser || !env.SELF_HOSTED), meaning any authenticated builder can upload executable web content — SVG files with inline ------WebKitFormBoundaryXXXXXXXXXXXXXXXX-- ` ``json

HTTP/1.1 200 OK

[{"size":207,"name":"xss.svg","url":"http://target:10000/files/signed/.../.svg?X-Amz-...","extension":"svg","key":"workspace_id/attachments/.svg"}] ``` ### Impact * App end users - Stored XSS on any screen containing the attachment URL. Session cookie theft → full account takeover. | * Builder accounts - If malicious URL is shared within the workspace (table attachment, embedded image), XSS fires in builder's session → workspace takeover. -------- Discovered By: Abdulrahman Albatel Abdullah Alrasheed

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Authenticated Budibase builders can upload active-content files (SVG, HTML, JS) via the attachment endpoint, leading to stored XSS for all app users.

Vulnerability

In Budibase server versions up to and including 3.30.6, the uploadFile function in packages/server/src/api/controllers/static/index.ts (lines 93–179) does not enforce active-content restrictions for authenticated users. The extension checks for dangerous file types (e.g., .html, .svg, .js, .php) are conditionally wrapped inside if (isPublicUser) or if (isPublicUser || !env.SELF_HOSTED), so an authenticated builder can upload files such as SVGs with inline ` tags, HTML files containing JavaScript, or .js` modules. These files are stored in the object store (MinIO/S3) with their correct MIME types and are served via signed URLs. [1][2][3]

Exploitation

An attacker needs a Budibase account with the Builder role (not necessarily admin) on a self-hosted Docker deployment. After authenticating via POST /api/attachments/process, the attacker uploads a crafted SVG, HTML, or JS file. The upload succeeds because the dangerous‑extension check is skipped for builders. The file is stored and a signed URL is generated. When any app user (end user of the published app) accesses that signed URL, the browser loads the file with its original MIME type and executes any embedded script, achieving stored cross‑site scripting (XSS). [2][3]

Impact

A successful attack results in persistent stored XSS over all application end users. The attacker can execute arbitrary JavaScript in the context of any victim’s browser session, potentially leading to session hijacking, data theft, or other malicious actions. No special privileges beyond the Builder role are required. [2][3]

Mitigation

Budibase released version 3.38.2 on 2026‑05‑19, which includes a security fix that blocks active content attachment uploads [4]. Users should upgrade to 3.38.2 or later. For self‑hosted deployments that cannot upgrade immediately, no official workaround is documented in the available references. [1][4]

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Budibase/BudibaseGHSA2 versions
    < 3.38.2+ 1 more
    • (no CPE)range: < 3.38.2
    • (no CPE)range: <= 3.30.6

Patches

1
1ef9af201571

Block active content attachment uploads

https://github.com/Budibase/budibasePeter ClementMay 11, 2026Fixed in 3.38.2via llm-release-walk
2 files changed · +39 26
  • packages/server/src/api/controllers/static/index.ts+24 21 modified
    @@ -50,8 +50,12 @@ import { isWorkspaceFullyMigrated } from "../../../workspaceMigrations"
     const ACTIVE_CONTENT_EXTENSIONS = new Set([
       "html",
       "htm",
    +  "js",
    +  "jse",
    +  "mjs",
       "svg",
       "svgz",
    +  "wasm",
       "xhtml",
       "mhtml",
       "shtml",
    @@ -60,6 +64,9 @@ const ACTIVE_CONTENT_EXTENSIONS = new Set([
     const ACTIVE_CONTENT_MIME_TYPES = [
       "text/html",
       "image/svg+xml",
    +  "application/javascript",
    +  "application/wasm",
    +  "text/javascript",
       "application/xhtml+xml",
     ]
     
    @@ -165,29 +172,25 @@ export const uploadFile = async function (
             )
           }
     
    -      if (isPublicUser) {
    -        if (ACTIVE_CONTENT_EXTENSIONS.has(extensionLower)) {
    -          throw new ActiveContentFileError(fileName)
    -        }
    +      if (ACTIVE_CONTENT_EXTENSIONS.has(extensionLower)) {
    +        throw new ActiveContentFileError(fileName)
    +      }
     
    -        const mimeType =
    -          typeof rawMimeType === "string"
    -            ? rawMimeType.toLowerCase()
    -            : undefined
    -        if (
    -          mimeType &&
    -          ACTIVE_CONTENT_MIME_TYPES.some(type => mimeType.includes(type))
    -        ) {
    -          throw new ActiveContentFileError(fileName)
    -        }
    +      const mimeType =
    +        typeof rawMimeType === "string" ? rawMimeType.toLowerCase() : undefined
    +      if (
    +        mimeType &&
    +        ACTIVE_CONTENT_MIME_TYPES.some(type => mimeType.includes(type))
    +      ) {
    +        throw new ActiveContentFileError(fileName)
    +      }
     
    -        if (
    -          filePath &&
    -          (typeof filePath === "string" || Buffer.isBuffer(filePath)) &&
    -          (await detectActiveContent(filePath))
    -        ) {
    -          throw new ActiveContentFileError(fileName)
    -        }
    +      if (
    +        filePath &&
    +        (typeof filePath === "string" || Buffer.isBuffer(filePath)) &&
    +        (await detectActiveContent(filePath))
    +      ) {
    +        throw new ActiveContentFileError(fileName)
           }
     
           // filenames converted to UUIDs so they are unique
    
  • packages/server/src/api/routes/tests/attachment.spec.ts+15 5 modified
    @@ -61,6 +61,15 @@ describe("/api/applications/:appId/sync", () => {
           )) as unknown as APIError
           expect(resp.message).toContain("No file provided")
         })
    +
    +    it("should reject active content for builder uploads", async () => {
    +      let resp = (await config.api.attachment.process(
    +        "xss.svg",
    +        Buffer.from("<svg><script>alert(1)</script></svg>"),
    +        { status: 400 }
    +      )) as unknown as APIError
    +      expect(resp.message).toContain("active content")
    +    })
       })
     
       describe("/api/attachments/:tableId/upload", () => {
    @@ -89,13 +98,14 @@ describe("/api/applications/:appId/sync", () => {
           })
         })
     
    -    it("should allow active content for authenticated users", async () => {
    -      const resp = await config.api.attachment.upload(
    +    it("should reject active content for authenticated users", async () => {
    +      const resp = (await config.api.attachment.upload(
             tableId,
             "image.png",
    -        Buffer.from("<svg><script>alert(1)</script></svg>")
    -      )
    -      expect(resp.length).toBe(1)
    +        Buffer.from("<svg><script>alert(1)</script></svg>"),
    +        { status: 400 }
    +      )) as unknown as APIError
    +      expect(resp.message).toContain("active content")
         })
       })
     })
    

Vulnerability mechanics

Synthesis attempt was rejected by the grounding validator. Re-run pending.

References

3

News mentions

0

No linked articles in our index yet.