GHSA-QHV3-WJG8-6FX6
Vulnerability from github – Published: 2026-06-12 18:28 – Updated: 2026-06-12 18:28The 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.
Details
The route appears to be builder-only:
packages/server/src/api/routes/webhook.ts:5-9
5:builderRoutes
6: .get("/api/webhooks", controller.fetch)
7: .put("/api/webhooks", webhookValidator(), controller.save)
8: .delete("/api/webhooks/:id/:rev", controller.destroy)
9: .post("/api/webhooks/schema/:instance/:id", controller.buildSchema)
However, webhook endpoint detection explicitly includes schema:
packages/server/src/middleware/utils.ts:3-9
3:const WEBHOOK_ENDPOINTS = new RegExp(
4: "^/api/webhooks/(trigger|schema|discord|ms-teams|slack)(/|$)"
5:)
6:
7:export function isWebhookEndpoint(ctx: UserCtx): boolean {
8: const path = ctx.path || ctx.request.url.split("?")[0]
9: return WEBHOOK_ENDPOINTS.test(path)
The authorization middleware bypasses all webhook endpoints before checking ctx.user or permissions:
packages/server/src/middleware/authorized.ts:90-99
90: ) =>
91: async (ctx: UserCtx, next: any) => {
92: // webhooks don't need authentication, each webhook unique
93: // also internal requests (between services) don't need authorized
94: if (isWebhookEndpoint(ctx) || ctx.internal) {
95: return next()
96: }
97:
98: if (!ctx.user) {
99: return ctx.throw(401, "No user info found")
The bypassed controller writes attacker-derived schema data to the webhook and automation trigger outputs:
packages/server/src/api/controllers/webhook.ts:56-83
56:export async function buildSchema(
57: ctx: Ctx<BuildWebhookSchemaRequest, BuildWebhookSchemaResponse>
58:) {
59: await context.doInWorkspaceContext(ctx.params.instance, async () => {
60: const db = context.getWorkspaceDB()
61: const webhook = await db.get<Webhook>(ctx.params.id)
62: webhook.bodySchema = toJsonSchema(ctx.request.body)
63: // update the automation outputs
64: if (webhook.action.type === WebhookActionType.AUTOMATION) {
65: let automation = await db.get<Automation>(webhook.action.target)
66: const autoOutputs = automation.definition.trigger.schema.outputs
67: let properties = webhook.bodySchema?.properties
68: // reset webhook outputs
69: autoOutputs.properties = {
70: body: autoOutputs.properties.body,
71: }
72: for (let prop of Object.keys(properties || {})) {
73: if (properties?.[prop] == null) {
74: continue
75: }
76: const def = properties[prop]
77: if (typeof def === "boolean") {
78: continue
79: }
80: autoOutputs.properties[prop] = {
81: type: def.type as AutomationIOType,
82: description: AUTOMATION_DESCRIPTION,
83: }
The route grouping suggests builder authorization was intended, but the global webhook bypass removes it.
PoC
Non-destructive validation approach:
- Create a webhook-backed automation as a builder.
- Record the workspace ID and webhook ID.
- Log out or send no auth headers.
- Send:
POST /api/webhooks/schema/<workspaceId>/<webhookId> HTTP/1.1
content-type: application/json
{"unauth_schema_probe":"test"}
- Fetch the webhook as a builder and observe that
bodySchemahas changed. - For automation-backed webhooks, inspect the automation trigger schema outputs and observe that properties were reset/updated.
Impact
An 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.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "@budibase/server"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.39.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-48151"
],
"database_specific": {
"cwe_ids": [
"CWE-862"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-12T18:28:34Z",
"nvd_published_at": "2026-05-27T18:16:27Z",
"severity": "HIGH"
},
"details": "The 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: ) =\u003e\n91: async (ctx: UserCtx, next: any) =\u003e {\n92: // webhooks don\u0027t need authentication, each webhook unique\n93: // also internal requests (between services) don\u0027t 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\u003cBuildWebhookSchemaRequest, BuildWebhookSchemaResponse\u003e\n58:) {\n59: await context.doInWorkspaceContext(ctx.params.instance, async () =\u003e {\n60: const db = context.getWorkspaceDB()\n61: const webhook = await db.get\u003cWebhook\u003e(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\u003cAutomation\u003e(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/\u003cworkspaceId\u003e/\u003cwebhookId\u003e 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.",
"id": "GHSA-qhv3-wjg8-6fx6",
"modified": "2026-06-12T18:28:34Z",
"published": "2026-06-12T18:28:34Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Budibase/budibase/security/advisories/GHSA-qhv3-wjg8-6fx6"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-48151"
},
{
"type": "PACKAGE",
"url": "https://github.com/Budibase/budibase"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "Budibase: Webhook schema endpoint authorization bypass allows unauthenticated mutation of webhook and automation schema"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.