java-topology/docs/tickets/vagrant-0001-bundler-plugin-include-in-loop.md
russell@unturf.com 33cc466b3a
wave5: vagrant-0001 flagship + 32-target CI/CD/IaC scan survey
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.
2026-04-25 10:01:56 -04:00

2.5 KiB
Raw Blame History

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×