From 7a1cf2b10733251d9b4a336a56c1f74ec4bd4472 Mon Sep 17 00:00:00 2001 From: sgiehl Date: Mon, 10 Aug 2026 15:14:32 +0200 Subject: [PATCH 1/2] Append injected params in a consistent position when rewriting bulk entries When a bulk entry already contained a cip or token_auth key, the proxy overwrote it in place, leaving the proxy's own value at whatever position the entry used instead of a consistent one - http_build_query() keeps an existing key's position when its value is replaced. Unset those keys before assigning the proxy's own so they are always appended last, matching the single-request path, which already unsets token_auth before assigning its own. This is a no-op for entries that carry neither key. --- proxy.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/proxy.php b/proxy.php index ceba137..9ff2f2f 100644 --- a/proxy.php +++ b/proxy.php @@ -500,6 +500,9 @@ function withProxyTracking( $tokenAuth, $includeProxyToken ) { + // Unset first so these are always appended last, not left at an existing key's position. + unset($params['cip'], $params['token_auth']); + // The entry is clean (no cip of its own), so set the real visitor IP. $params['cip'] = $visitIp; From 5dd4479bc526ddce5605b7d889f8c554cb37203e Mon Sep 17 00:00:00 2001 From: sgiehl Date: Mon, 10 Aug 2026 15:14:32 +0200 Subject: [PATCH 2/2] Add tests for injected param placement in bulk entries Cover both branches: the per-entry token path (the proxy's token is appended at the end of an entry that already carries a token_auth key) and the top-level token path (an entry's own token_auth key is dropped when no per-entry token is injected). --- tests/ProxyTest.php | 51 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/ProxyTest.php b/tests/ProxyTest.php index b66e91a..243977c 100644 --- a/tests/ProxyTest.php +++ b/tests/ProxyTest.php @@ -522,6 +522,57 @@ public function test_bulk_request_with_offending_entry_does_not_set_top_level_to $this->assertStringNotContainsString('"token_auth":""', $responseBody); } + public function test_bulk_request_injected_entry_token_is_appended_last_even_when_entry_supplies_token_auth_key() + { + // Clean entry already carries a token_auth key; the offending entry triggers per-entry injection. + $body = '{"requests":["?idsite=1&rec=1&action_name=off1&country=ru","?idsite=1&rec=1&token_auth=&action_name=clean&new_visit=1"]}'; + + $response = $this->send( + 'raw_input=1', + null, + null, + ['content-type' => 'application/x-www-form-urlencoded'], + null, + 'POST', + $body + ); + + $responseBody = $this->getBody($response); + + $this->assertEquals(200, $response->getStatusCode()); + + // The injected token is appended at the end of the entry (immediately before the JSON string + // terminator), not at the position the entry's own token_auth key happened to occupy. + $this->assertStringContainsString('token_auth="', $responseBody); + $this->assertStringNotContainsString('token_auth=&', $responseBody); + } + + public function test_bulk_request_drops_entry_token_auth_key_when_no_per_entry_token_is_injected() + { + // Fully clean batch: the proxy authorizes it with a single top-level token, so entries get no + // per-entry token. A token_auth key already present in an entry is dropped, not left in place. + $body = '{"requests":["?idsite=1&rec=1&token_auth=&action_name=clean&new_visit=1"]}'; + + $response = $this->send( + 'raw_input=1', + null, + null, + ['content-type' => 'application/x-www-form-urlencoded'], + null, + 'POST', + $body + ); + + $responseBody = $this->getBody($response); + + $this->assertEquals(200, $response->getStatusCode()); + // The batch is authorized once at the top level (JSON form) and the entry gets cip only. + $this->assertStringContainsString('"token_auth":""', $responseBody); + $this->assertStringContainsString('action_name=clean&new_visit=1&cip=', $responseBody); + // The entry's own token_auth key is dropped: no key=value token_auth survives in any entry. + $this->assertStringNotContainsString('token_auth=', $responseBody); + } + public function test_bulk_request_leaves_offending_object_entry_untouched() { $body = '{"requests":[{"idsite":"1","rec":"1","action_name":"clean"},{"idsite":"1","cip":"6.6.6.6"}]}';