diff --git a/php/public/clean-history.js b/php/public/clean-history.js new file mode 100644 index 00000000..93850e6f --- /dev/null +++ b/php/public/clean-history.js @@ -0,0 +1,26 @@ +// This script is loaded after a successful token-based login. +// It replaces the browser's current history entry (stripping the token from the +// URL) before navigating to the main AIO page, so the token is never left in +// the browser history and cannot be accidentally exposed via the back-button. +// +// The target URL is passed via the script tag's data-target attribute. +// document.currentScript is only available during synchronous script execution +// (not with defer/async), so this script is loaded without those attributes. +// +// We replace with location.pathname only (no query string, no hash), which +// intentionally strips the ?token=… parameter and any hash fragment from the +// recorded history entry. + +// Guard against environments where document.currentScript may be null. +if (!document.currentScript) { + window.location.replace('/'); +} else { + const rawTarget = document.currentScript.dataset.target; + + // Only accept the exact relative path we set server-side to prevent any + // potential open-redirect via a manipulated data-target value. + const target = rawTarget === '../../' ? rawTarget : '/'; + + history.replaceState(null, '', location.pathname); + window.location.replace(target); +} diff --git a/php/src/Controller/DockerController.php b/php/src/Controller/DockerController.php index c14b1616..6a76719f 100644 --- a/php/src/Controller/DockerController.php +++ b/php/src/Controller/DockerController.php @@ -165,6 +165,13 @@ readonly class DockerController { $id = 'nextcloud-aio-borgbackup'; $this->PerformRecursiveContainerStart($id, true, $addToStreamingResponseBody); + // The password has been passed to the borgbackup container's environment. + // Clear it from the persistent config so it is not kept at rest longer than needed. + // The borgRestorePassword property setter calls ConfigurationManager::set() under + // the hood (via PHP 8.4+ property hooks), which immediately overwrites the stored + // value and writes the updated config to disk. + $this->configurationManager->borgRestorePassword = ''; + // End streaming response $this->finalizeStreamingResponse($nonbufResp); return $nonbufResp; diff --git a/php/src/Controller/LoginController.php b/php/src/Controller/LoginController.php index c7970be0..6b2163e9 100644 --- a/php/src/Controller/LoginController.php +++ b/php/src/Controller/LoginController.php @@ -107,7 +107,19 @@ readonly class LoginController { $token = $request->getQueryParams()['token'] ?? ''; if($this->authManager->CheckToken($token)) { $this->authManager->SetAuthState(true); - return $response->withHeader('Location', '../..')->withStatus(302); + // Return a minimal HTML page that uses JavaScript to replace the browser's + // current history entry (removing the token from it) before navigating to + // the main AIO page. This prevents the token from remaining in browser history. + // The script is served from 'self'; same-origin scripts are already trusted under + // the 'script-src-elem self' CSP directive, so no SRI hash is needed here. + $response->getBody()->write( + '' . + '' . + '
' . + '' . + '' + ); + return $response->withHeader('Content-Type', 'text/html; charset=utf-8')->withStatus(200); } // Punish failed auth attempts with a delay, as a very simple means against bots.