{"record":{"id":"c9f6306e7588c5db","repo":"louislam/uptime-kuma","slug":"permission-denied-c9f630","errorCode":null,"errorMessage":"Permission denied.","messagePattern":"Permission denied\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/socket-handlers/maintenance-socket-handler.js","lineNumber":53,"sourceCode":"                maintenanceID,\n            });\n        } catch (e) {\n            callback({\n                ok: false,\n                msg: e.message,\n            });\n        }\n    });\n\n    // Edit a maintenance\n    socket.on(\"editMaintenance\", async (maintenance, callback) => {\n        try {\n            checkLogin(socket);\n\n            let bean = server.getMaintenance(maintenance.id);\n\n            if (bean.user_id !== socket.userID) {\n                throw new Error(\"Permission denied.\");\n            }\n\n            await Maintenance.jsonToBean(bean, maintenance);\n            await R.store(bean);\n            await bean.run(true);\n            await server.sendMaintenanceList(socket);\n\n            callback({\n                ok: true,\n                msg: \"Saved.\",\n                msgi18n: true,\n                maintenanceID: bean.id,\n            });\n        } catch (e) {\n            log.error(\"maintenance\", e);\n            callback({\n                ok: false,\n                msg: e.message,","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/louislam/uptime-kuma/blob/6b5ea0155793e666666745fb8d6fef1e829543a2/server/socket-handlers/maintenance-socket-handler.js#L35-L71","documentation":"Thrown by the editMaintenance handler when the maintenance bean returned by server.getMaintenance(maintenance.id) has a user_id different from socket.userID. Unlike checkOwner this is a per-handler ownership comparison against the in-memory maintenanceList entry, enforcing tenant isolation for edits.","triggerScenarios":"Logged-in user emits 'editMaintenance' with an id whose cached Maintenance belongs to another user. getMaintenance returns a bean (not null) but bean.user_id !== socket.userID.","commonSituations":"Cross-user access attempt; client reused a maintenance id from another account; maintenance shared/visible but not owned; race where maintenance was transferred to another user.","solutions":["Only emit editMaintenance for maintenance IDs the current user owns (check the maintenance list returned by sendMaintenanceList).","If collaborative editing is intended, that is not supported—surface read-only state instead.","Reload the maintenance list to discard stale/foreign IDs before editing."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const owned = maintenanceList.some(m => Number(m.id) === Number(maintenance.id) && m.user_id === currentUserID);\nif (!owned) throw new Error(\"Permission denied.\");\nsocket.emit(\"editMaintenance\", maintenance, cb);","typeGuard":"function isOwnedMaintenance(m, userID, list) {\n  return Array.isArray(list) && list.some(x => Number(x.id) === Number(m.id) && x.user_id === userID);\n}","tryCatchPattern":"try { await emitAsync(\"editMaintenance\", m, cb); }\ncatch (e) { if (/Permission denied/.test(e.message)) refreshMaintenanceList(); else throw e; }","preventionTips":["Only edit maintenances returned by the user's own list.","Send maintenance.id as the correct type.","Drop stale IDs after list reloads."],"tags":["authorization","maintenance","ownership","socket-io","security"],"backgroundTag":null,"analyzedSha":"6b5ea0155793e666666745fb8d6fef1e829543a2","analyzedAt":"2026-08-12T23:42:12.959Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}