Bugfix Issue 1429 users API DB error JSON guard - sebiboga/testlink-upgraded GitHub Wiki
Issue 1429 — api/users: PUT/POST with locale > varchar(10) leaks DB debug backtrace instead of JSON error (CWE-200)
Issue: #1429
Branch: fix/issue-1429
Status: VERIFIED-FIXED (2026-09-12)
PUT /api/users/index.php/{id} (and the same POST /api/users create path) with a
locale longer than the DB column users.locale varchar(10) fails the write with
MariaDB strict-mode error 1406 - Data too long for column 'locale'. The BFF returned a
broken non-JSON response instead of the documented JSON error contract:
- before #1423,
exec_query()printed the raw HTML pageDB Access Error - debug_print_backtrace() OUTPUT STARTwith absolute server paths (CWE-200); - after #1423 changed
lib/functions/database.class.phptothrow Exception(...)for BFF XHR requests, the throw escapedapi/users/index.phpuncaught →HTTP/1.0 500with empty body.
Either way the client never gets {status:'error', message:...}.

- Log in as admin (
POST /api/auth/login). - Create a fixture user with a non-empty email (fresh-import admin has empty email →
E_EMAILLENGTH before SQL, unrelated):
POST /api/users{login:repro, email:[email protected], password:"Repro123!", locale:"en_GB", globalRoleID:5}. -
PUT /api/users/index.php/2with{"locale":"<img src=x onerror=1>"}+ headerX-Requested-With: XMLHttpRequest. - Pre-fix response:
HTTP/1.0 500, empty body;eventsgetsERROR ON exec_query() ... 1406 - Data too long for column 'locale'.
-
api/users/index.php:359/:295assign$u->locale = $body['locale']verbatim — no length/allowed-value validation before the DB write. -
lib/functions/tlUser.class.php:432,443writeToDB()runsexec_query()withlocale = '<payload>'. -
users.locale varchar(10)underSTRICT_TRANS_TABLES→1406 Data too long,Execute()falsy (database.class.php:190). - #1423 guard (
database.class.php:204-207) throwsException('Database error (query failed)')— HTML backtrace gone, but the throw is unhandled. -
api/users/index.phphas no exception handling on eitherwriteToDB()(:316POST create,:364PUT update) → nakedHTTP 500, empty body.
Blast-radius grep: same unguarded writeToDB() in api/userinfo/index.php:166 (profile
PUT, same locale field!) and api/roles/index.php:106,138 — follow-up candidates.
Method: minimal catch on the two write paths → HTTP 422 + {status:'error', message: 'Error creating/updating user', code:'db_write_failed'} — pure JSON, no exception text,
no paths, no HTML. Rejected alternative: up-front locale whitelist validation — changes
acceptance semantics; the DB constraint is the truth and a generic catch also covers
future constraint failures.
Regression suite Regression — Issue #1429 in tmp/TLU_Test_Cases.md (8/8 PASS):
| Case | Check |
|---|---|
| 1429.1 / 1429.2 | pre-fix 500/empty body reproduced → post-fix 422 JSON db_write_failed
|
| 1429.3 / 1429.4 | valid locale PUT (en_US) → 200 + persisted; valid POST create → 200
|
| 1429.5 | POST create locale > 10 chars → 422 JSON, no row persisted |
| 1429.6 | untouched routes (active toggle, DELETE, GET list/detail/meta) → all 200
|
| 1429.7 | UI smoke (usersView.html edit modal save) → toast, DB updated, 0 console errors |
| 1429.8 | Event Viewer: no new Error/Warning from valid operations |
-
api/users/index.php(+19/-2) — try/catch (Throwable) on bothwriteToDB()call sites →422JSONdb_write_failedon catch. -
CHANGELOG(+7) — KEY BUGFIX line referencing #1429. -
tmp/TLU_Test_Cases.md(+20) — regression suite 1429.1-1429.8. -
docs/Bugfix-Issue-1429-users-API-DB-error-JSON-guard.md+ this wiki page + screenshotissue-1429-usersview-fixed.png.
- #1423 (already landed) replaced the HTML backtrace with a throw — this fix completes the
JSON contract on
api/userswrites. - Follow-ups with the same unguarded
writeToDB():api/userinfo/index.php:166(profile PUTlocale) andapi/roles/index.php:106,138. - Fresh-import admin (empty email) makes any PUT on id=1 return
code:-2(E_EMAILLENGTH,tlUser.class.php:1079) — pre-existing, unrelated.