{"record":{"id":"33732f3cee79d1cc","repo":"louislam/uptime-kuma","slug":"you-do-not-own-this-monitor","errorCode":null,"errorMessage":"You do not own this monitor.","messagePattern":"You do not own this monitor\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/server.js","lineNumber":1817,"sourceCode":"            relation.monitor_id = monitorID;\n            relation.notification_id = notificationID;\n            await R.store(relation);\n        }\n    }\n}\n\n/**\n * Check if a given user owns a specific monitor\n * @param {number} userID ID of user to check\n * @param {number} monitorID ID of monitor to check\n * @returns {Promise<void>}\n * @throws {Error} The specified user does not own the monitor\n */\nasync function checkOwner(userID, monitorID) {\n    let row = await R.getRow(\"SELECT id FROM monitor WHERE id = ? AND user_id = ? \", [monitorID, userID]);\n\n    if (!row) {\n        throw new Error(\"You do not own this monitor.\");\n    }\n}\n\n/**\n * Function called after user login\n * This function is used to send the heartbeat list of a monitor.\n * @param {Socket} socket Socket.io instance\n * @param {object} user User object\n * @returns {Promise<void>}\n */\nasync function afterLogin(socket, user) {\n    socket.userID = user.id;\n    socket.join(user.id);\n\n    let monitorList = await server.sendMonitorList(socket);\n    await Promise.allSettled([\n        sendInfo(socket),\n        server.sendMaintenanceList(socket),","sourceCodeStart":1799,"sourceCodeEnd":1835,"githubUrl":"https://github.com/louislam/uptime-kuma/blob/6b5ea0155793e666666745fb8d6fef1e829543a2/server/server.js#L1799-L1835","documentation":"Thrown by checkOwner(userID, monitorID) when a SQL lookup for a monitor row matching both monitorID and userID returns no row. It is the server-side ownership gate ensuring a user can only act on monitors belonging to them. The function is documented as rejecting when 'the specified user does not own the monitor.'","triggerScenarios":"Any handler that calls checkOwner after checkLogin, passing a monitorID that either does not exist or exists under a different user_id than socket.userID. The query SELECT id FROM monitor WHERE id = ? AND user_id = ? yields no row.","commonSituations":"Stale client state referencing a monitor that was deleted; cross-tenant access attempt; monitorID sent as a string/non-numeric so the parametrized query finds nothing; account switch where the cached monitor list belongs to another user.","solutions":["Refresh the monitor list on the client before acting on a monitor and use the current user's IDs only.","Confirm monitorID is being passed as the correct numeric value and matches a monitor in the user's own list.","If the monitor was shared/deleted, surface a not-found/permission UI instead of retrying the same call."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Verify ownership client-side before calling owned-monitor APIs\nconst owned = monitorList.some(m => m.id === monitorID);\nif (!owned) {\n  throw new Error(`monitor ${monitorID} not owned by current user`);\n}","typeGuard":"function isOwnedMonitor(monitorID, monitorList) {\n  return Array.isArray(monitorList)\n    && monitorList.some(m => Number(m.id) === Number(monitorID));\n}","tryCatchPattern":"try {\n  await emitAsync(\"someMonitorAction\", monitorID);\n} catch (e) {\n  if (/do not own this monitor/i.test(e.message)) refreshMonitorList();\n  else throw e;\n}","preventionTips":["Refresh the monitor list after login and account switches.","Pass monitorID as a number matching the list.","Disable UI controls for monitors not in the user's list."],"tags":["authorization","ownership","monitor","socket-io","security"],"backgroundTag":null,"analyzedSha":"6b5ea0155793e666666745fb8d6fef1e829543a2","analyzedAt":"2026-08-12T23:42:12.959Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}