Skip to content

Fix security issues and PHP 8.4 compatibility#139

Open
aaapl wants to merge 3 commits intosmsapi:masterfrom
aaapl:fix/security-and-php84-compat
Open

Fix security issues and PHP 8.4 compatibility#139
aaapl wants to merge 3 commits intosmsapi:masterfrom
aaapl:fix/security-and-php84-compat

Conversation

@aaapl
Copy link
Copy Markdown

@aaapl aaapl commented Mar 18, 2026

Summary

  • LoggerDecorator: sanitize Authorization / Proxy-Authorization headers in logs — tokens are now masked as xxxx... (len = N). Fix stream handling: seekable streams are rewound after read, non-seekable streams are not touched (placeholder logged instead)
  • cURL HttpClient: explicitly enforce SSL verification (CURLOPT_SSL_VERIFYPEER=true, CURLOPT_SSL_VERIFYHOST=2)
  • Implicit nullable types: remove bool $param = null pattern (deprecated in PHP 8.4) across 11 occurrences in 8 files, replacing with untyped $param = null for backward compatibility with PHP 7.0+

Motivation

  • Token leakage in logs is a security risk (especially in shared logging infrastructure)
  • Original getContents() consumed the stream without rewinding — broke request/response body for downstream code
  • Unconditional rewind() would crash on non-seekable PSR-7 streams
  • PHP 8.4 emits E_DEPRECATED for every call to methods with implicit nullable types

Tests

Added LoggerDecoratorTest (7 tests, 17 assertions) covering:

  • Authorization header sanitization
  • Proxy-Authorization header sanitization
  • Regular headers not affected
  • Seekable request/response body preserved after logging
  • Non-seekable request/response body not consumed by logging

Test plan

  • New LoggerDecoratorTest passes (7/7)
  • Run full existing test suite
  • Verify on PHP 7.4, 8.0, 8.1, 8.2, 8.3, 8.4
  • Confirm no E_DEPRECATED notices on PHP 8.4

…rator

- Mask Authorization and Proxy-Authorization headers in logs,
  showing only "xxxx... (len = N)" instead of the actual token value
- Use StreamInterface type hint on readBody() for proper contracts
- For seekable streams: read body via (string) cast, then rewind()
  so downstream code receives an intact stream
- For non-seekable streams: log a placeholder instead of consuming
  or crashing on rewind() — prevents logging from breaking requests
- Add LoggerDecoratorTest with 7 tests covering: header sanitization,
  seekable body preservation, and non-seekable stream safety
Set CURLOPT_SSL_VERIFYPEER=true and CURLOPT_SSL_VERIFYHOST=2
to ensure SSL verification is always active regardless of
system-level php.ini or cURL configuration.
Replace `bool $param = null` with `$param = null` (untyped) to avoid
E_DEPRECATED in PHP 8.4 while maintaining backward compatibility
with PHP 7.0+ as declared in composer.json.

Type information preserved in @param docblocks for IDE/static analysis.
@maciejlew maciejlew self-requested a review March 27, 2026 10:52
@maciejlew
Copy link
Copy Markdown
Collaborator

It would be best to divide it into 3 independent parts, as divided in summary:

  • request log issue
  • ssl issue
  • php 8.4 deprecations issue.

Copy link
Copy Markdown
Collaborator

@maciejlew maciejlew left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from the login part, everything else is acceptable.

$this->assertArrayHasKey('headers', $requestLog);

$authHeader = $requestLog['headers']['Authorization'][0];
$this->assertStringNotContainsString('my-secret-token', $authHeader);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge/rebase master and run PHP 7.0 tests:

make test-suite-php-7.0 SUITE=unit

you will get:

Error: Call to undefined method Smsapi\Client\Tests\Unit\Infrastructure\HttpClient\Decorator\LoggerDecoratorTest::assertStringNotContainsString()

/app/tests/Unit/Infrastructure/HttpClient/Decorator/LoggerDecoratorTest.php:40
phpvfscomposer:///app/vendor/phpunit/phpunit/phpunit:52

{
private $logs = [];

public function log($level, $message, array $context = []): void
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge/rebase master and run PHP 7.0 tests:

make test-suite-php-7.0 SUITE=unit

you will get

Smsapi\Client\Tests\Unit\Infrastructure\HttpClient\Decorator\LoggerDecoratorTest::it_sanitizes_authorization_header
TypeError: Return value of Smsapi\Client\Tests\Unit\Infrastructure\HttpClient\Decorator\SpyLogger::log() must be an instance of Smsapi\Client\Tests\Unit\Infrastructure\HttpClient\Decorator\void, none returned

/app/tests/Unit/Infrastructure/HttpClient/Decorator/LoggerDecoratorTest.php:174
/app/vendor/psr/log/Psr/Log/AbstractLogger.php:113
/app/src/Infrastructure/HttpClient/Decorator/LoggerDecorator.php:33
/app/tests/Unit/Infrastructure/HttpClient/Decorator/LoggerDecoratorTest.php:34
phpvfscomposer:///app/vendor/phpunit/phpunit/phpunit:52

'uri' => $request->getUri(),
'headers' => $request->getHeaders(),
'body' => $request->getBody()->getContents(),
'headers' => $this->sanitizeHeaders($request->getHeaders()),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default logger is NullLogger. If you're injecting your own logger and have trouble with the logged data, your logger is the place to process it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Detach all that logging related stuff from this PR. All the rest is acceptable.

* @param bool|null $checkIdx
*/
public function setExternalId(string $idx, bool $checkIdx = null): self
public function setExternalId(string $idx, $checkIdx = null): self
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This solution is not 100% backward compatible. If someone extended these classes and methods, they would receive a warning for PHP < 8.0:

Warning: Declaration of Smsapi\Client\Tests\Unit\Feature\Sms\Bag\ExtendedSendSmssBag::setExternalId(array $idx, ?bool $checkIdx = NULL): Smsapi\Client\Feature\Sms\Bag\SendSmssBag should be compatible with Smsapi\Client\Feature\Sms\Bag\SendSmssBag::setExternalId(array $idx, $checkIdx = NULL): Smsapi\Client\Feature\Sms\Bag\SendSmssBag in /app/tests/Unit/Feature/Sms/Bag/SendSmssBagTest.php on line 23

and a fatal error for PHP >= 8.0.

Fatal error: Declaration of Smsapi\Client\Tests\Unit\Feature\Sms\Bag\ExtendedSendSmssBag::setExternalId(array $idx, ?bool $checkIdx = null): Smsapi\Client\Feature\Sms\Bag\SendSmssBag must be compatible with Smsapi\Client\Feature\Sms\Bag\SendSmssBag::setExternalId(array $idx, $checkIdx = null): Smsapi\Client\Feature\Sms\Bag\SendSmssBag in /app/tests/Unit/Feature/Sms/Bag/SendSmssBagTest.php on line 23

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, there is probably no better solution - I'd merge it and release it in the next version of the lib.

{
curl_setopt($httpClient, CURLOPT_HEADER, true);
curl_setopt($httpClient, CURLOPT_RETURNTRANSFER, true);
curl_setopt($httpClient, CURLOPT_SSL_VERIFYPEER, true);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extract a prepareConnectionOptions method for these options.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants