{"record":{"id":"7037677150c586b9","repo":"Budibase/budibase","slug":"notification-notificationdocid-does-not-belong","errorCode":null,"errorMessage":"Notification ${notificationDocId} does not belong to escalation ${escalationId}","messagePattern":"Notification (.+?) does not belong to escalation (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/server/src/sdk/workspace/escalations.ts","lineNumber":184,"sourceCode":"    return { status: \"closed\" }\n  }\n\n  const notifDoc = await db.tryGet<EscalationNotificationDoc>(notificationDocId)\n  if (!notifDoc) {\n    throw new Error(`Notification doc ${notificationDocId} not found`)\n  }\n  // Ensure the notification actually belongs to this escalation - stops a forged\n  // payload pairing a valid notificationDocId with a different escalationId.\n  if (notifDoc.escalationId !== escalationId) {\n    console.warn(\n      \"Escalation respond: notification does not belong to escalation (possible forged payload)\",\n      {\n        escalationId,\n        notificationDocId,\n        notifEscalationId: notifDoc.escalationId,\n      }\n    )\n    throw new Error(\n      `Notification ${notificationDocId} does not belong to escalation ${escalationId}`\n    )\n  }\n  await db.put({\n    ...notifDoc,\n    response,\n    respondedAt: new Date().toISOString(),\n  })\n\n  const notifDocs = await listNotifications(escalationId)\n  const totalRecipients = contextDoc.recipients?.length ?? 0\n  const responses = notifDocs\n    .filter(doc => doc.respondedAt)\n    .sort((a, b) => (a.respondedAt! < b.respondedAt! ? -1 : 1))\n    .map(doc => doc.response)\n\n  console.log(\"Escalation respond: responses so far\", {\n    escalationId,","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/sdk/workspace/escalations.ts#L166-L202","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Send the escalationId and notificationDocId exactly as issued together in the original notification payload","Audit client code that stores/pairs these IDs to ensure they are never combined across escalations","If this appears in logs unexpectedly, treat it as a potential forged-request attempt and verify the source of the payloads"],"exampleFix":"// before\nawait respond(otherEscalationId, response, resolve) // notifDoc.escalationId !== otherEscalationId\n// after\nawait respond(notifDoc.escalationId, response, resolve) // use the escalation ID stored on the notification","handlingStrategy":"type-guard","validationCode":"const notif = await db.tryGet<EscalationNotificationDoc>(notificationDocId)\nif (notif && notif.escalationId !== escalationId) {\n  throw new Error(\"Notification/escalation mismatch — possible forged payload\")\n}","typeGuard":"const belongsToEscalation = (\n  notif: EscalationNotificationDoc,\n  escalationId: string\n): notif is EscalationNotificationDoc & { escalationId: string } =>\n  notif.escalationId === escalationId","tryCatchPattern":"try {\n  await respond(escalationId, response, resolve)\n} catch (err) {\n  if (/does not belong to escalation/.test(err.message)) {\n    console.warn(\"Rejecting mismatched escalation response\")\n    return { status: \"rejected\" }\n  }\n  throw err\n}","preventionTips":["Always send the exact escalationId/notificationDocId pair issued together","Never mix IDs across escalations in bulk scripts","Investigate mismatches as potential forged requests — the server logs them as warnings"],"tags":["escalations","security","forged-payload","validation"],"backgroundTag":"ownership-mismatch","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}