# UNDF: UNDF-2026-000001277 # UNDF: UNDF-2026-XXXXXXXXX # CWE-407: Algorithmic Complexity -- O(N*M) -> O(N+M) in SessionCapabilitiesMutator # # Defect: Grid Node session mutator deduplicates args/extensions from client caps # against slot stereotype caps via ArrayList.contains inside forEach. That # runs O(M) per iteration across N items. Chromium and Firefox merge paths # both carry this pattern; Chromium path also applies it to extensions. # # Fix: Pre-build a LinkedHashSet from the existing stereotype list once, then # iterate the incoming list with set.add() which returns true only on first # insertion. LinkedHashSet preserves insertion order, preserving the original # semantics. Cost drops to O(N+M) per merged list. # # Complexity gate (tests/test-selenium-cwe407.py): # k-scaling 5x: time ratio must be <17.5x (O(k) ~=5x, not O(k^2) ~=25x) # N=M=20 per session, merge: must complete in <1ms --- a/java/src/org/openqa/selenium/grid/node/config/SessionCapabilitiesMutator.java +++ b/java/src/org/openqa/selenium/grid/node/config/SessionCapabilitiesMutator.java @@ -19,10 +19,12 @@ package org.openqa.selenium.grid.node.config; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.function.Function; import org.openqa.selenium.Capabilities; import org.openqa.selenium.ImmutableCapabilities; @@ -133,12 +135,15 @@ public class SessionCapabilitiesMutator implements Function( (List) (stereotypeOptions.getOrDefault(("args"), new ArrayList<>()))); - arguments.forEach( - arg -> { - if (!stereotypeArguments.contains(arg)) { - stereotypeArguments.add(arg); - } - }); + // Pre-build a Set from the existing list for O(1) dedup (was O(M) per iteration, + // totaling O(N*M) across the forEach). LinkedHashSet preserves insertion order + // so downstream consumers see the same args sequence as the prior implementation. + Set seenArgs = new LinkedHashSet<>(stereotypeArguments); + for (String arg : arguments) { + if (seenArgs.add(arg)) { + stereotypeArguments.add(arg); + } + } toReturn.put("args", stereotypeArguments); } @@ -151,12 +156,12 @@ public class SessionCapabilitiesMutator implements Function( (List) (stereotypeOptions.getOrDefault(("extensions"), new ArrayList<>()))); - extensionList.forEach( - extension -> { - if (!stereotypeExtensions.contains(extension)) { - stereotypeExtensions.add(extension); - } - }); + Set seenExtensions = new LinkedHashSet<>(stereotypeExtensions); + for (String extension : extensionList) { + if (seenExtensions.add(extension)) { + stereotypeExtensions.add(extension); + } + } toReturn.put("extensions", stereotypeExtensions); } @@ -191,12 +196,12 @@ public class SessionCapabilitiesMutator implements Function( (List) (stereotypeOptions.getOrDefault(("args"), new ArrayList<>()))); - arguments.forEach( - arg -> { - if (!stereotypeArguments.contains(arg)) { - stereotypeArguments.add(arg); - } - }); + Set seenArgs = new LinkedHashSet<>(stereotypeArguments); + for (String arg : arguments) { + if (seenArgs.add(arg)) { + stereotypeArguments.add(arg); + } + } toReturn.put("args", stereotypeArguments); }