security: FTP password not masked in AjaxController maskSecrets/mergeExistingSecrets #169
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
AjaxController::maskSecrets()andAjaxController::mergeExistingSecrets()define secret field mappings forsftp,s3, andgoogle_driveremote types — butftpis completely missing. When a remote destination of typeftpis loaded via the AJAXlistRemotesendpoint, the FTP password is returned in cleartext to the browser.Location
src/Controller/AjaxController.php— lines 1142-1146 and 1164-1168:What to do
'ftp' => ['password']to the$secretsarray inmaskSecrets()'ftp' => ['password']to the$secretsarray inmergeExistingSecrets()profiles()endpoint also masks FTP credentials (it does via a separate$sensitiveFieldslist that includesftp_password)Why
This is a credential exposure vulnerability. Any admin user who can view the remotes list sees FTP passwords in the DOM/network response. The other three remote types correctly mask their secrets.
Branch created:
feature/169-security-ftp-password-not-masked-in-ajaxConfirmed against 02.56.05 — keep open (canonical FTP-masking issue; #185 closed as duplicate).
AjaxController.php:1203-1207(maskSecrets()) and:1225-1229(mergeExistingSecrets()) both build a$secretsmap with onlysftp/s3/google_drive— noftpkey. FTP was removed from the create UI (tmpl/profile/edit.php:139-142offers only those three), butRemoteTable.php:33still lists'ftp'in$validTypes, so an FTP remote can still be stored (legacy data or a crafted POST). When such a row is loaded vialistRemotes→maskSecrets($config, $row->type)it falls through to$secrets['ftp'] ?? []= empty, and the FTP password is returned unmasked.Fix: add
'ftp' => ['password']to both$secretsarrays. (Separately consider dropping'ftp'fromRemoteTable::$validTypesto close it at the data layer.)