Hi there,
I run Ghost Platform LLC, maker of Ghost Architect (TM), a code analysis tool. As part of testing it against real-world open source projects, I ran a scan against playwright-php/playwright (publicly available on GitHub, no access beyond a public clone was used). Below are the confirmed findings from that scan (this excludes a further set of findings our own verifier could not confirm against the source, so this list is the higher-confidence subset).
Ghost Architect (TM) Codebase Health Score: 10/100 (Critical risk, do not proceed without remediation)
Executive Summary
This pre-engagement analysis of the gh-playwright-php--playwright- codebase identified 7 findings: 2 critical-severity and 5 high-severity. A further 4 findings could not be confirmed against the source and are set aside, outside this count. The most severe risk involves a silent shutdown handler in Playwright.php that swallows exceptions during client factory initialization, effectively masking critical failures before they can be addressed by operators. A second critical issue exists within Sanitizer::sanitizeString() where overly specific regex patterns may fail to detect certain credential formats, leaving sensitive data exposed during transport operations. The remaining issues span high-severity gaps including missing timeouts on WebSocket waits and empty catch blocks that hide cleanup or coordination failures across the application's lifecycle.
Critical Findings
Silent shutdown handler swallowing exceptions in Playwright client factory
Files: src/Playwright.php
Severity: CRITICAL Effort: 2-3 hours Complexity: MEDIUM
The client() method registers a global shutdown function that calls $client->close(). This closure catches all \Throwables with an empty body, meaning any error during browser cleanup is silently discarded. If the close operation fails, operators lose visibility into failures and may not detect resource leaks or zombie processes until they cause production outages later.
Why this matters: Silent failure in shutdown handlers prevents detection of leaked resources like browser instances that remain running after a crash, leading to memory exhaustion on the host machine over time.
Fix:
- Log any exception caught during
$client->close() before discarding it.
- Re-throw critical exceptions if they indicate unrecoverable state rather than swallowing them entirely.
- Wrap cleanup logic in a try-catch that explicitly logs context about why cleanup failed.
Regex sanitization patterns in Sanitizer::sanitizeString() may fail to match all credential formats due to overly specific regexes
Files: src/Transport/Sanitizer.php
Severity: CRITICAL Effort: 4-6 hours Complexity: HIGH
The regular expressions in Sanitizer::sanitizeString() rely on restrictive character classes and fixed-length assumptions that allow non-standard credentials to bypass masking during logging. The current error handling also converts a false return from preg_replace into an uncaught RuntimeException, risking application crashes when processing legitimate but malformed log payloads.
Why this matters: This combination exposes sensitive API keys and tokens in logs if they use encoding schemes or lengths outside the hardcoded patterns, while introducing instability that could halt critical logging operations under edge cases.
Fix:
- Refactor
sanitizeString() to use a more permissive pattern before applying specific token logic, so all credential formats are captured.
- Replace the exception-throwing error handling for
preg_replace with graceful degradation (log and return the original string or a placeholder) instead of crashing the request.
- Add unit tests covering edge cases like long tokens, unusual encodings, and payloads designed to break the regex engine.
High-Severity Findings
Empty rollback in transport error handling - APIRequestContext fetch path
Files: src/API/APIRequestContext.php
Severity: HIGH Effort: 1-2 hours Complexity: LOW
APIRequestContext::fetch() assumes an API response always contains a 'response' key. Unexpected data structures can lead to cryptic errors or silent failures where downstream systems process incomplete data without operator awareness.
Fix: Validate the response structure before accessing keys, throw a descriptive exception on mismatch, and log validation failures.
Missing timeout on WebSocket event wait
Files: src/WebSocket/WebSocket.php, bin/lib/coordination.js
Severity: HIGH Effort: 2-3 hours Complexity: MEDIUM
WebSocket::waitForEvent defaults to a 30 second timeout that may be insufficient under high latency or load; coordination.js has similar hardcoded timeouts that can cause premature cleanup of active command sequences.
Fix: Increase default timeouts based on CI data, make them configurable per environment, and add retry with exponential backoff before declaring failure.
Silent failure in async callback handling
Files: bin/lib/coordination.js, src/WebSocket/WebSocketRoute.php
Severity: HIGH Effort: 2-3 hours Complexity: MEDIUM
AsyncCommandCoordinator::executeNextPhase logs exceptions before rethrowing without actionable context, and WebSocketRoute's handlers are invoked without validation or exception handling, leaving connection state inconsistent after an error.
Fix: Log full stack traces and phase/handler context, run cleanup in finally blocks regardless of success, and validate callback inputs before invoking them.
Empty catch block in cleanupBrowserResources during exit/shutdown
Files: bin/playwright-server.js
Severity: HIGH Effort: 1-2 hours Complexity: LOW
cleanupBrowserResources deletes pages without error handling; a locked or still-active context can fail silently, leaving orphaned resources.
Fix: Wrap page deletion in try-catch with logged context, add retry logic for stubborn resources, and monitor memory metrics for incomplete cleanup.
Empty catch block in waitForPopup coordination failure path
Files: bin/lib/handlers.js
Severity: HIGH Effort: 1-2 hours Complexity: LOW
waitForPopup only logs an error on coordinator failure and returns a null popupPageId with no indication whether the failure was transient or permanent, which can cause indefinite retries by callers.
Fix: Add metadata indicating transient vs. permanent failure, use exponential backoff for retryable errors, and document expected behavior on popup failure.
I am not trying to sell you anything here, I just thought you would want to know in case any of it is useful. Happy to hear if any of this is a false positive given you know the codebase far better than a scanner does.
Ernst Wisner
Ghost Platform LLC
Hi there,
I run Ghost Platform LLC, maker of Ghost Architect (TM), a code analysis tool. As part of testing it against real-world open source projects, I ran a scan against playwright-php/playwright (publicly available on GitHub, no access beyond a public clone was used). Below are the confirmed findings from that scan (this excludes a further set of findings our own verifier could not confirm against the source, so this list is the higher-confidence subset).
Ghost Architect (TM) Codebase Health Score: 10/100 (Critical risk, do not proceed without remediation)
Executive Summary
This pre-engagement analysis of the gh-playwright-php--playwright- codebase identified 7 findings: 2 critical-severity and 5 high-severity. A further 4 findings could not be confirmed against the source and are set aside, outside this count. The most severe risk involves a silent shutdown handler in Playwright.php that swallows exceptions during client factory initialization, effectively masking critical failures before they can be addressed by operators. A second critical issue exists within Sanitizer::sanitizeString() where overly specific regex patterns may fail to detect certain credential formats, leaving sensitive data exposed during transport operations. The remaining issues span high-severity gaps including missing timeouts on WebSocket waits and empty catch blocks that hide cleanup or coordination failures across the application's lifecycle.
Critical Findings
Silent shutdown handler swallowing exceptions in Playwright client factory
Files: src/Playwright.php
Severity: CRITICAL Effort: 2-3 hours Complexity: MEDIUM
The
client()method registers a global shutdown function that calls$client->close(). This closure catches all\Throwables with an empty body, meaning any error during browser cleanup is silently discarded. If the close operation fails, operators lose visibility into failures and may not detect resource leaks or zombie processes until they cause production outages later.Why this matters: Silent failure in shutdown handlers prevents detection of leaked resources like browser instances that remain running after a crash, leading to memory exhaustion on the host machine over time.
Fix:
$client->close()before discarding it.Regex sanitization patterns in Sanitizer::sanitizeString() may fail to match all credential formats due to overly specific regexes
Files: src/Transport/Sanitizer.php
Severity: CRITICAL Effort: 4-6 hours Complexity: HIGH
The regular expressions in
Sanitizer::sanitizeString()rely on restrictive character classes and fixed-length assumptions that allow non-standard credentials to bypass masking during logging. The current error handling also converts a false return frompreg_replaceinto an uncaughtRuntimeException, risking application crashes when processing legitimate but malformed log payloads.Why this matters: This combination exposes sensitive API keys and tokens in logs if they use encoding schemes or lengths outside the hardcoded patterns, while introducing instability that could halt critical logging operations under edge cases.
Fix:
sanitizeString()to use a more permissive pattern before applying specific token logic, so all credential formats are captured.preg_replacewith graceful degradation (log and return the original string or a placeholder) instead of crashing the request.High-Severity Findings
Empty rollback in transport error handling - APIRequestContext fetch path
Files: src/API/APIRequestContext.php
Severity: HIGH Effort: 1-2 hours Complexity: LOW
APIRequestContext::fetch()assumes an API response always contains a 'response' key. Unexpected data structures can lead to cryptic errors or silent failures where downstream systems process incomplete data without operator awareness.Fix: Validate the response structure before accessing keys, throw a descriptive exception on mismatch, and log validation failures.
Missing timeout on WebSocket event wait
Files: src/WebSocket/WebSocket.php, bin/lib/coordination.js
Severity: HIGH Effort: 2-3 hours Complexity: MEDIUM
WebSocket::waitForEventdefaults to a 30 second timeout that may be insufficient under high latency or load;coordination.jshas similar hardcoded timeouts that can cause premature cleanup of active command sequences.Fix: Increase default timeouts based on CI data, make them configurable per environment, and add retry with exponential backoff before declaring failure.
Silent failure in async callback handling
Files: bin/lib/coordination.js, src/WebSocket/WebSocketRoute.php
Severity: HIGH Effort: 2-3 hours Complexity: MEDIUM
AsyncCommandCoordinator::executeNextPhaselogs exceptions before rethrowing without actionable context, and WebSocketRoute's handlers are invoked without validation or exception handling, leaving connection state inconsistent after an error.Fix: Log full stack traces and phase/handler context, run cleanup in finally blocks regardless of success, and validate callback inputs before invoking them.
Empty catch block in cleanupBrowserResources during exit/shutdown
Files: bin/playwright-server.js
Severity: HIGH Effort: 1-2 hours Complexity: LOW
cleanupBrowserResourcesdeletes pages without error handling; a locked or still-active context can fail silently, leaving orphaned resources.Fix: Wrap page deletion in try-catch with logged context, add retry logic for stubborn resources, and monitor memory metrics for incomplete cleanup.
Empty catch block in waitForPopup coordination failure path
Files: bin/lib/handlers.js
Severity: HIGH Effort: 1-2 hours Complexity: LOW
waitForPopuponly logs an error on coordinator failure and returns a null popupPageId with no indication whether the failure was transient or permanent, which can cause indefinite retries by callers.Fix: Add metadata indicating transient vs. permanent failure, use exponential backoff for retryable errors, and document expected behavior on popup failure.
I am not trying to sell you anything here, I just thought you would want to know in case any of it is useful. Happy to hear if any of this is a false positive given you know the codebase far better than a scanner does.
Ernst Wisner
Ghost Platform LLC