From ba54421811d1101dd6313e135ca7d4c89fe1f9d2 Mon Sep 17 00:00:00 2001 From: Dexploarer <211557447+Dexploarer@users.noreply.github.com> Date: Sat, 25 Apr 2026 21:55:06 +0000 Subject: [PATCH] milady: add assertTableExists to database update/delete routes This patch adds `assertTableExists` checks to the `PUT` (handleUpdateRow) and `DELETE` (handleDeleteRow) endpoints in the database API. Previously, these endpoints used the raw tableName path parameter in raw SQL strings, which constituted an authorization bypass because malicious users could theoretically mutate arbitrary or internal tables. Now, update and delete mutations use the exact same validation mechanism as inserts and reads. --- .jules/sentinel.md | 4 ++++ src/api/database.ts | 10 ++++++++++ 2 files changed, 14 insertions(+) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000000..d79a36b640 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2025-04-26 - Missing Table Existence Check in Row Mutations +**Vulnerability:** Authorization bypass. PUT (update) and DELETE endpoints in /api/database/tables/:table/rows did not verify that the target table was a valid user table (using assertTableExists), unlike GET and POST endpoints. +**Learning:** This oversight allowed potential modification/deletion of arbitrary or internal tables if an attacker guessed the table name, bypassing the safety mechanisms intended to restrict queries to user-facing base tables. +**Prevention:** Always apply the exact same authorization and existence checks across all CRUD operations for a given resource. diff --git a/src/api/database.ts b/src/api/database.ts index 3f786fca9f..ef797fa2d1 100644 --- a/src/api/database.ts +++ b/src/api/database.ts @@ -970,6 +970,11 @@ async function handleUpdateRow( return; } + if (!(await assertTableExists(runtime, tableName))) { + sendJsonError(res, `Table "${tableName}" not found`, 404); + return; + } + const setClauses = Object.entries(body.data).map(([col, val]) => sqlAssign(col, val), ); @@ -1016,6 +1021,11 @@ async function handleDeleteRow( return; } + if (!(await assertTableExists(runtime, tableName))) { + sendJsonError(res, `Table "${tableName}" not found`, 404); + return; + } + const whereClauses = Object.entries(body.where).map(([col, val]) => sqlPredicate(col, val), );