npm / playwright-core / 1.64.1-beta-1791926310000

playwright-core 1.64.1-beta-1791926310000

Source
https://github.com/microsoft/playwright
Commit scanned
cfe490d941f9 (recorded by npm)
Directory
packages/playwright-core (only this part of the repository was scanned)
Compared with
866cbb864009 (0 signals introduced since)
Files
370 scanned, 49 skipped (binary, oversized, or unrecognised)
Signals
2 critical · 0 warn · 24 info
Scan time
17.4 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-15 00:07

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);
    likely benign LLM, confidence 0.65 Evaluates a user-supplied JavaScript expression in a locator's element context.
    Reasoning

    eval of a caller-supplied expression inside a page is the intended purpose of this evaluate tool. It is an unsafe pattern if input is untrusted, but it is not hidden or obfuscated, and the code shows no sign of malicious intent.

  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);
    likely benign LLM, confidence 0.65 Evaluates a user-supplied JavaScript expression in the page context.
    Reasoning

    Same as the locator variant: eval of the requested expression in the page, which is the tool's documented function. Risky by design, but not disguised or malicious in itself.

  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.93 Runs the Chrome Beta binary's --version using an escaped-space path.
    Reasoning

    The backslash escapes are ordinary shell quoting for spaces in the macOS app bundle path (Google Chrome Beta.app). The command is a plain version check on a known application, with no encoding or hidden target.

  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.93 Runs the Edge Beta binary's --version with escaped spaces in its path.
    Reasoning

    Same pattern: escaped spaces in the Microsoft Edge Beta app path, followed by a --version call. The script's rm of a temp .pkg is a normal reinstall step.

  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.93 Runs the Edge Dev binary's --version with escaped spaces in its path.
    Reasoning

    Identical to the Edge Beta script, with the path to Microsoft Edge Dev. Escaped spaces are standard shell quoting here.

  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.90 Loads a package.json from a browser reference directory to read its version.
    Reasoning

    require is called on a path.join of a browser reference directory and the literal 'package.json'. This is a routine way to read a package version, with a fixed filename and no attacker-controlled input visible.

  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.92 Requires the package's own package.json via a computed path.
    Reasoning

    The specifier is the package root joined with 'package.json'. This is a standard self-reference to read the package's own metadata.

  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.88 Gradle wrapper idiom that expands DEFAULT_JVM_OPTS and related variables into positional arguments.
    Reasoning

    The eval of set -- with $(xargs ...) is the well-known idiom in the stock Gradle wrapper script for splitting JVM options into arguments. The file's structure matches the standard generated wrapper.

  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.90 Launches the resolved Java executable with the wrapper's arguments, as the stock Gradle wrapper does.
    Reasoning

    exec "$JAVACMD" "$@" is the final launch line of the standard Gradle wrapper, which runs the JVM resolved earlier in the script. It is expected behaviour.

  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.75 Decodes base64 payloads into File objects to simulate a drop event.
    Reasoning

    atob decodes base64 file payloads into a Uint8Array to build File objects for drag-and-drop simulation. It does not feed eval or fetch, so it is a legitimate data-transfer step, though payload origin is not shown.

  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.85 Syntax-validates an expression string to choose a serialization path.
    Reasoning

    new Function('(' + expression + ')') is used only as a syntax check to decide how to serialize a user expression, as Playwright-style evaluation code does. The body is not executed, and the catch branch handles shorthand functions.

  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.85 Second syntax-validation Function call in the expression serializer fallback.
    Reasoning

    Same syntax-check pattern as the earlier Function call, used in the fallback serialization branch. It validates syntax and does not run the code.

  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.90 Loads browsers.json from a symlink-resolved browser directory.
    Reasoning

    The require target is a browsers.json path built from a symlink target resolved by the registry. It is a standard metadata load for browser descriptors.

  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.92 Loads the bundled browsers.json to construct the registry.
    Reasoning

    Requires browsers.json from the package root, the registry's own bundled configuration file. This is a normal module-level data load.

  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.70 Dynamic require of a configured init-page module that is called with the page object.
    Reasoning

    The require specifier is `initPage`, which appears to be a user- or config-supplied init-script path loaded and invoked with the page. That is a plugin-style hook rather than obfuscation. The risk is that an attacker-controlled config could load arbitrary code, but the snippet alone shows no encoding or hidden behaviour, and the pattern is common in browser automation tools.

  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);
    likely benign LLM, confidence 0.92 Loads a bundled help.json file via a path helper.
    Reasoning

    The specifier is `libPath('tools', 'cli-client', 'help.json')`, a fixed path to a bundled JSON help file inside the package. The non-literal form comes from a path helper, not from external input, and loading a JSON file through require is ordinary.

  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.70 Dynamic require of a Playwright library chosen by descriptor, used to connect to a browser of a matching version.
    Reasoning

    The specifier is `descriptor.playwrightLib`, which selects a Playwright library version for cross-version browser connection. The module is then used to pick a browser type and connect. A dynamic module name is a theoretical injection point if the descriptor is attacker-controlled, but the snippet shows a legitimate version-selection purpose with no obfuscation.

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 cfe490d941f992655948eee8fd4fd94e1c4234bf
disclude scan --severity info --diff 866cbb864009f84d7e8e41c64fdbfceff43f280d packages/playwright-core