fix(security): cap message body length (LC-153) #188

Merged
nrupard merged 2 commits from feat/lc-153-message-length-cap into main 2026-05-22 20:46:19 +02:00
Owner

What

Closes the missing message-length bound from the LC-148 audit (finding S9).

Problem

post_message, patch_message, and post_thread_reply checked only that the trimmed body was non-empty - never an upper bound. A body up to Axum's default request cap (~2 MiB) was stored and broadcast to every room subscriber: an amplification vector.

Fix

MAX_MESSAGE_CHARS = 16_000 + a check_message_length helper returning 400 before any DB write or broadcast. Counted in characters (not bytes) so multibyte CJK / emoji text isn't unfairly penalized. Applied to all three write paths (new message, edit, thread reply). 16k chars is generous for chat while killing the 2 MiB case.

Test

message_over_length_cap_returns_400 (16_001 chars -> 400) and message_at_length_cap_is_accepted (16_000 chars -> 200). just check / just test / just test-saas green (the routes_uploads flake is pre-existing; no upload code touched).

Part of the LC-159 post-audit story.

🤖 Generated with Claude Code

## What Closes the missing message-length bound from the LC-148 audit (finding S9). ## Problem `post_message`, `patch_message`, and `post_thread_reply` checked only that the trimmed body was non-empty - never an upper bound. A body up to Axum's default request cap (~2 MiB) was stored and broadcast to every room subscriber: an amplification vector. ## Fix `MAX_MESSAGE_CHARS = 16_000` + a `check_message_length` helper returning **400** before any DB write or broadcast. Counted in **characters** (not bytes) so multibyte CJK / emoji text isn't unfairly penalized. Applied to all three write paths (new message, edit, thread reply). 16k chars is generous for chat while killing the 2 MiB case. ## Test `message_over_length_cap_returns_400` (16_001 chars -> 400) and `message_at_length_cap_is_accepted` (16_000 chars -> 200). `just check` / `just test` / `just test-saas` green (the `routes_uploads` flake is pre-existing; no upload code touched). Part of the LC-159 post-audit story. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(security): cap message body length (LC-153)
All checks were successful
check-secrets / Nosey parker (push) Successful in 3s
check-secrets / Kingfisher (push) Successful in 5s
check-secrets / TruffleHog (push) Successful in 6s
check-secrets / Kingfisher (pull_request) Successful in 5s
check-secrets / TruffleHog (pull_request) Successful in 5s
check-secrets / Nosey parker (pull_request) Successful in 5s
Check / clippy + fmt + tests (pull_request) Successful in 1m34s
bc48c01692
post_message / patch_message / post_thread_reply checked only that the trimmed body was non-empty, never an upper bound, so a ~2 MiB body (up to Axum's default request cap) was stored and broadcast to every room subscriber - an amplification vector (audit S9).

Adds MAX_MESSAGE_CHARS = 16_000 and a check_message_length helper that returns 400 before any DB write or broadcast. Counted in characters, not bytes, so multibyte (CJK / emoji) text is not unfairly penalized. Applied to new messages, edits, and thread replies.

Tests: a 16_001-char body is rejected with 400; a 16_000-char body is accepted.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fix(security): cap message length on the JSON API v1 POST too (LC-153)
All checks were successful
check-secrets / Nosey parker (push) Successful in 6s
check-secrets / Nosey parker (pull_request) Successful in 3s
check-secrets / Kingfisher (push) Successful in 9s
check-secrets / Kingfisher (pull_request) Successful in 11s
check-secrets / TruffleHog (pull_request) Successful in 10s
check-secrets / TruffleHog (push) Successful in 10s
Create release / Create release from merged PR (pull_request) Has been skipped
Check / clippy + fmt + tests (pull_request) Successful in 1m23s
f60c738de1
Review follow-up: the web composer paths got the 16k-char cap but the bearer-token API endpoint (POST /api/v1/rooms/{id}/messages) still inserted an unbounded body after only the empty check, leaving the same amplification vector open via the API. Make check_message_length pub(crate) and call it there after the empty check. New test: a 16_001-char API post returns 400.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
nrupard deleted branch feat/lc-153-message-length-cap 2026-05-22 20:46:20 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!188
No description provided.