Problem
approveEditSuggestion interpolates a user-supplied string as a column name:
// src/lib/companies/admin-moderation-service.ts:355-358
.from('shared_companies')
.update({ [suggestion.field_name]: suggestion.new_value })
company_edit_suggestions.field_name is declared VARCHAR(50) NOT NULL with no CHECK constraint and no allowlist, and the INSERT policy lets any authenticated user set it freely.
Failure scenario
A user submits an edit suggestion with field_name = 'is_verified', new_value = 'true', reason = 'fixing a typo in the name'. An admin skims the queue, sees a plausible-looking text correction, and clicks approve. The update flips the company's verified flag instead — the admin has no way to tell from the UI what column will actually be written.
Same shape works for metro_area_id or is_seed. If the named column doesn't exist, the update errors and takes out the moderation queue instead.
This needs an admin to click approve, so it is a missing-validation bug rather than a directly exploitable one — but the admin's click is not informed consent, because the moderation UI shows the reason, not the target column.
Fix
- Allowlist the writable columns in application code (
const EDITABLE = ['name','website','description', …]) and reject anything else at both submit and approve time.
- Add a
CHECK (field_name IN (...)) constraint so the database enforces it independently of the client.
- Surface the target column in the moderation UI so the approver sees what they are approving.
Acceptance
Problem
approveEditSuggestioninterpolates a user-supplied string as a column name:company_edit_suggestions.field_nameis declaredVARCHAR(50) NOT NULLwith no CHECK constraint and no allowlist, and the INSERT policy lets any authenticated user set it freely.Failure scenario
A user submits an edit suggestion with
field_name = 'is_verified',new_value = 'true',reason = 'fixing a typo in the name'. An admin skims the queue, sees a plausible-looking text correction, and clicks approve. The update flips the company's verified flag instead — the admin has no way to tell from the UI what column will actually be written.Same shape works for
metro_area_idoris_seed. If the named column doesn't exist, the update errors and takes out the moderation queue instead.This needs an admin to click approve, so it is a missing-validation bug rather than a directly exploitable one — but the admin's click is not informed consent, because the moderation UI shows the reason, not the target column.
Fix
const EDITABLE = ['name','website','description', …]) and reject anything else at both submit and approve time.CHECK (field_name IN (...))constraint so the database enforces it independently of the client.Acceptance
field_name = 'is_verified'is rejected at insertfield_nameexplicitly