fix: reject feedback origins longer than the source_origin column

readTargetParam accepted values up to 3000 chars but the raw origin is
stored verbatim into source_origin VARCHAR(2048) (MySQL/Postgres), so a
2049-3000 char origin passed every validation and then blew up the
INSERT with an HTTP 500 on Postgres/strict MySQL — or was silently
truncated on non-strict MySQL, corrupting the abuse-forensics value the
column exists for. Cap the param at the column size.
This commit is contained in:
Nariman Jelveh
2026-08-11 18:27:42 -07:00
parent 1c2d33f149
commit ef7fc05dcc
2 changed files with 23 additions and 1 deletions
@@ -357,6 +357,20 @@ describe('AppFeedbackController POST /', () => {
).rejects.toMatchObject({ statusCode: 400 });
});
it('throws 400 when the origin exceeds the stored column size', async () => {
// source_origin is VARCHAR(2048) on MySQL/Postgres; a longer origin
// must be rejected up front, not fail (or silently truncate) at the
// INSERT after passing every other validation.
mockEmailConfigured();
const { userId: ownerId } = await makeUser();
const app = await makeApp(ownerId, { feedbackEnabled: true });
const origin = `${new URL(app.index_url).origin}/${'x'.repeat(2500)}`;
const { actor } = await makeUser();
await expect(
submit(actor, { origin, message: 'hi' }),
).rejects.toMatchObject({ statusCode: 400 });
});
it('throws 403 feedback_not_enabled when the app has not opted in', async () => {
mockEmailConfigured();
const { userId: ownerId } = await makeUser();
@@ -38,8 +38,16 @@ import { PuterController } from '../types.js';
/** Sanity cap on the raw body field; the service enforces the real limit. */
const RAW_MESSAGE_CAP = 50_000;
// Upper bound on the `app`/`origin` target params. Must not exceed the
// `source_origin` column (VARCHAR(2048) on MySQL/Postgres): the raw origin is
// stored verbatim, and a longer value would fail the INSERT after passing
// every validation — or be silently truncated on non-strict MySQL.
const TARGET_PARAM_MAX_LENGTH = 2048;
const readTargetParam = (value: unknown): string | undefined => {
return typeof value === 'string' && value.length > 0 && value.length <= 3000
return typeof value === 'string' &&
value.length > 0 &&
value.length <= TARGET_PARAM_MAX_LENGTH
? value
: undefined;
};