{"id":"CVE-2026-48151","aliases":["GHSA-qhv3-wjg8-6fx6"],"title":"Budibase: Webhook schema endpoint authorization bypass allows unauthenticated mutation of webhook and automation schema","summary":"Budibase: Webhook schema endpoint authorization bypass allows unauthenticated mutation of webhook and automation schema","severity":"high","cvss":7.5,"cwe":["CWE-862"],"vendor":"budibase","product":"@budibase/server","ecosystem":"npm","affected":["@budibase/server < 3.39.0"],"patched":["@budibase/server 3.39.0"],"published":"2026-06-12","updated":"2026-06-12","source":"GHSA","sourceUrl":"https://github.com/advisories/GHSA-qhv3-wjg8-6fx6","references":[{"url":"https://github.com/Budibase/budibase/security/advisories/GHSA-qhv3-wjg8-6fx6"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-48151"},{"url":"https://github.com/advisories/GHSA-qhv3-wjg8-6fx6"}],"tags":["ghsa","npm"],"epss":0.00224,"epssPercentile":0.13283,"ingestedAt":"2026-07-07T15:41:59.234Z","slug":"CVE-2026-48151","body":"## Overview\n\nThe webhook schema-building endpoint is registered under `builderRoutes`, but the generic authorization middleware skips authorization for all paths matching `/api/webhooks/schema`. As a result, an unauthenticated caller can update the body schema for a known webhook and mutate the corresponding automation trigger output schema.\n\n### Details\n\nThe route appears to be builder-only:\n\n- `packages/server/src/api/routes/webhook.ts:5-9`\n\n```ts\n5:builderRoutes\n6:  .get(\"/api/webhooks\", controller.fetch)\n7:  .put(\"/api/webhooks\", webhookValidator(), controller.save)\n8:  .delete(\"/api/webhooks/:id/:rev\", controller.destroy)\n9:  .post(\"/api/webhooks/schema/:instance/:id\", controller.buildSchema)\n```\n\nHowever, webhook endpoint detection explicitly includes `schema`:\n\n- `packages/server/src/middleware/utils.ts:3-9`\n\n```ts\n3:const WEBHOOK_ENDPOINTS = new RegExp(\n4:  \"^/api/webhooks/(trigger|schema|discord|ms-teams|slack)(/|$)\"\n5:)\n6:\n7:export function isWebhookEndpoint(ctx: UserCtx): boolean {\n8:  const path = ctx.path || ctx.request.url.split(\"?\")[0]\n9:  return WEBHOOK_ENDPOINTS.test(path)\n```\n\nThe authorization middleware bypasses all webhook endpoints before checking `ctx.user` or permissions:\n\n- `packages/server/src/middleware/authorized.ts:90-99`\n\n```ts\n90:  ) =>\n91:  async (ctx: UserCtx, next: any) => {\n92:    // webhooks don't need authentication, each webhook unique\n93:    // also internal requests (between services) don't need authorized\n94:    if (isWebhookEndpoint(ctx) || ctx.internal) {\n95:      return next()\n96:    }\n97:\n98:    if (!ctx.user) {\n99:      return ctx.throw(401, \"No user info found\")\n```\n\nThe bypassed controller writes attacker-derived schema data to the webhook and automation trigger outputs:\n\n- `packages/server/src/api/controllers/webhook.ts:56-83`\n\n```ts\n56:export async function buildSchema(\n57:  ctx: Ctx<BuildWebhookSchemaRequest, BuildWebhookSchemaResponse>\n58:) {\n59:  await context.doInWorkspaceContext(ctx.params.instance, async () => {\n60:    const db = context.getWorkspaceDB()\n61:    const webhook = await db.get<Webhook>(ctx.params.id)\n62:    webhook.bodySchema = toJsonSchema(ctx.request.body)\n63:    // update the automation outputs\n64:    if (webhook.action.type === WebhookActionType.AUTOMATION) {\n65:      let automation = await db.get<Automation>(webhook.action.target)\n66:      const autoOutputs = automation.definition.trigger.schema.outputs\n67:      let properties = webhook.bodySchema?.properties\n68:      // reset webhook outputs\n69:      autoOutputs.properties = {\n70:        body: autoOutputs.properties.body,\n71:      }\n72:      for (let prop of Object.keys(properties || {})) {\n73:        if (properties?.[prop] == null) {\n74:          continue\n75:        }\n76:        const def = properties[prop]\n77:        if (typeof def === \"boolean\") {\n78:          continue\n79:        }\n80:        autoOutputs.properties[prop] = {\n81:          type: def.type as AutomationIOType,\n82:          description: AUTOMATION_DESCRIPTION,\n83:        }\n```\n\nThe route grouping suggests builder authorization was intended, but the global webhook bypass removes it.\n\n### PoC\n\nNon-destructive validation approach:\n\n1. Create a webhook-backed automation as a builder.\n2. Record the workspace ID and webhook ID.\n3. Log out or send no auth headers.\n4. Send:\n\n```http\nPOST /api/webhooks/schema/<workspaceId>/<webhookId> HTTP/1.1\ncontent-type: application/json\n\n{\"unauth_schema_probe\":\"test\"}\n```\n\n5. Fetch the webhook as a builder and observe that `bodySchema` has changed.\n6. For automation-backed webhooks, inspect the automation trigger schema outputs and observe that properties were reset/updated.\n\n### Impact\n\nAn unauthenticated attacker can modify webhook schema metadata and automation trigger output schema for known webhook IDs. This can corrupt builder-visible automation definitions, alter downstream binding behavior, and disrupt webhook-backed automation workflows.\n\n## Affected packages\n\n- `@budibase/server < 3.39.0`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `@budibase/server 3.39.0`","depth":"twilight","depthScore":41,"depthScoreParts":{"impact":41.3,"likelihood":0,"exploitation":0,"ransomware":0},"changes":[]}