npm / playwright-core / 1.65.0-alpha-1792021904000

playwright-core 1.65.0-alpha-1792021904000

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

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 Invokes the Chrome Beta binary at its macOS app-bundle path with --version.
    Reasoning

    The backslash escapes are ordinary shell quoting for an application bundle path containing spaces. The command is a plain version check on the installed Chrome Beta binary, so 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.95 Runs the Microsoft Edge Beta binary at its app-bundle path with --version after cleaning up a temp package.
    Reasoning

    The escaped spaces are needed to reference the Microsoft Edge Beta app bundle path. The script removes a temporary package and then runs the browser's --version, which is routine install-script behaviour.

  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 at its app-bundle path with --version after removing a temp package.
    Reasoning

    Same pattern as the Edge Beta script: escaped spaces in an app-bundle path, followed by a version check. No obfuscation of the command's intent is evident.

  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 to read its version.
    Reasoning

    The require specifier is path.join of a browser's reference directory with 'package.json', which is a local, fixed-name metadata file. The flag is only the non-literal specifier, and the file is read to get a version string.

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

    This loads the project's own package.json from a root computed from __dirname. This is a standard way to read package metadata and has no sign of malicious loading.

  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.88 Gradle wrapper idiom that uses eval to word-split DEFAULT_JVM_OPTS and related options into positional arguments.
    Reasoning

    The eval "set -- $(...)" construct is the well-known idiom in the official Gradle wrapper script for splitting JVM options safely. It is standard upstream boilerplate, not an encoded 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.90 Gradle wrapper's final exec of the resolved java binary with its arguments.
    Reasoning

    exec "$JAVACMD" "$@" is the final launch line in the standard Gradle wrapper, running java with the wrapper's arguments. It is expected behaviour for this file.

  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 to build File objects for simulated drag-and-drop.
    Reasoning

    atob decodes base64 file payloads that are used to construct File objects for a DataTransfer, which is how file drops are simulated in the DOM. The decoded bytes are not 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.78 Parses an expression string via the Function constructor to test whether it is a valid expression, without running it.
    Reasoning

    new Function('(' + expression + ')') is used only as a syntax probe to decide whether a serialized expression parses, and it is never invoked. The pattern is common in browser automation serializers, though it compiles caller-supplied text.

  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.78 Second Function-constructor syntax probe in the expression serializer's fallback path.
    Reasoning

    This is the second fallback syntax probe in the same serializer, again compiling but not executing the expression. It has the same benign purpose and the same caveat about compiling input text.

  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.72 Loads browsers.json from a link target directory found in the local browser registry.
    Reasoning

    The require target is path.join of a symlink target read from the local registry, with a fixed browsers.json filename. Loading a JSON file through a symlink is unusual but supports the browser registry logic, and the code only parses descriptors.

  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.90 Requires the package's browsers.json from the package root to create the registry.
    Reasoning

    This requires the package's own browsers.json from the package root to construct the registry. It is the standard way the module loads its static configuration.

  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 user-supplied JavaScript expression inside a locator's element context as the evaluate tool's core function.
    Reasoning

    eval(`(${expr})`) runs a caller-supplied JavaScript expression in the page context. That is the stated purpose of this evaluate tool, so the eval is intentional. The risk is real for an automation tool but comes from its designed function rather than hidden code.

  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 user-supplied JavaScript expression at page level as the evaluate tool's core function.
    Reasoning

    This is the same design as the locator variant: an intentional eval of a supplied expression in the page for the page-level evaluate tool. It is a feature with inherent injection risk, not concealed malicious code.

  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.75 Dynamic require of a configured init-page module inside a browser tab helper.
    Reasoning

    The require specifier is the initPage variable, which appears to be a user-configured init-script path. Loading a user-supplied init module is a documented extension point in a browser automation tool, not an obfuscation technique. The risk is that the path could come from untrusted input, but the snippet shows no sign of that, and the surrounding try/catch is ordinary error handling.

  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.90 Require of a bundled help.json resource via a path helper in the CLI client.
    Reasoning

    The specifier is libPath('tools','cli-client','help.json'), a fixed internal path to a bundled JSON help file. It only looks dynamic because it goes through a path helper. Loading a local JSON resource this way is routine.

  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.75 Runtime require of a playwright library selected from a browser descriptor for cross-version connect.
    Reasoning

    The specifier is descriptor.playwrightLib, so the playwright module is chosen at runtime by the caller. This fits a cross-version connection utility that picks the matching library for a browser descriptor. The snippet shows no encoding or hidden behaviour. The residual doubt is whether the descriptor could carry attacker-chosen module paths, which would make the require an arbitrary code load.

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