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
- 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.
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
- 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.
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
- You do not own this monitor.
- Permission denied.
- Accepted status codes are not all strings
- Invalid period.
- Incorrect current password
AI-assisted analysis of louislam/uptime-kuma@6b5ea01557 (2026-08-12).
Data as JSON: /api/errors/54f8c689cfdb3377.
Report an issue: GitHub.