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

playwright-core 1.65.0-alpha-2026-10-20

Source
https://github.com/microsoft/playwright
Commit scanned
27219bd2cc94 (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
17.0 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 Dynamically requires an init-page module and calls its default export with the page object.
    Reasoning

    The require specifier comes from a variable named initPage, which appears to be a configured init-script path, and the call sits inside a try block that runs the default export on the page. That fits a plugin or init-hook loader. The snippet does not show where initPage originates, so if it were attacker-controlled this would be arbitrary code loading, but nothing here indicates that.

  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
    likely benign LLM, confidence 0.90 Runs the installed Chrome Beta binary with --version using an escaped path.
    Reasoning

    The backslash-escaped spaces in the path to a macOS .app bundle are the normal way to write a path with spaces in bash. The command just prints the browser version.

  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
    likely benign LLM, confidence 0.90 Runs the Microsoft Edge Beta binary with --version using an escaped path.
    Reasoning

    Escaped spaces in an application bundle path are ordinary shell quoting. The script removes a temp pkg and then queries the version.

  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
    likely benign LLM, confidence 0.90 Runs the Microsoft Edge Dev binary with --version using an escaped path.
    Reasoning

    Same pattern as the other reinstall scripts: escaped spaces in a macOS app path, with a version check afterward.

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

    require of package.json at a path joined from the browser's reference directory is a plain data load for version lookup. No untrusted code is executed beyond reading a JSON file the tool itself manages.

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

    The path is built from __dirname and a fixed filename, so this loads the package's own package.json. This is standard packaging code.

  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.90 Gradle wrapper's standard eval to split JVM option strings into arguments.
    Reasoning

    This eval of DEFAULT_JVM_OPTS, JAVA_OPTS and GRADLE_OPTS is the standard boilerplate of the Gradle wrapper script for splitting JVM arguments. It is generated, widely distributed code and not an obfuscated 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's final exec that launches the java binary.
    Reasoning

    exec of $JAVACMD with $@ is the normal final step of the Gradle wrapper, launching the resolved Java binary with its arguments.

  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.85 Decodes base64 file contents into File objects for simulated drag-and-drop.
    Reasoning

    atob decodes base64 payloads sent by the caller into bytes that become File objects for a DataTransfer. This is a legitimate way to simulate file drops in browser automation. The decoded data is 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.85 Syntax-checks an expression string by constructing a Function, without invoking it.
    Reasoning

    new Function is used only to test whether an expression string parses, as a fallback for serializing function shorthands. The result is never run. This is a syntax probe, not a dropper.

  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.85 Second syntax-probe Function construction in the serialization fallback.
    Reasoning

    Same syntax-probe pattern as the earlier Function construction, used in a fallback serialization path. It does not execute attacker-supplied logic.

  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.85 Loads browsers.json through a symlink-resolved directory path.
    Reasoning

    The require target is a path joined from a symlink target and browsers.json. This reads the project's own registry config, and the path is not attacker-controlled in an obvious way.

  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.92 Requires the packaged browsers.json registry file.
    Reasoning

    The file is joined from packageRoot and a fixed name, and loads the package's own browsers.json registry data. This is ordinary configuration loading.

  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.75 Evaluates a user-provided JS expression in the page context for an evaluate tool.
    Reasoning

    This eval runs a caller-supplied JavaScript expression inside the page for an evaluate tool. Arbitrary code execution is the tool's intended function, so it is a design risk rather than hidden obfuscation. The wrapping in parentheses is ordinary expression handling.

  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.75 Evaluates a user-provided JS expression in the page context for the tab evaluate tool.
    Reasoning

    Same as the other evaluate call: running a caller-supplied expression is the feature's purpose. It is a dynamic-execution surface but not concealed malicious behavior.

  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 via a path helper with fixed segments.
    Reasoning

    The specifier is built by libPath from fixed literal segments ('tools', 'cli-client', 'help.json'), so it loads a bundled JSON help file. The non-literal flag is a false positive from the static checker, and there is no code execution.

  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 Requires a Playwright library chosen by a browser descriptor to connect across versions.
    Reasoning

    The module specifier is descriptor.playwrightLib, which selects a Playwright library version for connecting to a browser across versions. That is a legitimate compatibility pattern. The snippet does not show where the descriptor comes from, so a caller-controlled value could load arbitrary modules, which keeps this from being fully settled.

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