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

playwright-core 1.65.0-alpha-2026-10-21

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

Signals

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

  1. 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.92 Invokes Chrome Beta's bundled binary by its escaped .app path to check its version.
    Reasoning

    Backslash-escaped spaces in a macOS application bundle path are the normal way to write such a path in bash. The command runs the app's own --version flag. Nothing is hidden.

  2. 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.92 Removes a temporary Edge Beta installer and runs the Edge Beta binary with --version.
    Reasoning

    Same pattern as the Chrome script: escaped spaces in a legitimate /Applications path, followed by a version check. The preceding rm of a temp .pkg is a routine cleanup step.

  3. 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.92 Removes a temporary Edge Dev installer and runs the Edge Dev binary with --version.
    Reasoning

    Identical structure to the Edge Beta script, with escaped spaces in a normal application path. This is ordinary scripting for macOS app paths.

  4. 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

    The specifier is path.join of a browser reference directory and the literal 'package.json'. It reads a local package manifest for its version, which is a common and benign pattern.

  5. 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
  6. 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
  7. 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 Loads the package's own package.json via a path joined from __dirname.
    Reasoning

    The path is built from __dirname and the literal 'package.json', so this loads the package's own manifest. This is standard in Node packages.

  8. 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*)
  9. 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*)
  10. 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*)
  11. 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" )
  12. 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" )
  13. 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 script that splits DEFAULT_JVM_OPTS and JAVA_OPTS into positional arguments via eval.
    Reasoning

    This is the standard Gradle wrapper idiom: eval set -- over xargs-split JVM options. The eval is on locally configured options, not remote input. Gradle's generated wrapper script does this routinely, though the eval is a real injection surface if the env vars are attacker-controlled.

  14. 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.92 Gradle wrapper exec of the resolved Java command with user arguments.
    Reasoning

    Standard Gradle wrapper final exec of the resolved java binary with forwarded arguments. This is the expected launch step, not a dropper.

  15. 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
  16. 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
  17. 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 a File object to attach to a synthetic drop event.
    Reasoning

    atob decodes base64 file payloads into bytes that become a File object for drag-and-drop emulation in the browser DOM. No fetch or eval follows. This is data handling for a browser automation feature.

  18. 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 Syntax-checks a user expression by constructing a Function without calling it.
    Reasoning

    new Function is used only to test whether an expression parses, as a syntax check before trying other serialization paths. The function is never invoked. This is a known idiom in JS tooling and is not obfuscated.

  19. 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 parse-only Function check in the expression serialization fallback.
    Reasoning

    Same parse-only use of new Function as the earlier branch, in a fallback serialization path. It is not executed and there is no evasion.

  20. 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 symlinked browsers directory target.
    Reasoning

    The path is joined from a symlink target and the literal 'browsers.json', so this loads the registry manifest for installed browsers. This is ordinary configuration loading.

  21. 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 package's bundled browsers.json to construct the registry.
    Reasoning

    The path is packageRoot joined with the literal 'browsers.json', which is the package's own bundled registry file. Loading it is routine.

  22. 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 an agent-supplied JavaScript expression inside the page for a browser evaluate tool.
    Reasoning

    eval runs a caller-supplied expression, but inside the browser page context through locator.evaluate, as the tool's stated purpose. The wrapper is a plain template that is not hidden. This is a powerful capability and a risk surface, not obfuscation or a dropper.

  23. 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 an agent-supplied JavaScript expression in the page for a page-level evaluate tool.
    Reasoning

    Same design as the locator variant: eval of a caller-supplied expression in the page context via page.evaluate, consistent with the tool's documented function. It is a capability to note, not evidence of malicious intent.

  24. info warn → info by review dynamic-import packages/playwright-core/src/tools/backend/tab.ts:199: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 Loads a user-configured init page module via require() with a variable path and calls its default export on the page.
    Reasoning

    The require() specifier comes from initPage, which appears to be a user- or config-supplied init script path. That is a plugin-style feature, not an obfuscated payload. The risk is that an attacker controlling that config could load arbitrary code, but nothing in the snippet shows the value is attacker-controlled or hidden.

  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 Requires a bundled help.json resource through a path helper.
    Reasoning

    The specifier is libPath('tools','cli-client','help.json'), a fixed internal JSON resource built from literal path segments. The non-literal flag comes from the helper call, not from untrusted input. This is a routine static data load.

  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.80 Dynamically requires a version-specific Playwright library to connect to a browser.
    Reasoning

    The require() loads a Playwright library chosen by descriptor.playwrightLib so the tool can connect to browsers across Playwright versions. The lookup is a plain property access on a typed descriptor, with no encoding or hiding. It matches a known compatibility pattern.

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.1.0
git clone https://github.com/microsoft/playwright playwright && cd playwright
git checkout 66070a1eaa0b869379607a9e0e1a652ddf2a77da
disclude scan --severity info --diff 27219bd2cc94e1ebceec58f188760a6bf458c192 packages/playwright-core