Skip to content

Ghost Architect scan results for playwright-php (unsolicited, friendly) #173

Description

@EJWisner

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:

  1. Log any exception caught during $client->close() before discarding it.
  2. Re-throw critical exceptions if they indicate unrecoverable state rather than swallowing them entirely.
  3. 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:

  1. Refactor sanitizeString() to use a more permissive pattern before applying specific token logic, so all credential formats are captured.
  2. 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.
  3. 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

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions