From 33bc839eea365a0e340ecbb3115cc7d3e96be0ed Mon Sep 17 00:00:00 2001 From: Ankur Date: Tue, 12 May 2026 12:38:20 +0530 Subject: [PATCH 1/2] fix(CBP-44874): Fix: Signature verification error with CBP/Unify API keys --- .../Configuration/ConfigurationParser.php | 17 +++-- src/Rox/Server/Rox.php | 3 - .../ConfigurationParserTests.php | 66 ++++++++++++++++++- 3 files changed, 76 insertions(+), 10 deletions(-) diff --git a/src/Rox/Core/Configuration/ConfigurationParser.php b/src/Rox/Core/Configuration/ConfigurationParser.php index 99af807..0ac12e2 100644 --- a/src/Rox/Core/Configuration/ConfigurationParser.php +++ b/src/Rox/Core/Configuration/ConfigurationParser.php @@ -74,9 +74,15 @@ public function __construct( * @param array $configData * @return bool */ - protected function isVerifiedSignature($configData) + protected function isVerifiedSignature($configData, $apiKey = null) { - if ($this->_roxOptions->isSignatureDisabled()) { + // Check if signature verification is explicitly disabled + if ($this->_roxOptions !== null && $this->_roxOptions->isSignatureDisabled()) { + return true; + } + + // Auto-disable signature verification for CBP/Unify keys (they don't send signature_v0) + if ($apiKey !== null && \Rox\Core\Utils\ApiKeyHelpers::isCBPApiKey($apiKey)) { return true; } @@ -107,16 +113,17 @@ public function parse(ConfigurationFetchResult $fetchResult, SdkSettingsInterfac { try { $json = $fetchResult->getParsedData(); + $apiKey = $sdkSettings !== null ? $sdkSettings->getApiKey() : null; - if (!$this->isVerifiedSignature($json)) { + if (!$this->isVerifiedSignature($json, $apiKey)) { $this->_configurationFetchedInvoker->invokeWithError(FetcherError::SignatureVerificationError); $this->_errorReporter->report( "Failed to validate signature", new Exception( sprintf( "Data : %s Signature : %s", - (string) $json["data"], - (string) $json["signature_v0"] + (string) ($json["data"] ?? ""), + (string) ($json["signature_v0"] ?? "") ) ) ); diff --git a/src/Rox/Server/Rox.php b/src/Rox/Server/Rox.php index eefc447..740f8af 100644 --- a/src/Rox/Server/Rox.php +++ b/src/Rox/Server/Rox.php @@ -93,9 +93,6 @@ public static function setup($apiKey, RoxOptions $roxOptions = null) try { if (!$roxOptions) { $roxOptionsBuilder = new RoxOptionsBuilder(); - if (ApiKeyHelpers::isCBPApiKey($apiKey)) { - $roxOptionsBuilder->setDisableSignatureVerification(true); - } $roxOptions = new RoxOptions($roxOptionsBuilder); } diff --git a/tests/Rox/Core/Configuration/ConfigurationParserTests.php b/tests/Rox/Core/Configuration/ConfigurationParserTests.php index 3e656de..449dfec 100644 --- a/tests/Rox/Core/Configuration/ConfigurationParserTests.php +++ b/tests/Rox/Core/Configuration/ConfigurationParserTests.php @@ -99,7 +99,7 @@ public function testWillReturnNullWhenUnexpectedException() "nodata":"{\"application\":\"12345\",\"targetGroups\":[{\"condition\":\"eq(true,true)\",\"_id\":\"12345\"},{\"_id\":\"123456\",\"condition\":\"eq(true,true)\"}],\"experiments\":[{\"deploymentConfiguration\":{\"condition\":\"ifThen(and(true, true)\"},\"featureFlags\":[{\"name\":\"FeatureFlags.isFeatureFlagsEnabled\"}],\"archived\":false,\"name\":\"Feature Flags Drawer Item\",\"_id\":\"1\"},{\"deploymentConfiguration\":{\"condition\":\"ifThen(and(true, true)\"},\"featureFlags\":[{\"name\":\"Invitations.isInvitationsEnabled\"}],\"archived\":false,\"name\":\"Enable Modern Invitations\",\"_id\":\"2\"}] } ", "signature_v0":"K/bEQCkRXa6+uFr5H2jCRCaVgmtsTwbgfrFGVJ9NebfMH8CgOhCDIvF4TM1Vyyl0bGS9a4r4Qgi/g63NDBWk0ZbRrKAUkVG56V3/bI2GDHxFvRNrNbiPmFv/wmLLuwgh1mdzU0EwLG4M7yXoNXtMr6Jli8t4xfBOaWW1g0QpASkiWa7kdTamVip/1QygyUuhX5hOyUMpy4Ny9Hi/QPvVBn6GDMxQtxpLfTavU9cBly2D7Ex8Z7sUUOKeoEJcdsoF1QzH14XvA2HQSICESz7D/uld0PNdG0tMj9NlAZfki8eY2KuUe/53Z0Og5WrqQUxiAdPuJoZr6+kSqlASZrrkYw==", "signed_date":"2018-01-09T19:02:00.720Z" -} +} EOT; $configFetchResult = new ConfigurationFetchResult(json_decode($json, true), ConfigurationSource::CDN); @@ -110,7 +110,9 @@ public function testWillReturnNullWhenUnexpectedException() $this->assertNull($conf); $this->assertNotNull($this->_cfiEvent); - $this->assertEquals(FetcherError::Unknown, $this->_cfiEvent->getErrorDetails()); + // Fixed: Now properly returns SignatureVerificationError when data field is missing + // instead of letting it throw an exception that gets caught as Unknown + $this->assertEquals(FetcherError::SignatureVerificationError, $this->_cfiEvent->getErrorDetails()); } public function testWillReturnNullWhenWrongSignature() @@ -202,4 +204,64 @@ public function testWillParseExperimentsAndTargetGroups() $this->assertNull($this->_cfiEvent); } + + public function testWillReturnNullWhenSignatureMissingAndVerificationNotDisabled() + { + $json = <<_mockedOptions->shouldReceive('isSignatureDisabled') + ->andReturn(false); + + $configFetchResult = new ConfigurationFetchResult(json_decode($json, true), ConfigurationSource::API); + + $cp = new ConfigurationParser($this->_sf, $this->_kf, $this->_errRe, $this->_cfi, $this->_mockedOptions); + $this->assertNull($cp->parse($configFetchResult, $this->_sdk)); + $this->assertNotNull($this->_cfiEvent); + $this->assertEquals(FetcherError::SignatureVerificationError, $this->_cfiEvent->getErrorDetails()); + } + + public function testWillParseSuccessfullyWhenSignatureMissingButVerificationDisabled() + { + $json = <<_mockedOptions->shouldReceive('isSignatureDisabled') + ->andReturn(true); + + $configFetchResult = new ConfigurationFetchResult(json_decode($json, true), ConfigurationSource::API); + + $cp = new ConfigurationParser($this->_sf, $this->_kf, $this->_errRe, $this->_cfi, $this->_mockedOptions); + $conf = $cp->parse($configFetchResult, $this->_sdk); + + $this->assertNotNull($conf); + $this->assertEquals(count($conf->getTargetGroups()), 0); + $this->assertEquals(count($conf->getExperiments()), 0); + } + + public function testWillHandleNullRoxOptionsGracefully() + { + $json = <<_sf, $this->_kf, $this->_errRe, $this->_cfi, null); + + // Should return null because signature is missing and we can't check if verification is disabled + $this->assertNull($cp->parse($configFetchResult, $this->_sdk)); + } } From ec16e3076869db974cf4dd184054d1742eafe369 Mon Sep 17 00:00:00 2001 From: Ankur Date: Tue, 12 May 2026 13:01:15 +0530 Subject: [PATCH 2/2] chore: Upgrade CI to use Composer 2 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dd20bd5..1c276a6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: uses: shivammathur/setup-php@master with: php-version: ${{ matrix.php-versions }} - tools: composer:v1 + tools: composer:v2 - name: Check PHP Version run: php -v