louislam/uptime-kuma · error · Error

Permission denied.

Error message

Permission denied.

What it means

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.

Source

Thrown at server/server.js:826

                log.error("monitor", `Error adding Monitor: ${monitor.id} User ID: ${socket.userID}`);

                callback({
                    ok: false,
                    msg: e.message,
                });
            }
        });

        // Edit a monitor
        socket.on("editMonitor", async (monitor, callback) => {
            try {
                let removeGroupChildren = false;
                checkLogin(socket);

                let bean = await R.findOne("monitor", " id = ? ", [monitor.id]);

                if (bean.user_id !== socket.userID) {
                    throw new Error("Permission denied.");
                }

                // Check if Parent is Descendant (would cause endless loop)
                if (monitor.parent !== null) {
                    const childIDs = await Monitor.getAllChildrenIDs(monitor.id);
                    if (childIDs.includes(monitor.parent)) {
                        throw new Error("Invalid Monitor Group");
                    }
                }

                // Remove children if monitor type has changed (from group to non-group)
                if (bean.type === "group" && monitor.type !== bean.type) {
                    removeGroupChildren = true;
                }

                // Ensure status code ranges are strings
                if (!monitor.accepted_statuscodes.every((code) => typeof code === "string")) {
                    throw new Error("Accepted status codes are not all strings");

View on GitHub (pinned to 6b5ea01557)

Solutions

  1. Only send editMonitor for monitor IDs that belong to the current user (filter the monitor list by user_id).
  2. Treat 'Permission denied.' as an authorization failure and refresh the monitor list.
  3. Add a server-side ownership guard earlier (or null-check bean) to return a clean 403/404.
  4. Avoid passing user-controlled monitor.id without first confirming ownership.

Example fix

// before
socket.emit('editMonitor', { ...form, id: selectedId }, cb);

// after
const owned = monitors.value.some((m) => m.id === selectedId && m.user_id === userID);
if (!owned) return toast.error('Not your monitor');
socket.emit('editMonitor', { ...form, id: selectedId }, cb);
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm the monitor belongs to the current user before editing
const owned = monitors.value.some((m) => m.id === Number(monitorId) && m.user_id === userID);
if (!owned) return toast.error('You do not own this monitor');

Type guard

function userOwnsMonitor(monitors, monitorId, userID) {
  return monitors.some((m) => m.id === Number(monitorId) && m.user_id === userID);
}

Try / catch

try {
  await emitAsync(socket, 'editMonitor', payload);
} catch (e) {
  if (/Permission denied/.test(e.message)) { refreshMonitorList(); return; }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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