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

playwright 1.65.0-alpha-2026-10-20

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

Signals

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

  1. info warn → info by review dynamic-execution packages/playwright/src/isomorphic/teleReceiver.ts:462:90

    `atob` decodes a base64 value at runtime — common step before dynamic fetch or eval (ast pass, confidence 0.75)

      private _onStdIO(type: JsonStdIOType, testId: string | undefined, resultId: string | undefined, data: string, isBase64: boolean) {
        const chunk = isBase64 ? ((globalThis as any).Buffer ? Buffer.from(data, 'base64') : atob(data)) : data;
        const test = testId ? this._tests.get(testId) : undefined;
    dismissed LLM, confidence 0.88 Decodes base64 stdio payloads from the test runner into output chunks.
    Reasoning

    atob is used only to decode base64 stdio chunks sent by the test runner's own reporter. The decoded data becomes a plain buffer or string for display, with no eval, fetch, or execution path.

  2. info warn → info by review dynamic-import packages/playwright/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 function libPath(...parts: string[]): string {
    dismissed LLM, confidence 0.92 Loads the package's own package.json via a computed path.
    Reasoning

    The require argument is path.join of the package root and the literal 'package.json', so it loads the tool's own manifest. This is a standard way to read package metadata.

  3. info warn → info by review dynamic-import packages/playwright/src/reporters/coverage.ts:120:27

    `require` called with a non-literal specifier (ast pass, confidence 0.60)

          const resolveFrom = (name: string) => require.resolve(name, { paths: [this._options.configDir] });
          const libCoverage = require(resolveFrom('istanbul-lib-coverage'));
          const libReport = require(resolveFrom('istanbul-lib-report'));
          const reports = require(resolveFrom('istanbul-reports'));
    dismissed LLM, confidence 0.93 Loads istanbul-lib-coverage through a resolver anchored to the config directory.
    Reasoning

    The specifier is resolved with require.resolve from a fixed literal module name, 'istanbul-lib-coverage', and a configured directory. This is a normal optional-dependency load.

  4. info warn → info by review dynamic-import packages/playwright/src/reporters/coverage.ts:121:25

    `require` called with a non-literal specifier (ast pass, confidence 0.60)

          const libCoverage = require(resolveFrom('istanbul-lib-coverage'));
          const libReport = require(resolveFrom('istanbul-lib-report'));
          const reports = require(resolveFrom('istanbul-reports'));
          const context = libReport.createContext({
    dismissed LLM, confidence 0.93 Loads istanbul-lib-report through the same resolver.
    Reasoning

    Same pattern as the neighbouring line: a fixed literal module name resolved against the config directory, then required. Nothing here is attacker-controlled.

  5. info warn → info by review dynamic-import packages/playwright/src/reporters/coverage.ts:122:23

    `require` called with a non-literal specifier (ast pass, confidence 0.60)

          const libReport = require(resolveFrom('istanbul-lib-report'));
          const reports = require(resolveFrom('istanbul-reports'));
          const context = libReport.createContext({
            dir: outputDir,
    dismissed LLM, confidence 0.93 Loads istanbul-reports through the same resolver.
    Reasoning

    A fixed literal module name, 'istanbul-reports', is resolved from the config directory and required. This is an ordinary plugin-style dependency load.

  6. info warn → info by review dynamic-import packages/playwright/src/transform/babelBundle.ts:169:52

    `require` called with a non-literal specifier (ast pass, confidence 0.60)

          ...plugins,
          ...pluginsEpilogue.map(([name, options]) => [require(name), options]),
        ],
        compact: false,
    likely benign LLM, confidence 0.80 Requires Babel plugins by name from the configured epilogue list.
    Reasoning

    The plugin names come from the user's own Babel or Playwright configuration, and require of configured plugins is the expected behaviour. The risk is only that a malicious config could name an arbitrary module, which the project's trust model already covers.

  7. info warn → info by review dynamic-import packages/playwright/src/transform/transform.ts:259:74

    `require` called with a non-literal specifier (ast pass, confidence 0.60)

    
      const { babelTransform }: { babelTransform: BabelTransformFunction } = require(libPath('transform', 'babelBundle'));
      const babelResult = babelTransform(originalCode, filename, !!moduleUrl, pluginsEpilogue, useDefineForClassFields, jsx);
      if (!babelResult?.code)
    dismissed LLM, confidence 0.92 Lazily loads the internal babelBundle module.
    Reasoning

    The path is libPath('transform','babelBundle'), a fixed internal module. The require is a lazy-load of the bundled transform.

  8. info critical → info by review dynamic-execution packages/playwright/src/transform/transform.ts:286:29

    `eval` called with a non-literal expression (ast pass, confidence 0.90)

        const fileName = url.pathToFileURL(file);
        const esmImport = () => eval(`import(${JSON.stringify(fileName)})`);
    
        // For ESM imports handled by the asynchronous loader, issue a preflight to populate
    likely benign LLM, confidence 0.85 Uses eval to issue a native dynamic import of a file URL for ESM loading.
    Reasoning

    eval is used to produce a dynamic import() that Node's CommonJS transpilation would otherwise rewrite to require. The argument is JSON.stringify of a file URL the loader already resolved, so there is no injected payload. This is a known workaround, not an obfuscated dropper.

  9. info critical → info by review dynamic-execution packages/playwright/src/transform/transform.ts:293:13

    `eval` called with a non-literal expression (ast pass, confidence 0.90)

        if (loaderChannel) {
          await eval(`import(${JSON.stringify(fileName + '.esm.preflight')})`)
              .catch((error: any) => debugTest('Failed to load preflight for ' + file + ', source maps may be missing for errors thrown during loading.', error))
              .finally(nextTask);
    likely benign LLM, confidence 0.85 Uses eval to dynamically import an ESM preflight module with error handling.
    Reasoning

    Same workaround as the previous eval: a dynamic import of a derived preflight URL built with JSON.stringify. The fixed suffix and error handling show legitimate loader plumbing.

  10. info warn → info by review dynamic-import packages/playwright/src/transform/transform.ts:305:18

    `require` called with a non-literal specifier (ast pass, confidence 0.60)

      }
      const result = require(file);
      const depsCollector = cc.currentFileDepsCollector();
      if (depsCollector) {
    likely benign LLM, confidence 0.80 Requires the test file currently being loaded by the runner.
    Reasoning

    This loads the test file being run, which is the core function of a test runner. The path comes from the user's own test list, so the non-literal require is expected.

  11. file mixes tab and space indentation (9 tab-indented and 112 space-indented lines) (raw pass, confidence 0.40)

    interface TsConfig {
      extends?: string;
      compilerOptions?: {
        baseUrl?: string;
  12. line length 560 characters (raw pass, confidence 0.50)

      video: VideoMode | /** deprecated */ 'retry-with-video' | { mode: VideoMode, size?: ViewportSize, fps?: number, show?: { actions?: { duration?: number, position?: 'top-left' | 'top' | 'top-right' | 'bottom-left' | 'bottom' | 'bottom-right', /** @deprecated Use `style.title` instead. */ fontSize?: number, cursor?: 'no

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