Repository navigation
Bugfix Issue 1576 CodeTracker Cfg Token Leak
Bug fix — Issue #1576: codetracker GET list/detail leaks raw cfg (plaintext token) to codetracker_view-only users
The modern Code Tracker BFF (api/codetracker/index.php) returns each
tracker's raw cfg field — the DB codetrackers.cfg XML — on the READ routes
(GET /api/codetracker/index.php list and GET /api/codetracker/index.php/{id}
detail). For GitHub trackers that XML stores the access token in plaintext
(<token>ghp_…</token>). A user holding only codetracker_view (right 52, no
codetracker_management right 51) receives the full list/detail including
those tokens, while the same payload already masks the parsed
github.token as ******** — the raw cfg defeats that masking. Gap vs
legacy codeTrackerView.tpl, which only ever rendered name/type/env-check and
never the config XML.
- Fixtures: user
ctviewonly(role 10 holding ONLY right 52codetracker_view, verified viarole_rights), admin (role 8). GitHub trackerrepro-1576-gh(id 2, type 200) created via the API with cfg<codetracker>\n <repository>https://github.com/acme/secret</repository>\n <branch>main</branch>\n <token>ghp_SUPERSECRETTOKEN123</token>\n</codetracker>. - Logged in as
ctviewonly(isolated browser context):GET /api/codetracker/index.phpandGET /api/codetracker/index.php/2. - Pre-fix: HTTP 200 on both;
cfgreturned the full XML verbatim with the plaintext token,github.token=********. Post-fix: HTTP 200,cfg="",github.tokenstill********.
-
api/codetracker/index.php:37— the page-level gate only splits allowed (codetracker_viewORcodetracker_management) vs denied. A view-only user passes and reaches every data route. -
api/codetracker/index.php:77-88— the GitHub native parser (issue #433) masks only the parsed copy:github['token'] = '********'when non-empty. -
api/codetracker/index.php:96(pre-fix) —'cfg' => $item['cfg'] ?? ''copied the raw XML (token/API keys in plaintext) verbatim into EVERY item for EVERY user. The parsed-member mask is invisible to it. - Both read routes reuse the same helper: beacon list
:110, detail:134. - Legacy expectation:
gui/templates/dashio/codetrackers/codeTrackerView.tplnever surfaces the XML; the cfg only reaches the browser through the legacy EDIT page which is gated oncodetracker_management(codeTrackerEdit.php:181-184).
Why it breaks NOW: the GitHub-native BFF work (issue #433) kept raw cfg
round-tripping because the edit modal prefills the generic XML textarea from it
(gui/templates/codetracker/codetrackerView.html:263), but forgot to gate that
field on the management right.
Blast radius: trackerToJSON() call sites returning cfg — list :110,
detail :134, create-response :156, update-response :178, delete-response
:297. Frontend consumer: codetrackerView.html:263/:340 (edit prefill /
generic save). Data source: single DB column codetrackers.cfg. No i18n keys
involved (no new strings).
-
api/codetracker/index.php— after the read gate:$canManage = ($user->hasRight($db, 'codetracker_management') == 'yes');— mirrors legacy$gui->canManage(codeTrackerView.php:24).hasRight()returns the string'yes'or null (lib/functions/roles.inc.php:254-273), hence the explicit== 'yes'. -
trackerToJSON($item, $mgr)→trackerToJSON($item, $mgr, $canManage); thecfgmember is now$safeCfg = $canManage ? ($item['cfg'] ?? '') : ''. View-only users getcfg:''; managers keep the full raw XML (edit modal still prefills).serverUrland the parsedgithub.*fields are derived from cfg before this point and keep working without it. - All 5 call sites updated (list, detail, create-response, update-response, delete-response).
- Partially masking secret tags inside the XML — the XML is opaque and may hold arbitrary credential shapes; not masking everything guarantees another leak. Blanking the whole field for view-only matches legacy, which never showed it.
- Returning
cfgon list but redacting on detail — inconsistent; both routes use the same helper and the same principle.
-
php -l api/codetracker/index.php→ no syntax errors. - View-only
ctviewonly:GET /listHTTP 200, everyitems[].cfg="",github.token=********;GET /detailitem.cfg="". Network payload confirmed in the browser (request/response captured). - Admin: list + detail +
PUTresponse all keep the full cfg XML; edit modal opens with repository/branch prefilled, token input blank. - Browser screen renders for both roles; console clean (pre-existing a11y
notice only); Event Viewer
log_level=2 (ERROR/WARNING)= 0 for the valid-typed tracker flow. - Regression suite 1576 6/6 PASS appended to
tmp/TLU_Test_Cases.md.
Admin (management) code tracker screen after the fix — full management UI, tracker row renders normally:
![]()
View-only user (ctviewonly, right 52 only) code tracker screen after the fix — the list renders the row; the raw cfg payload is empty (cfg:"") with github.token masked as ******** (API network payload confirmed), so no credential ever reaches the viewer:
![]()
-
api/codetracker/index.php—$canManage+ cfg gating intrackerToJSON(+21/−7 lines vs the base3ebd8ebb9). -
tmp/TLU_Test_Cases.md—Regression — Issue #1576suite (6/6 PASS). -
CHANGELOG—[KEY BUGFIX - SECURITY] - #1576entry. - Wiki mirror page
Bugfix-Issue-1576-CodeTracker-Cfg-Token-Leak.md(with screenshots) + this docs mirror.
Issue #1576 fixed error-free and pushed (fix/issue-1576-cfg-token-leak,
rebased onto 3ebd8ebb9; commits ab77164c9 fix + dd78d6319 suite +
4d043e574 docs); regression suite 6/6 PASS; Event Viewer clean (the 16
pre-existing tlCodeTracker.class.php WARNING rows were generated by the
repro fixture with the unregistered type-6 tracker and already filed). A
related out-of-scope finding (BFF accepts unregistered tracker type →
E_WARNING noise) was filed separately as issue #1577.