Skip to content

Harden PdoStorageService: input validation, security, and reliability#19

Open
dasgarner wants to merge 2 commits intomasterfrom
claude/security-review-sanitizer-9yzhA
Open

Harden PdoStorageService: input validation, security, and reliability#19
dasgarner wants to merge 2 commits intomasterfrom
claude/security-review-sanitizer-9yzhA

Conversation

@dasgarner
Copy link
Copy Markdown
Member

Summary

This PR significantly hardens the PdoStorageService database wrapper with input validation, improved security, enhanced error handling, and better logging practices. The changes address SQL injection vectors, improve connection resilience, and provide better observability without leaking secrets.

Key Changes

Security & Input Validation

  • DSN parameter injection prevention: Added strict regex validation for database host, port, and name in createDsn() to prevent malicious DSN parameter injection
  • Time zone injection prevention: Added validation for setTimeZone() to accept only numeric offsets (e.g., -08:00) or IANA-style names (e.g., Europe/London), rejecting arbitrary input
  • Prepared statement hardening: Disabled emulated prepares (PDO::ATTR_EMULATE_PREPARES = false) to ensure escaping is performed by the server using the connection charset, closing a class of SQL injection vulnerabilities
  • Charset in DSN: Declared utf8mb4 charset in the DSN to avoid SET NAMES/emulated-prepares charset desync issues

Logging & Observability

  • Parameter redaction: Refactored logSql() to never log raw parameter values; logs the SQL with placeholders intact and parameter keys in context only
  • SQL syntax error logging: Added error-level logging for MySQL error 1064 (syntax errors) to surface programmer errors
  • Improved log context: Changed from string interpolation to structured logging with context arrays

Error Handling & Resilience

  • ErrorException handling: Added catch blocks for \ErrorException (e.g., "Error while sending QUERY packet") in all query methods with reconnect support
  • Consistent error handling: Unified error code extraction using null coalescing operator across all methods
  • Better exception messages: Improved DeadLockException message formatting

Connection Management

  • SSL/TLS support: Added optional ssl and sslVerify parameters to connect() for certificate-based authentication
  • Connection pooling improvements: Refactored setConnection() to use new openConnection() helper method
  • Close flag: Added $close parameter to all query methods (select, insert, update, exists, isolated, commitIfNecessary) to allow closing connections after operations
  • Transaction control: Added $transaction parameter to insert(), update(), and updateWithDeadlockLoop() to allow skipping automatic transaction management

Code Quality

  • Type hints: Improved PHPDoc type hints (e.g., \PDO[], string|null)
  • Null coalescing: Replaced ternary checks with ?? operator throughout
  • Consistent defaults: Changed default connection parameter from null to 'default' in method signatures for clarity
  • Refactored DSN building: Cleaner logic for host/port parsing and DSN construction
  • Removed redundant code: Eliminated duplicate connection logic by extracting openConnection() method

Testing

  • Added comprehensive test coverage for:
    • Time zone validation (numeric offsets, IANA names, injection payloads)
    • ErrorException reconnect paths across all query methods
    • Transaction flag behavior
    • Close flag behavior
    • SQL syntax error logging
    • Parameter value redaction in logs

Notable Implementation Details

  • The connect() method signature now includes optional $ssl and $sslVerify parameters for TLS support
  • All query methods now support a $close flag to discard the connection after use, useful for connection cleanup in certain scenarios
  • The $transaction flag allows callers to opt out of automatic transaction management when needed
  • Validation is performed before any database operations, failing fast on invalid input
  • The logging refactor ensures secrets/PII never leak into log files while maintaining debuggability

https://claude.ai/code/session_01QUqTHWNiX5nK6w1A5e1KYq

claude added 2 commits April 27, 2026 20:02
Adopts the evolved version from xibosignage/xibo-cms (lib/Storage/PdoStorageService.php),
relicensed AGPL-3.0+ to MIT by the rights holder, and layers on the security
hardening identified in the recent review.

Improvements ported from the CMS:
- TLS/SSL connection support via PDO::MYSQL_ATTR_SSL_CA / SSL_VERIFY_SERVER_CERT,
  configurable via new optional 'ssl' and 'sslVerify' config keys.
- ErrorException reconnect path catches "Error while sending QUERY packet."
- Per-call \$close and \$transaction flags on every operation.
- SQL syntax errors (1064) are now logged at error level.
- utf8mb4 charset.

Security hardening on top:
- Charset is declared in the DSN (charset=utf8mb4) instead of a post-connect
  SET NAMES, and PDO::ATTR_EMULATE_PREPARES is disabled. Closes the emulated-
  prepares charset-desync class of SQL injection.
- setTimeZone validates the value against a strict allow-list (numeric offsets
  or IANA-style names) before interpolation. MySQL does not allow placeholders
  in SET time_zone, so the guard is the only injection control available.
- DSN host/port/name are validated before concatenation, closing DSN parameter
  injection via misconfigured config.
- logSql now redacts bound parameter values: the SQL is logged with placeholders
  intact and a separate context array contains only parameter keys, never values.

Deliberate divergences from CMS:
- Connection state stays instance-level (\$this->conn), not static. Preserves
  per-instance test isolation and supports callers building multiple services.
- isolated() is retained (CMS dropped it); covered by tests, part of the contract.
- Generic PSR-3 LoggerInterface, not the CMS LogService::sql() helper.
- Constructor-injected config, not the CMS ConfigService global.

Interface change (BREAKING):
- StorageServiceInterface methods gain \$close (and \$transaction where applicable).
  Downstream packages that implement StorageServiceInterface must add the new
  optional parameters to their implementations. This warrants a major version bump.

Tests:
- 21 new tests cover setTimeZone validation (10 malicious payloads), ErrorException
  reconnect on every operation, \$transaction=false skip, \$close=true release,
  1064 error-level logging, and parameter-value redaction.
- Existing 38 database tests continue to pass.
@dasgarner dasgarner self-assigned this Apr 27, 2026
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.

2 participants