vagrant-0001: bundler.rb plugin loader runs Array#include? against plugins.keys / system_plugins inside per-spec loops. O(S*P) per vagrant command. Fix: hoist Set.new outside the loop, O(1) per spec lookup. Bench: 127x at S=2000 P=1000. wave5-cicd-iac-survey.md: documents 32 projects scanned across deployment (Spinnaker, fluxcd, Argo Rollouts/Events), modern CI/CD (Earthly, Dagger, Buck2), container runtime (containerd, crun, skopeo, ko, kaniko, buildah), local k8s (kind, minikube, k3s), IaC + testing (Packer, Vagrant, ansible-lint, Molecule, InSpec, Terratest), contract/mutation testing (Pact, Stryker, mutmut, PIT), security (Semgrep, Bandit, gosec), Java quality (Spotbugs, Checkstyle, chart-testing). Clean-scan honor roll +4: chart-testing, kind, ko, pact-ruby.
2.5 KiB
vagrant-0001: Bundler plugin loader — O(S×P) Array#include? in loop
Target: hashicorp/vagrant
Severity: MEDIUM
CWE: CWE-407 (Inefficient Algorithmic Complexity)
MOAD: MOAD-0001 (A Sedimentary Defect)
File: lib/vagrant/bundler.rb:469-471, 533-534
Language: Ruby
Status: open
Description
Vagrant's Bundler orchestrates plugin resolution and gem-spec selection on every vagrant command run. Two paths walk a list of resolved gem specs and check membership against a plugin/system Array via Array#include? (O(P) linear scan):
# bundler.rb:469-471 — pruning the solution to declared plugins
solution.find_all do |spec|
plugins.keys.include?(spec.name)
end
# bundler.rb:533-534 — adding strict-dependency enforcement specs
plugin_deps += vagrant_internal_specs.map do |spec|
if Vagrant.in_bundler?
next if system_plugins.include?(spec.name)
next if spec.default_gem?
end
...
end
For S resolved specs and P plugins (plus I internal vagrant specs and S system plugins), per-run cost is O(S×P) and O(I×S). Vagrant ships ~30 internal specs by default; users with many third-party plugins push P into the hundreds.
This runs on every vagrant command that touches the plugin path (vagrant up, vagrant ssh, vagrant plugin list, etc.) — slow startup compounds across every developer interaction.
Root Cause
Both plugins.keys and system_plugins are plain Ruby Arrays. Array#include? is O(N) linear scan with == on each entry. Inside the per-spec block, total cost scales as O(S×P).
Fix
Convert the lookup arrays to Sets once outside the loop. Set#include? is O(1) via hash.
# Hoist:
plugin_name_set = Set.new(plugins.keys)
solution.find_all do |spec|
plugin_name_set.include?(spec.name) # O(1)
end
# Same treatment for system_plugins:
system_plugins_set = Set.new(system_plugins)
plugin_deps += vagrant_internal_specs.map do |spec|
if Vagrant.in_bundler?
next if system_plugins_set.include?(spec.name) # O(1)
next if spec.default_gem?
end
...
end
require "set" is already at the top of bundler.rb (line 6). Total cost drops to O(S+P).
Severity Note
Per-vagrant-command overhead. Impact scales linearly with plugin count × resolved spec count. Negligible for one-plugin setups, measurable for multi-plugin developer environments. Cleanup-grade priority but high-frequency — every developer pays this on every command.
Complexity Gate
- S=P=500: fixed must complete in <5ms
- k-scaling 5×: time ratio must be <17.5×