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

playwright-core 1.65.0-alpha-2026-10-15

Source
https://github.com/microsoft/playwright
Commit scanned
a038fe7e0eb9 (recorded by npm)
Directory
packages/playwright-core (only this part of the repository was scanned)
Compared with
647e485f2ba5 (0 signals introduced since)
Files
370 scanned, 49 skipped (binary, oversized, or unrecognised)
Signals
0 critical · 0 warn · 26 info
Scan time
18.7 s
LLM review
Reviewed by claude-haiku-5-5 (Anthropic): 17 of 17 warn and critical signals; 17 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. 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.95 Runs the Chrome Beta binary with --version using escaped spaces in its macOS path.
    Reasoning

    The backslash escapes are ordinary shell quoting for the spaces in '/Applications/Google Chrome Beta.app'. The command is a plain version check on a macOS app path, so this is a legitimate pattern, not hidden command-name obfuscation.

  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.95 Deletes a temp pkg and runs the Microsoft Edge Beta binary with --version using escaped spaces in its path.
    Reasoning

    The escapes only handle spaces in the Microsoft Edge Beta app path. The script also removes a temporary pkg file first, which is ordinary reinstall housekeeping. Nothing here conceals intent.

  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.95 Runs the Microsoft Edge Dev binary with --version using escaped spaces in its path.
    Reasoning

    Same as the other Edge script: escaped spaces in the 'Microsoft Edge Dev' app path with a --version check. This is ordinary shell quoting.

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

    The require path is built from browser.referenceDir joined with 'package.json'. It reads a package manifest from a known install directory to get a version. The dynamic specifier is a routine way to load a JSON file and carries no sign of malicious loading.

  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 through a computed require path.
    Reasoning

    The path is the package root joined with 'package.json', which is the package's own manifest. Loading it with require is a standard idiom.

  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.90 Gradle wrapper eval used to safely re-split JVM option strings into positional arguments.
    Reasoning

    This is the standard Gradle wrapper idiom. It evals `set --` over xargs-split DEFAULT_JVM_OPTS, JAVA_OPTS and GRADLE_OPTS to word-split them safely. It is widely distributed boilerplate, not a dropper.

  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 that launches the resolved Java command with its arguments.
    Reasoning

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

  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 payloads into File objects for synthetic drag-and-drop.
    Reasoning

    The atob decodes base64 payload buffers into bytes to build File objects for a drag-and-drop data transfer. This is a legitimate browser-automation feature. The decoded data is never executed or fetched.

  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-check-only use of new Function to test whether an expression parses.
    Reasoning

    new Function is used only as a syntax check on a serialized expression, to see whether it parses. The result is discarded. This is a common parse-test technique for function-shorthand detection in Playwright-style evaluation code, not execution of attacker input.

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

    The second new Function call is the same parse-only check on a fallback path, with the result discarded. It does not run the expression.

  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.80 Requires a browsers.json found via a symlink target in the browser registry directory.
    Reasoning

    The path comes from a symlink target that points to a browser download directory, which is then joined with browsers.json. It loads a known manifest from a derived location. The dynamic path is a little loose but it is not a sign of malicious intent.

  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 browsers.json registry manifest via require.
    Reasoning

    This requires browsers.json from the package root, the package's own registry manifest. It is a standard, expected load.

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

    eval of a `(${expr})` expression runs arbitrary JavaScript inside the page context. This is the intended purpose of an evaluate tool, not concealed obfuscation. The risk is real if the expression is untrusted, but the code itself is an explicit feature, so it is more likely benign than malicious.

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

    Same as the locator variant: eval of a supplied expression in the page context is the advertised function of the evaluate tool. It is an intentional code-execution surface, not hidden malicious logic.

  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 Dynamic require of an init-page module path, then invokes its default export with the page object.
    Reasoning

    The require specifier is `initPage`, which appears to be a user-configured init script path loaded inside a try block and called with the page. That is a plausible extension hook for a browser automation tool. It is a dynamic require, but the snippet shows no encoding, obfuscation, or remote fetch. Without the config source, the input's origin is not confirmed as trusted.

  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.93 Requires a bundled help.json file via a path helper.
    Reasoning

    The require argument is `libPath('tools', 'cli-client', 'help.json')`, a fixed path to a bundled JSON file within the package. The flag comes from the non-literal argument form, not from any real dynamic code loading. Loading a static help file this way 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.80 Dynamically loads a Playwright library chosen by a descriptor to connect to a browser across versions.
    Reasoning

    The function requires `descriptor.playwrightLib` to load a Playwright build matching the target browser version, then looks up the browser type by name. This is a legitimate version-compatibility pattern. The module path comes from a descriptor argument, so a caller controlling it could load arbitrary code, but the snippet shows no malicious sink beyond the library-loading itself.

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