{"record":{"id":"54f8c689cfdb3377","repo":"louislam/uptime-kuma","slug":"permission-denied","errorCode":null,"errorMessage":"Permission denied.","messagePattern":"Permission denied\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/server.js","lineNumber":826,"sourceCode":"                log.error(\"monitor\", `Error adding Monitor: ${monitor.id} User ID: ${socket.userID}`);\n\n                callback({\n                    ok: false,\n                    msg: e.message,\n                });\n            }\n        });\n\n        // Edit a monitor\n        socket.on(\"editMonitor\", async (monitor, callback) => {\n            try {\n                let removeGroupChildren = false;\n                checkLogin(socket);\n\n                let bean = await R.findOne(\"monitor\", \" id = ? \", [monitor.id]);\n\n                if (bean.user_id !== socket.userID) {\n                    throw new Error(\"Permission denied.\");\n                }\n\n                // Check if Parent is Descendant (would cause endless loop)\n                if (monitor.parent !== null) {\n                    const childIDs = await Monitor.getAllChildrenIDs(monitor.id);\n                    if (childIDs.includes(monitor.parent)) {\n                        throw new Error(\"Invalid Monitor Group\");\n                    }\n                }\n\n                // Remove children if monitor type has changed (from group to non-group)\n                if (bean.type === \"group\" && monitor.type !== bean.type) {\n                    removeGroupChildren = true;\n                }\n\n                // Ensure status code ranges are strings\n                if (!monitor.accepted_statuscodes.every((code) => typeof code === \"string\")) {\n                    throw new Error(\"Accepted status codes are not all strings\");","sourceCodeStart":808,"sourceCodeEnd":844,"githubUrl":"https://github.com/louislam/uptime-kuma/blob/6b5ea0155793e666666745fb8d6fef1e829543a2/server/server.js#L808-L844","documentation":"Thrown by the 'editMonitor' socket handler when the loaded monitor's user_id does not equal socket.userID (the authenticated user). Note there is no null-check on the bean first, so a non-existent monitor would throw a TypeError on bean.user_id before reaching this; this error specifically means the monitor exists but is owned by a different user.","triggerScenarios":"An authenticated user edits a monitor ID they do not own. Triggered by tampering with the monitor.id in the edit payload, or a UI bug mixing monitors across accounts.","commonSituations":"Client-side monitor ID swapped/cached across users; multi-user instance where a user attempts to edit another's monitor; admin tooling that does not scope by user.","solutions":["Only send editMonitor for monitor IDs that belong to the current user (filter the monitor list by user_id).","Treat 'Permission denied.' as an authorization failure and refresh the monitor list.","Add a server-side ownership guard earlier (or null-check bean) to return a clean 403/404.","Avoid passing user-controlled monitor.id without first confirming ownership."],"exampleFix":"// before\nsocket.emit('editMonitor', { ...form, id: selectedId }, cb);\n\n// after\nconst owned = monitors.value.some((m) => m.id === selectedId && m.user_id === userID);\nif (!owned) return toast.error('Not your monitor');\nsocket.emit('editMonitor', { ...form, id: selectedId }, cb);","handlingStrategy":"try-catch","validationCode":"// Confirm the monitor belongs to the current user before editing\nconst owned = monitors.value.some((m) => m.id === Number(monitorId) && m.user_id === userID);\nif (!owned) return toast.error('You do not own this monitor');","typeGuard":"function userOwnsMonitor(monitors, monitorId, userID) {\n  return monitors.some((m) => m.id === Number(monitorId) && m.user_id === userID);\n}","tryCatchPattern":"try {\n  await emitAsync(socket, 'editMonitor', payload);\n} catch (e) {\n  if (/Permission denied/.test(e.message)) { refreshMonitorList(); return; }\n  throw e;\n}","preventionTips":["Filter the monitor list by the current user's ID.","Never trust a client-supplied monitor.id without ownership context.","Treat 'Permission denied.' as an authz failure and refresh state.","Consider a server-side null-check on bean to return a clean 404 too."],"tags":["monitor","authorization","ownership","socket","security"],"backgroundTag":null,"analyzedSha":"6b5ea0155793e666666745fb8d6fef1e829543a2","analyzedAt":"2026-08-12T23:42:12.959Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}