npm / playwright-core / 1.64.1-beta-1792067398000

playwright-core 1.64.1-beta-1792067398000

Source
https://github.com/microsoft/playwright
Commit scanned
0915b2e91289 (recorded by npm)
Directory
packages/playwright-core (only this part of the repository was scanned)
Compared with
cfe490d941f9 (0 signals introduced since)
Files
370 scanned, 49 skipped (binary, oversized, or unrecognised)
Signals
2 critical · 0 warn · 24 info
Scan time
16.0 s
LLM review
Reviewed by claude-haiku-5-5 (Anthropic): 17 of 17 warn and critical signals; 15 demoted to info.
Disclude version
4.0.0
Scanned (UTC)
2026-10-16 23:26

Signals

Ordered by severity, then signals introduced in this release. Hidden characters are shown as ⟨U+…⟩.

  1. `eval` called with a non-literal expression (ast pass, confidence 0.90)

            evalResult = await locator.locator.evaluate(async (element, expr) => {
              const value = eval(`(${expr})`);
              const isFunction = typeof value === 'function';
              const result = await (isFunction ? value(element) : value);
    suspicious LLM, confidence 0.70 Evaluates a caller-provided JavaScript expression in the page context.
    Reasoning

    eval of a caller-supplied expression inside a page evaluate context. This is a deliberate feature of an MCP browser tool that runs model/user-provided JavaScript in the browser, so it is dynamic execution by design rather than hidden malware. The risk is real for an agent-driven tool, but it is not obfuscation.

  2. `eval` called with a non-literal expression (ast pass, confidence 0.90)

            evalResult = await tab.page.evaluate(async expr => {
              const value = eval(`(${expr})`);
              const isFunction = typeof value === 'function';
              const result = await (isFunction ? value() : value);
    suspicious LLM, confidence 0.70 Evaluates a caller-provided JavaScript expression in the page for the evaluate tool.
    Reasoning

    Same by-design eval of user-supplied expression in tab.page.evaluate for the browser-automation evaluate tool. Expected functionality, though it is a code-execution surface worth reviewing.

  3. info warn → info by review obfuscated-command-name packages/playwright-core/bin/reinstall_chrome_beta_mac.sh:13:1

    command name contains 4 backslash-escape sequences — hiding the true command (ast pass, confidence 0.85)

    
    /Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta --version
    likely benign LLM, confidence 0.85 Invokes Chrome Beta binary via a path with escaped spaces.
    Reasoning

    Backslash-escaped spaces in a macOS application path are the standard way to write paths with spaces in bash; the command is simply invoking the Chrome Beta binary with --version. Not obfuscation.

  4. info warn → info by review obfuscated-command-name packages/playwright-core/bin/reinstall_msedge_beta_mac.sh:11:1

    command name contains 4 backslash-escape sequences — hiding the true command (ast pass, confidence 0.85)

    rm -rf /tmp/msedge_beta.pkg
    /Applications/Microsoft\ Edge\ Beta.app/Contents/MacOS/Microsoft\ Edge\ Beta --version
    likely benign LLM, confidence 0.85 Runs Edge Beta --version using an escaped-space app path.
    Reasoning

    Escaped spaces in the Microsoft Edge Beta app path are ordinary bash quoting for a path containing spaces. The script removes a temp pkg and checks the version; no hidden command.

  5. info warn → info by review obfuscated-command-name packages/playwright-core/bin/reinstall_msedge_dev_mac.sh:11:1

    command name contains 4 backslash-escape sequences — hiding the true command (ast pass, confidence 0.85)

    rm -rf /tmp/msedge_dev.pkg
    /Applications/Microsoft\ Edge\ Dev.app/Contents/MacOS/Microsoft\ Edge\ Dev --version
    likely benign LLM, confidence 0.85 Runs Edge Dev --version using an escaped-space app path.
    Reasoning

    Same pattern as the other browser reinstall scripts: escaped spaces in an app bundle path for Microsoft Edge Dev. Legitimate quoting, not concealment.

  6. info warn → info by review dynamic-import packages/playwright-core/src/cli/installActions.ts:49:25

    `require` called with a non-literal specifier (ast pass, confidence 0.60)

          continue;
        const packageJSON = require(path.join(browser.referenceDir, 'package.json'));
        const version = packageJSON.version;
        dirToVersion.set(browser.referenceDir, version);
    likely benign LLM, confidence 0.80 Reads a browser's package.json version via require on a joined path.
    Reasoning

    Requiring package.json from a computed browser reference directory is a normal way to read a version. The path is internal to the tool, not attacker-controlled input in this snippet.

  7. line length 614 characters (raw pass, confidence 0.50)

    import type { Binary, Channel, AndroidWebView, AndroidSelector, AndroidElementInfo, APIResponse, Metadata, ClientSideCallMetadata, SDKLanguage, WaitInfo, HttpCredentials, SetNetworkCookie, NetworkCookie, ResourceTiming, SecurityDetails, RequestSizes, RemoteAddr, ExpectedTextValue, SelectorEngine, FormField, LifecycleEv
  8. line length 614 characters (raw pass, confidence 0.50)

    export type { Binary, Channel, AndroidWebView, AndroidSelector, AndroidElementInfo, APIResponse, Metadata, ClientSideCallMetadata, SDKLanguage, WaitInfo, HttpCredentials, SetNetworkCookie, NetworkCookie, ResourceTiming, SecurityDetails, RequestSizes, RemoteAddr, ExpectedTextValue, SelectorEngine, FormField, LifecycleEv
  9. info warn → info by review dynamic-import packages/playwright-core/src/package.ts:22:28

    `require` called with a non-literal specifier (ast pass, confidence 0.60)

    export const packageRoot = path.join(__dirname, '..');
    export const packageJSON = require(path.join(packageRoot, 'package.json'));
    export const binPath = path.join(packageRoot, 'bin');
    likely benign LLM, confidence 0.85 Loads the project's own package.json relative to package root.
    Reasoning

    Loads the package's own package.json from a root computed from __dirname. Standard packaging code with no external input.

  10. command name is a variable (`$cygwin`) that this file sets only to plain text (ast pass, confidence 0.40)

    # Increase the maximum file descriptors if we can.
    if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
        case $MAX_FD in #(
          max*)
  11. command name is a variable (`$darwin`) that this file sets only to plain text (ast pass, confidence 0.40)

    # Increase the maximum file descriptors if we can.
    if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
        case $MAX_FD in #(
          max*)
  12. command name is a variable (`$nonstop`) that this file sets only to plain text (ast pass, confidence 0.40)

    # Increase the maximum file descriptors if we can.
    if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
        case $MAX_FD in #(
          max*)
  13. command name is a variable (`$cygwin`) that this file sets only to plain text (ast pass, confidence 0.40)

    # For Cygwin or MSYS, switch paths to Windows format before running java
    if "$cygwin" || "$msys" ; then
        APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
        CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
  14. command name is a variable (`$msys`) that this file sets only to plain text (ast pass, confidence 0.40)

    # For Cygwin or MSYS, switch paths to Windows format before running java
    if "$cygwin" || "$msys" ; then
        APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
        CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
  15. info critical → info by review dynamic-execution packages/playwright-core/src/server/android/driver/gradlew:233:1

    `eval` called on a dynamic (variable/substitution) expression (ast pass, confidence 0.90)

    
    eval "set -- $(
            printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
            xargs -n1 |
    likely benign LLM, confidence 0.85 Gradle wrapper eval used to split JVM options into positional args.
    Reasoning

    This is the standard Gradle wrapper script idiom: eval of set -- with xargs-split JVM options is how the official gradlew parses DEFAULT_JVM_OPTS. Flagged by the AST pass, but it is the well-known upstream template.

  16. info critical → info by review dynamic-execution packages/playwright-core/src/server/android/driver/gradlew:240:1

    `exec` called with a dynamic (variable/substitution) path (ast pass, confidence 0.85)

    
    exec "$JAVACMD" "$@"
    likely benign LLM, confidence 0.85 Gradle wrapper exec of the resolved Java command.
    Reasoning

    exec of $JAVACMD with positional args is the standard final line of the Gradle wrapper, launching the resolved Java binary. Expected behavior for this script.

  17. line length 614 characters (raw pass, confidence 0.50)

    import type { Binary, Channel, AndroidWebView, AndroidSelector, AndroidElementInfo, APIResponse, Metadata, ClientSideCallMetadata, SDKLanguage, WaitInfo, HttpCredentials, SetNetworkCookie, NetworkCookie, ResourceTiming, SecurityDetails, RequestSizes, RemoteAddr, ExpectedTextValue, SelectorEngine, FormField, LifecycleEv
  18. line length 614 characters (raw pass, confidence 0.50)

    export type { Binary, Channel, AndroidWebView, AndroidSelector, AndroidElementInfo, APIResponse, Metadata, ClientSideCallMetadata, SDKLanguage, WaitInfo, HttpCredentials, SetNetworkCookie, NetworkCookie, ResourceTiming, SecurityDetails, RequestSizes, RemoteAddr, ExpectedTextValue, SelectorEngine, FormField, LifecycleEv
  19. info warn → info by review dynamic-execution packages/playwright-core/src/server/dom.ts:709:43

    `atob` decodes a base64 value at runtime — common step before dynamic fetch or eval (ast pass, confidence 0.75)

              for (const p of payloads) {
                const bytes = Uint8Array.from(atob(p.buffer), c => c.charCodeAt(0));
                const file = new File([bytes], p.name, { type: p.mimeType, lastModified: p.lastModifiedMs });
                dt.items.add(file);
    likely benign LLM, confidence 0.80 Decodes base64 payloads into File bytes for simulating file drops.
    Reasoning

    atob decodes base64 file payloads to build File objects for drag-and-drop/file-input simulation in a browser automation library. Decoding here produces file bytes, not executed code, so the warning's 'before eval' concern does not apply.

  20. info critical → info by review dynamic-execution packages/playwright-core/src/server/javascript.ts:315:7

    `Function(...)` called with a non-literal body (ast pass, confidence 0.90)

        try {
          new Function('(' + expression + ')');
        } catch (e1) {
          // This means we might have a function shorthand. Try another
    likely benign LLM, confidence 0.75 Uses new Function to test whether an expression string parses.
    Reasoning

    Function constructor used only to syntax-check a serialized expression, with a fallback for function shorthand. This is a parse probe inside a Playwright-like evaluate serializer; the input is the caller's own expression to be evaluated in the page anyway.

  21. info critical → info by review dynamic-execution packages/playwright-core/src/server/javascript.ts:324:9

    `Function(...)` called with a non-literal body (ast pass, confidence 0.90)

          try {
            new Function('(' + expression  + ')');
          } catch (e2) {
            // We tried hard to serialize, but there's a weird beast here.
    likely benign LLM, confidence 0.75 Second Function-constructor syntax probe for expression serialization.
    Reasoning

    Same syntax-probe pattern as the previous finding, used while serializing a user-supplied evaluation expression. Expected in a browser automation evaluate path.

  22. info warn → info by review dynamic-import packages/playwright-core/src/server/registry/index.ts:1201:30

    `require` called with a non-literal specifier (ast pass, confidence 0.60)

            linkTarget = (await fs.promises.readFile(linkPath)).toString();
            const browsersJSON = require(path.join(linkTarget, 'browsers.json'));
            const descriptors = readDescriptors(browsersJSON);
            for (const browserName of allDownloadableDirectoriesThatEverExisted) {
    likely benign LLM, confidence 0.80 Loads browsers.json from a symlink-resolved directory.
    Reasoning

    Requires browsers.json from a path read from a symlink (linkPath) to a browsers directory. Reads a local config JSON; it is a data load, not code execution from untrusted input. Some reliance on link target trust, but benign in context.

  23. info warn → info by review dynamic-import packages/playwright-core/src/server/registry/index.ts:1396:38

    `require` called with a non-literal specifier (ast pass, confidence 0.60)

    
    export const registry = new Registry(require(path.join(packageRoot, 'browsers.json')));
    
    export { runOopDownloadBrowserMain } from './oopDownloadBrowserMain';
    likely benign LLM, confidence 0.85 Loads bundled browsers.json to build the browser registry.
    Reasoning

    Requires the package's own browsers.json from packageRoot to construct the registry. Standard internal config loading.

  24. info warn → info by review dynamic-import packages/playwright-core/src/tools/backend/tab.ts:189:35

    `require` called with a non-literal specifier (ast pass, confidence 0.60)

          try {
            const { default: func } = require(initPage);
            await func({ page: this.page });
          } catch (e) {
    likely benign LLM, confidence 0.80 Dynamically requires a configured page-initialization module and calls its default export with the page.
    Reasoning

    The AST pass flags require() with a non-literal specifier. The specifier is an initPage value, which appears to be a user-configured init script path loaded and invoked with the page. That is a plugin or hook pattern, not obfuscation. It would only become a concern if initPage could be set by untrusted input, and the snippet does not show that. The surrounding try/catch and invocation look ordinary.

  25. info warn → info by review dynamic-import packages/playwright-core/src/tools/cli-client/program.ts:78:16

    `require` called with a non-literal specifier (ast pass, confidence 0.60)

      const clientInfo = createClientInfo();
      const help = require(libPath('tools', 'cli-client', 'help.json'));
    
      const argv = process.argv.slice(2);
    dismissed LLM, confidence 0.90 Loads a bundled help.json from an internal library path for the CLI client.
    Reasoning

    The specifier is libPath('tools', 'cli-client', 'help.json'), a fixed internal path to a bundled JSON file. The non-literal form is only a build-path helper. Nothing in the snippet is encoded, hidden, or executes arbitrary code. This is a false positive from the dynamic-import signal.

  26. info warn → info by review dynamic-import packages/playwright-core/src/tools/utils/connect.ts:21:14

    `require` called with a non-literal specifier (ast pass, confidence 0.60)

    export async function connectToBrowserAcrossVersions(descriptor: BrowserDescriptor): Promise<playwright.Browser> {
      const pw = require(descriptor.playwrightLib);
      const browserType = pw[descriptor.browser.browserName] as playwright.BrowserType;
      return await browserType.connect(descriptorEndpoint(descriptor));
    likely benign LLM, confidence 0.75 Dynamically requires a Playwright library by configured path to connect to a browser of a given version.
    Reasoning

    require(descriptor.playwrightLib) loads a Playwright library chosen by the browser descriptor, then looks up a browser type by name. This matches the stated purpose of connecting across Playwright versions. The risk is that an attacker controlling the descriptor could load an arbitrary module, but the snippet shows no obfuscation or hidden payload, and the descriptor is an internal configuration object.

Reproduce This Scan

Run disclude locally to verify. Add --format sarif for SARIF output. Add --llm (with an API key in the environment) to include the LLM review.

cargo install disclude --version 4.0.0
git clone https://github.com/microsoft/playwright playwright && cd playwright
git checkout 0915b2e9128968bba8f5fdfee57b6bca3dab5fbe
disclude scan --severity info --diff cfe490d941f992655948eee8fd4fd94e1c4234bf packages/playwright-core