Defensive coding: sanitize free text, constrain inputs

Player-authored free text is scrubbed at the API boundary before storage via a
new validation.py: drop control characters and unpaired surrogates, trim
whitespace, and cap length (names 120, other text 2000). Applied to crew/player
names, the character sheet, like/hate answers, techniques, recruit techniques,
the renamed Name, the Gat description, and challenge stakes. Values matched
against stored text (swap offers, face-card assignments) and identifiers/enums
are left untouched so they still compare equal.

Constrained set-role to the two real roles so it can't stash an arbitrary string
in the column. Audited the rest: queries are parameterized by SQLModel (the lone
f-string SQL in database.py is the hardcoded legacy-migration list, no user
input); objective-type and rollback writes already whitelist their keys; no
endpoint accepts a generic (field, value) write.

Tests cover the sanitizer (control chars, surrogates, emoji, caps) and the
create/join HTTP path end-to-end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 15:54:21 -07:00
parent ccb32e7103
commit 122e66b817
9 changed files with 120 additions and 14 deletions

View File

@@ -6,7 +6,7 @@
// - Add a CHANGELOG entry only when a commit changes something players can
// see. Skip refactors, tests, and tooling. Keep wording player-facing.
export const VERSION = 10;
export const VERSION = 11;
// Newest first. Each entry: { version, date: 'YYYY-MM-DD', changes: [string, ...] }.
export const CHANGELOG = [