diff --git a/src/Discord/Http.php b/src/Discord/Http.php index eec7143..610cd31 100644 --- a/src/Discord/Http.php +++ b/src/Discord/Http.php @@ -139,8 +139,13 @@ class Http implements HttpInterface * @param LoopInterface $loop * @param DriverInterface|null $driver */ - public function __construct(string $token, LoopInterface $loop, LoggerInterface $logger, ?DriverInterface $driver = null) - { + public function __construct( + #[\SensitiveParameter] + string $token, + LoopInterface $loop, + LoggerInterface $logger, + ?DriverInterface $driver = null + ) { $this->token = $token; $this->loop = $loop; $this->logger = $logger; @@ -150,4 +155,18 @@ public function __construct(string $token, LoopInterface $loop, LoggerInterface $this->promiseV3 = str_starts_with(InstalledVersions::getVersion('react/promise'), '3.'); } + + /** + * Debug representation with the token masked, so var_dump() and Xdebug + * stack traces never print it. + * + * @return array + */ + public function __debugInfo(): array + { + $vars = get_object_vars($this); + $vars['token'] = '*****'; + + return $vars; + } } diff --git a/tests/Discord/HttpTest.php b/tests/Discord/HttpTest.php index 9efe9f4..cac564e 100644 --- a/tests/Discord/HttpTest.php +++ b/tests/Discord/HttpTest.php @@ -35,6 +35,25 @@ public function testTheTokenIsSentAsAuthorization() $this->assertSame('Bot abc', $driver->requests[0]->getHeaders()['Authorization']); } + public function testTheTokenIsMaskedInDebugOutput() + { + [$http] = $this->http('Bot secret-token-value'); + + $this->assertSame('*****', $http->__debugInfo()['token']); + $this->assertStringNotContainsString('secret-token-value', print_r($http, true)); + } + + /** + * @requires PHP >= 8.2 + */ + public function testTheTokenParameterIsRedactedFromStackTraces() + { + $parameter = (new \ReflectionMethod(Http::class, '__construct'))->getParameters()[0]; + + $this->assertSame('token', $parameter->getName()); + $this->assertNotEmpty($parameter->getAttributes(\SensitiveParameter::class)); + } + public function testAClientWithoutATokenSendsNoAuthorization() { // For routes that take the application's credentials in the body.