npm / playwright-core / 1.65.0-alpha-2026-10-19

playwright-core 1.65.0-alpha-2026-10-19

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

Signals

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

  1. `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.60 Loads an optional page init-script module by a configured path and calls its default export with the page.
    Reasoning

    The require specifier is `initPage`, which appears to be a user-supplied init-script path loaded and invoked with the page. That is a plausible configuration hook. It does execute arbitrary code, but only a path the user or config chose, and the snippet shows no obfuscation or remote source. The residual doubt is whether `initPage` can be influenced by untrusted input.

  2. 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
    dismissed LLM, confidence 0.90 Runs Google Chrome Beta's --version using an app path with escaped spaces.
    Reasoning

    The backslash escapes are ordinary shell quoting for spaces in the macOS app bundle path. The command is a plain version check on a known application, so the flag is a false positive.

  3. 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
    dismissed LLM, confidence 0.90 Runs Microsoft Edge Beta --version using an app path with escaped spaces.
    Reasoning

    Escaped spaces in the Edge Beta app path are normal shell quoting. The command is a version check after a temp package removal, with nothing hidden.

  4. 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
    dismissed LLM, confidence 0.90 Runs Microsoft Edge Dev --version using an app path with escaped spaces.
    Reasoning

    Same pattern as the Edge Beta script: escaped spaces in a literal app path followed by a version check. No obfuscation of the command's identity.

  5. 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.85 Reads a browser reference directory's package.json to get its version.
    Reasoning

    The require targets package.json inside a browser reference directory built with path.join. This is a common way to read a local package version and carries no sign of hidden payload loading.

  6. 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
  7. 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
  8. 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.90 Loads the package's own package.json via a computed path.
    Reasoning

    The require loads package.json from the package root computed from __dirname. This is a standard self-metadata read.

  9. 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*)
  10. 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*)
  11. 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*)
  12. 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" )
  13. 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" )
  14. 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 DEFAULT_JVM_OPTS and related options into arguments.
    Reasoning

    This eval set -- with xargs splitting of JVM options is the standard idiom in the Gradle wrapper script. It is widely distributed boilerplate, not a dropper.

  15. 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 Gradle wrapper launches the resolved Java binary.
    Reasoning

    The exec of $JAVACMD is the final launch step of the standard Gradle wrapper. The path is resolved from the JAVA_HOME setup earlier in the script.

  16. 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
  17. 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
  18. info warn → info by review dynamic-execution packages/playwright-core/src/server/dom.ts:714: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 file payloads to build File objects for simulated drag and drop.
    Reasoning

    Decoding base64 buffers into File objects for a DataTransfer drop simulation is a legitimate browser-automation technique. The decoded data is only attached to a File and not executed.

  19. 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.80 Parse-checks an expression string with new Function during serialization.
    Reasoning

    new Function here is used as a syntax probe to test whether an expression parses, falling back to function-shorthand handling. It is a parser check in a serialization utility, not execution of attacker code.

  20. 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.80 Second syntax-probe Function call in the serialization fallback path.
    Reasoning

    Same syntax-probe pattern as the earlier Function call, used as a fallback when serializing values. It is consistent with normal parser-based utility code.

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

    The require path is built from a symlink target read from the local install directory to locate browsers.json. This is a legitimate lookup, though the dynamic path warrants a glance at how linkTarget is validated.

  22. 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.90 Loads the bundled browsers.json manifest from the package root.
    Reasoning

    The require loads browsers.json from the package root. This is a standard static resource load.

  23. info critical → info by review dynamic-execution packages/playwright-core/src/tools/backend/evaluate.ts:50:25

    `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.70 Evaluates a caller-supplied JavaScript expression in the browser page context.
    Reasoning

    eval of an expression inside the page context is the stated purpose of this browser evaluation tool. It is by-design arbitrary JavaScript execution, so it is a deliberate capability rather than obfuscation. It is risky if the input is untrusted, but the snippet shows no hidden or malicious intent.

  24. info critical → info by review dynamic-execution packages/playwright-core/src/tools/backend/evaluate.ts:57:25

    `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.70 Evaluates a caller-supplied expression in the page for the tab-level evaluate tool.
    Reasoning

    Same by-design in-page evaluation of a supplied expression as the locator variant. It is an intended feature of the tool, with the risk coming from the trust placed in its input.

  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 file for the CLI client through a path helper.
    Reasoning

    The specifier is `libPath('tools','cli-client','help.json')`, a fixed relative path to a bundled JSON help file. The non-literal form comes only from a path helper. This is a standard way to load bundled assets and shows no sign of malicious intent.

  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 Dynamically loads a Playwright library chosen by a descriptor field to connect to a browser of a given version.
    Reasoning

    `descriptor.playwrightLib` selects which Playwright module to load so the code can connect to browsers across versions. The module is chosen from a descriptor the program constructs, not from network input or decoded data. Dynamic module selection by name is a legitimate pattern for version compatibility, though an attacker who controls the descriptor could load an arbitrary module.

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