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

  1. Only emit editMaintenance for maintenance IDs the current user owns (check the maintenance list returned by sendMaintenanceList).
  2. If collaborative editing is intended, that is not supported—surface read-only state instead.
  3. 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

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


AI-assisted analysis of louislam/uptime-kuma@6b5ea01557 (2026-08-12). Data as JSON: /api/errors/c9f6306e7588c5db. Report an issue: GitHub.