Budibase/budibase · error

Notification ${notificationDocId} does not belong to escalat

Error message

Notification ${notificationDocId} does not belong to escalation ${escalationId}

What it means

An anti-forgery check in the escalation respond flow: the notification document exists, but its escalationId field does not match the escalationId supplied in the request. This catches forged payloads that pair a valid notificationDocId with a different escalation to inject unauthorized responses. The mismatch is logged as a warning and the request is rejected.

Source

Thrown at packages/server/src/sdk/workspace/escalations.ts:184

    return { status: "closed" }
  }

  const notifDoc = await db.tryGet<EscalationNotificationDoc>(notificationDocId)
  if (!notifDoc) {
    throw new Error(`Notification doc ${notificationDocId} not found`)
  }
  // Ensure the notification actually belongs to this escalation - stops a forged
  // payload pairing a valid notificationDocId with a different escalationId.
  if (notifDoc.escalationId !== escalationId) {
    console.warn(
      "Escalation respond: notification does not belong to escalation (possible forged payload)",
      {
        escalationId,
        notificationDocId,
        notifEscalationId: notifDoc.escalationId,
      }
    )
    throw new Error(
      `Notification ${notificationDocId} does not belong to escalation ${escalationId}`
    )
  }
  await db.put({
    ...notifDoc,
    response,
    respondedAt: new Date().toISOString(),
  })

  const notifDocs = await listNotifications(escalationId)
  const totalRecipients = contextDoc.recipients?.length ?? 0
  const responses = notifDocs
    .filter(doc => doc.respondedAt)
    .sort((a, b) => (a.respondedAt! < b.respondedAt! ? -1 : 1))
    .map(doc => doc.response)

  console.log("Escalation respond: responses so far", {
    escalationId,

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Send the escalationId and notificationDocId exactly as issued together in the original notification payload
  2. Audit client code that stores/pairs these IDs to ensure they are never combined across escalations
  3. If this appears in logs unexpectedly, treat it as a potential forged-request attempt and verify the source of the payloads

Example fix

// before
await respond(otherEscalationId, response, resolve) // notifDoc.escalationId !== otherEscalationId
// after
await respond(notifDoc.escalationId, response, resolve) // use the escalation ID stored on the notification
Defensive patterns

Strategy: type-guard

Validate before calling

const notif = await db.tryGet<EscalationNotificationDoc>(notificationDocId)
if (notif && notif.escalationId !== escalationId) {
  throw new Error("Notification/escalation mismatch — possible forged payload")
}

Type guard

const belongsToEscalation = (
  notif: EscalationNotificationDoc,
  escalationId: string
): notif is EscalationNotificationDoc & { escalationId: string } =>
  notif.escalationId === escalationId

Try / catch

try {
  await respond(escalationId, response, resolve)
} catch (err) {
  if (/does not belong to escalation/.test(err.message)) {
    console.warn("Rejecting mismatched escalation response")
    return { status: "rejected" }
  }
  throw err
}

Prevention

When it happens

Trigger: Calling respond where notificationDocId points to a notification belonging to a different escalation — manually crafted requests mixing IDs from two escalations, or payloads reordered/merged incorrectly by client code.

Common situations: A malicious or buggy client replaying one escalation's notification ID against another escalation's ID; bulk scripts that zip two ID lists out of alignment.

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/7037677150c586b9. Report an issue: GitHub.