louislam/uptime-kuma · error · Error
Permission denied.
Error message
Permission denied.
What it means
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.
Source
Thrown at server/socket-handlers/maintenance-socket-handler.js:53
maintenanceID,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
// Edit a maintenance
socket.on("editMaintenance", async (maintenance, callback) => {
try {
checkLogin(socket);
let bean = server.getMaintenance(maintenance.id);
if (bean.user_id !== socket.userID) {
throw new Error("Permission denied.");
}
await Maintenance.jsonToBean(bean, maintenance);
await R.store(bean);
await bean.run(true);
await server.sendMaintenanceList(socket);
callback({
ok: true,
msg: "Saved.",
msgi18n: true,
maintenanceID: bean.id,
});
} catch (e) {
log.error("maintenance", e);
callback({
ok: false,
msg: e.message,View on GitHub (pinned to 6b5ea01557)
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.
Defensive patterns
Strategy: validation
Validate before calling
const owned = maintenanceList.some(m => Number(m.id) === Number(maintenance.id) && m.user_id === currentUserID);
if (!owned) throw new Error("Permission denied.");
socket.emit("editMaintenance", maintenance, cb); Type guard
function isOwnedMaintenance(m, userID, list) {
return Array.isArray(list) && list.some(x => Number(x.id) === Number(m.id) && x.user_id === userID);
} Try / catch
try { await emitAsync("editMaintenance", m, cb); }
catch (e) { if (/Permission denied/.test(e.message)) refreshMaintenanceList(); else throw e; } Prevention
- Only edit maintenances returned by the user's own list.
- Send maintenance.id as the correct type.
- Drop stale IDs after list reloads.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- You do not own this monitor.
- Permission denied.
- Invalid language
- Maintenance not found
- docker host not found
AI-assisted analysis of louislam/uptime-kuma@6b5ea01557 (2026-08-12).
Data as JSON: /api/errors/c9f6306e7588c5db.
Report an issue: GitHub.