From 66c19a6f9e0a6f83f476f75035666c4bd53a7f86 Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Thu, 10 Sep 2026 16:45:10 -0400 Subject: [PATCH] fix(passwordless): encode session IDs in send URLs Session IDs must not introduce path separators or reinterpret literal percent-encoded input when sending passwordless sessions. Apply the same encoding responsibility used by generated services without changing the shared HTTP client's behavior. --- lib/Passwordless.php | 2 +- tests/PasswordlessTest.php | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/Passwordless.php b/lib/Passwordless.php index 48dcee14..1a6a6bf1 100644 --- a/lib/Passwordless.php +++ b/lib/Passwordless.php @@ -55,7 +55,7 @@ public function sendSession(string $sessionId): void { $this->client->request( method: 'POST', - path: "passwordless/sessions/{$sessionId}/send", + path: 'passwordless/sessions/' . rawurlencode($sessionId) . '/send', body: [], ); } diff --git a/tests/PasswordlessTest.php b/tests/PasswordlessTest.php index b468efc9..1c374296 100644 --- a/tests/PasswordlessTest.php +++ b/tests/PasswordlessTest.php @@ -46,6 +46,34 @@ public function testSendSession(): void ); } + public function testSendSessionEncodesSessionIdAsSinglePathSegment(): void + { + $cases = [ + 'session/other' => 'session%2Fother', + '../other' => '..%2Fother', + 'session/../../other' => 'session%2F..%2F..%2Fother', + 'session?query=value#fragment' => 'session%3Fquery%3Dvalue%23fragment', + 'session%2Fother' => 'session%252Fother', + 'session with spaces' => 'session%20with%20spaces', + 'session+other' => 'session%2Bother', + ]; + + foreach ($cases as $sessionId => $encodedSessionId) { + $client = $this->createMockClient([['status' => 204]]); + $client->passwordless()->sendSession($sessionId); + $request = $this->getLastRequest(); + $this->assertSame('POST', $request->getMethod()); + $this->assertSame( + '/passwordless/sessions/' . $encodedSessionId . '/send', + $request->getUri()->getPath(), + $sessionId, + ); + $this->assertSame('', $request->getUri()->getQuery()); + $this->assertSame('', $request->getUri()->getFragment()); + $this->assertSame('{}', (string) $request->getBody()); + } + } + public function testPasswordlessAccessibleFromClient(): void { $client = $this->createMockClient([]);