fix: four validation/correctness audit findings (LC-355, LC-357, LC-358, LC-359) #369

Merged
longjacksonle merged 3 commits from fix/lc-355-357-358-359-audit-med into main 2026-06-19 05:31:31 +02:00

The four quick validation/correctness MED findings from the UI audit. just test and just test-saas both green; 5 new regression tests.

LC-355 - branding login text not capped (LC-153)

parse_branding_multipart stored login_heading/login_body unbounded; bounds were client-only. login_body is markdown-rendered on the PUBLIC /login page, so a forged multipart is an uncapped per-pageload render on an unauthenticated route. Cap at 120 / 2000 chars, inline error via the parser's existing Ok(Err) contract. Test: over-long body re-renders with the error and does not persist.

LC-357 - bridge/webhook targets unvalidated

post_bridges and post_outgoing_webhooks never checked room_id / scope_id existed (or, for bridges, was non-DM). Validate the room/enclave exists before acting; the bridge check runs before the bot is minted so a bad room can't orphan a bot + token. Tests: bridge and webhook to a nonexistent target re-render inline and create nothing.

LC-358 - site-wide rate-limit/quota unbounded

rate_limit_messages and quota_mib had no ceiling (the per-enclave burst is capped at 10000). Clamp rate_limit_messages to 10000, reject quota_mib above 16 TiB, matching max= on both inputs. Tests: 99999 clamps to 10000; an over-max quota 400s and doesn't persist.

LC-359 - multiline message edit

The inline edit form was a single-line <input>, clipping multiline messages to their first line. Swap for a <textarea> (field name unchanged); Escape cancels, Ctrl/Cmd+Enter saves so Enter inserts a newline.

Closes LC-355, LC-357, LC-358, LC-359.

The four quick validation/correctness MED findings from the UI audit. `just test` and `just test-saas` both green; 5 new regression tests. ## LC-355 - branding login text not capped (LC-153) `parse_branding_multipart` stored `login_heading`/`login_body` unbounded; bounds were client-only. `login_body` is markdown-rendered on the PUBLIC `/login` page, so a forged multipart is an uncapped per-pageload render on an unauthenticated route. Cap at 120 / 2000 chars, inline error via the parser's existing `Ok(Err)` contract. Test: over-long body re-renders with the error and does not persist. ## LC-357 - bridge/webhook targets unvalidated `post_bridges` and `post_outgoing_webhooks` never checked `room_id` / `scope_id` existed (or, for bridges, was non-DM). Validate the room/enclave exists before acting; the bridge check runs before the bot is minted so a bad room can't orphan a bot + token. Tests: bridge and webhook to a nonexistent target re-render inline and create nothing. ## LC-358 - site-wide rate-limit/quota unbounded `rate_limit_messages` and `quota_mib` had no ceiling (the per-enclave burst is capped at 10000). Clamp `rate_limit_messages` to 10000, reject `quota_mib` above 16 TiB, matching `max=` on both inputs. Tests: 99999 clamps to 10000; an over-max quota 400s and doesn't persist. ## LC-359 - multiline message edit The inline edit form was a single-line `<input>`, clipping multiline messages to their first line. Swap for a `<textarea>` (field name unchanged); Escape cancels, Ctrl/Cmd+Enter saves so Enter inserts a newline. Closes LC-355, LC-357, LC-358, LC-359.
parse_branding_multipart stored login_heading/login_body from field.text() unbounded; the bounds existed only as client maxlength attributes. login_body is rendered through markdown on the PUBLIC /login page, so a forged multipart bypassing the client cap is the LC-153 hazard (an uncapped per-pageload synchronous render on an unauthenticated route). Cap heading at 120 and body at 2000 chars (matching the template), returning an inline error via the function's existing Ok(Err(msg)) contract so the caller re-renders the page. Test: an over-long body re-renders with the error and does not persist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The hover "Edit" form rendered the body into a single-line <input>, so a multiline message (code blocks, lists, paragraphs) was clipped to its first visual line and easy to accidentally flatten on save. Swap it for a <textarea> (field name unchanged). Escape still cancels; Ctrl/Cmd+Enter saves so a plain Enter inserts a newline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(admin): validate bridge/webhook targets and bound rate-limit/quota (LC-357, LC-358)
All checks were successful
check-secrets / Nosey parker (push) Successful in 3s
check-secrets / Kingfisher (pull_request) Successful in 4s
check-secrets / Kingfisher (push) Successful in 5s
check-secrets / TruffleHog (pull_request) Successful in 6s
check-secrets / TruffleHog (push) Successful in 6s
check-secrets / Nosey parker (pull_request) Successful in 4s
Create release / Create release from merged PR (pull_request) Has been skipped
Check / clippy + fmt + tests (pull_request) Successful in 2m42s
3883d50e8a
LC-357: post_bridges and post_outgoing_webhooks took room_id / scope_id from the form and never checked the target existed (or, for bridges, was a non-DM room), so a forged POST wrote an orphan row pointing at a nonexistent or DM room. Validate the room/enclave exists before acting; for bridges the check runs before the bot is minted so a bad room cannot orphan a bot + token. Tests: bridge/webhook to a nonexistent target re-render inline with an error and create nothing.

LC-358: the site-wide rate_limit_messages and per-user quota_mib had no upper bound (the per-enclave burst is capped at 10000). Clamp rate_limit_messages to 10000 and reject quota_mib above 16 TiB, with matching max= attributes on both inputs. Tests: 99999 rate limit clamps to 10000; an over-max quota returns 400 and does not persist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch fix/lc-355-357-358-359-audit-med 2026-06-19 05:31:31 +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!369
No description provided.