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)

Symptom

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 page DB Access Error - debug_print_backtrace() OUTPUT START with absolute server paths (CWE-200);
  • after #1423 changed lib/functions/database.class.php to throw Exception(...) for BFF XHR requests, the throw escaped api/users/index.php uncaught → HTTP/1.0 500 with empty body.

Either way the client never gets {status:'error', message:...}.

User Management after UI edit-save

Repro steps

  1. Log in as admin (POST /api/auth/login).
  2. 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}.
  3. PUT /api/users/index.php/2 with {"locale":"<img src=x onerror=1>"} + header X-Requested-With: XMLHttpRequest.
  4. Pre-fix response: HTTP/1.0 500, empty body; events gets ERROR ON exec_query() ... 1406 - Data too long for column 'locale'.

Root cause

  1. api/users/index.php:359/:295 assign $u->locale = $body['locale'] verbatim — no length/allowed-value validation before the DB write.
  2. lib/functions/tlUser.class.php:432,443 writeToDB() runs exec_query() with locale = '<payload>'.
  3. users.locale varchar(10) under STRICT_TRANS_TABLES → 1406 Data too long, Execute() falsy (database.class.php:190).
  4. #1423 guard (database.class.php:204-207) throws Exception('Database error (query failed)') — HTML backtrace gone, but the throw is unhandled.
  5. api/users/index.php has no exception handling on either writeToDB() (:316 POST create, :364 PUT update) → naked HTTP 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.

Verification

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

Files changed

  • api/users/index.php (+19/-2) — try/catch (Throwable) on both writeToDB() call sites → 422 JSON db_write_failed on 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 + screenshot issue-1429-usersview-fixed.png.

Related findings (not fixed here)

  • #1423 (already landed) replaced the HTML backtrace with a throw — this fix completes the JSON contract on api/users writes.
  • Follow-ups with the same unguarded writeToDB(): api/userinfo/index.php:166 (profile PUT locale) and api/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.
⚠️ **GitHub.com Fallback** ⚠️