java-topology/docs/tickets/selenium-0001-grid-session-mutator-list-contains.md
russell@unturf.com 9a0253e724 browser-automation: 4 CWE-407 patches (selenium x2, playwright, webdriverio)
selenium-0001: SessionCapabilitiesMutator list.contains O(NxM) -> LinkedHashSet
  O(N+M). Grid Node session mutation hot path. Bench: 192x at N=M=1000.

selenium-0002: ChromiumOptions merge helpers consolidate four list.contains
  loops behind addArgumentsUnique/addEncodedExtensionsUnique. Bench: 254x
  at N=M=1000.

playwright-0001: roleUtils validRoles / allowsNameFromContent Array.includes
  on 20-70 element constant arrays per element. Converted to Set<string>
  at module load. Bench: 11x at N=10000 elements.

webdriverio-0001: xpath-conditions extractOrConditions orMatches.find +
  values.includes per regex match -> Map<attr, Set<values>>. Bench: 6x at
  K=V=60 in the 'mobileSelectorPerformanceOptimizer'.

Each defect ships: ticket, patch with complexity-gate header, Python
benchmark + correctness test, Makefile, outreach brief. All 16 tests
pass. UNDF IDs: 1276 (playwright), 1277 (selenium-0001), 1288
(selenium-0002), 1289 (webdriverio).
2026-04-22 18:14:39 -04:00

3.5 KiB
Raw Permalink Blame History

selenium-0001: SessionCapabilitiesMutator — O(N×M) args/extensions dedup per session

Target: SeleniumHQ/selenium Severity: MEDIUM-HIGH CWE: CWE-407 (Inefficient Algorithmic Complexity) MOAD: MOAD-0001 (A Sedimentary Defect) File: java/src/org/openqa/selenium/grid/node/config/SessionCapabilitiesMutator.java:136-141, 154-159, 194-199 Language: Java Status: open

Description

Selenium Grid Node runs SessionCapabilitiesMutator on every incoming session creation request. It merges the node's slot stereotype capabilities with the client-requested capabilities. For the Chromium and Firefox merge paths, the args and extensions arrays are deduplicated via List.contains inside a forEach loop, giving O(N×M) where N is the client args/extensions count and M is the stereotype's existing arg/extension count.

A Grid hub handling N parallel sessions per second pays this cost N times per second. On CI farms running large Chrome arg lists (2050 flags plus an extension profile), the per-session cost compounds.

Root Cause

// SessionCapabilitiesMutator.java:120-142  mergeChromiumOptions
for (Map.Entry<String, Object> entry : capsOptions.entrySet()) {
  String name = entry.getKey();
  Object value = entry.getValue();
  if (name.equals("args")) {
    List<String> arguments = new ArrayList<>((List<String>) value);
    List<String> stereotypeArguments =
        new ArrayList<>(
            (List<String>) (stereotypeOptions.getOrDefault(("args"), new ArrayList<>())));

    arguments.forEach(
        arg -> {
          if (!stereotypeArguments.contains(arg)) {   // O(M) scan per arg
            stereotypeArguments.add(arg);
          }
        });
    toReturn.put("args", stereotypeArguments);
  }

  if (name.equals("extensions")) {
    // same pattern for extensions (lines 154-159)
    ...
    extensionList.forEach(
        extension -> {
          if (!stereotypeExtensions.contains(extension)) {   // O(M) scan per extension
            stereotypeExtensions.add(extension);
          }
        });
    ...
  }
}

// mergeFirefoxOptions (lines 194-199): identical pattern for args

ArrayList.contains() is O(M). Called inside forEach(List) of size N, total cost is O(N×M) per session per merged list (args and extensions both).

Fix

Build a LinkedHashSet once from the stereotype list (preserves order, O(1) lookup), then iterate the client list and add only new items. Writes the final list back from the set via new ArrayList<>(set).

Set<String> stereotypeArgSet = new LinkedHashSet<>(stereotypeArguments);
for (String arg : arguments) {
  if (stereotypeArgSet.add(arg)) {
    stereotypeArguments.add(arg);
  }
}

Cost becomes O(N+M): one Set build + N Set lookups with amortized O(1) add. Semantics preserved: LinkedHashSet preserves insertion order matching the original ArrayList.contains-based dedup, and Set.add returns true only on first insertion, matching the original if-not-contains-then-add guard.

Severity Note

Affects every session-creation request routed through a Grid Node. Impact grows linearly with session throughput and with the size of Chrome flag lists. Typical production CI farms handle 10100 sessions/sec with 2050 args per session, giving a measurable but non-critical wall-clock cost. Greatest benefit accrues to large enterprises running Selenium Grid at scale.

Complexity Gate

  • N=M=20: fixed must complete in <1ms per session
  • k-scaling 5×: time ratio must be <17.5× (O(k) ≈5×, not O(k²) ≈25×)