fix(events): drop non-canonical keys from the event update before any check runs (#1346)
PUT /admin/events/:id spreads the body into the UPDATE. SQLite resolves
quoted identifiers case-insensitively, so `{ "Event_Name": ... }` lands
on event_name there — while every check in the handler (validators, the
field-level permission guards, the deny-set) keys on the exact lowercase
name. The deny-set already case-folded for its own columns; every other
column was reachable through a spelling variant.
Every events column and every input-only key the handler accepts is
lowercase snake_case, so a key with any uppercase in it is not something
a legitimate client sends. Such keys are now removed before anything
looks at the body. Postgres was unaffected (quoted identifiers are
case-sensitive there; a variant produced a 500 instead).
Surfaced by the Codex review of the folder-watcher change, where a
photos.upload guard on external_watch could be walked around this way.
Co-authored-by: Paul Nothaft <[email protected]>
This commit is contained in:
co-authored by
Paul Nothaft
parent
8cc7d7d14a
commit
810801a9ab
@@ -137,11 +137,17 @@ describe('authorization / ownership gaps', () => {
|
||||
// Case-variant keys — SQLite matches columns case-insensitively.
|
||||
Password_Hash: 'case-hijack-hash',
|
||||
Created_By: 88888,
|
||||
// A case variant of an ORDINARY column must not reach the UPDATE
|
||||
// either: field-level guards in the handler key on the exact name,
|
||||
// and on SQLite the variant would still land on the real column.
|
||||
Event_Name: 'case-variant-name',
|
||||
Welcome_Message: 'case-variant-welcome',
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const row = await db('events').where({ id: eventId }).first();
|
||||
expect(row.event_name).toBe('After'); // legit field applied
|
||||
expect(row.event_name).toBe('After'); // legit field applied; Event_Name variant dropped
|
||||
expect(row.welcome_message).toBeFalsy(); // case variant of an ordinary column dropped
|
||||
expect(row.created_by).toBe(superId); // ownership untouched (+ case-variant)
|
||||
expect(row.slug).toBe('authz-mass-assign'); // routing identity untouched
|
||||
expect(row.share_token).toBe(seedShareToken); // secret untouched
|
||||
|
||||
Reference in New Issue
Block a user