Skip to content

feat(detector): add Ruby package inventory and RubyGems/Bundler config audit - #258

Merged
ashishkurmi merged 4 commits into
step-security:mainfrom
raysubham:feat/ruby-inventory
Oct 26, 2026
Merged

ashishkurmi merged 4 commits into
step-security:mainfrom
raysubham:feat/ruby-inventory

Conversation

@raysubham

@raysubham raysubham commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds passive Ruby package inventory and RubyGems/Bundler configuration audit to enterprise telemetry through optional ruby_inventory and ruby_config_audit sections, collected in a new ruby_scan phase.

  • Collects declared requirements from Gemfile/gems.rb (including eval_gemfile and gemspec references) and project gemspecs, locked packages from Gemfile.lock/gems.locked (GEM, GIT, PATH and PLUGIN sources, CHECKSUMS), installed and default gems from gemspec stubs, Bundler git checkouts, and cached .gem archives and vendor caches.
  • Discovers gem homes from rbenv, RVM, asdf, mise and chruby rubies, macOS system Ruby, Homebrew (macOS and Linux), distribution prefixes, Windows RubyInstaller paths, ~/.gem, the XDG data directory, verified GEM_HOME/GEM_PATH and gemrc gemhome/gempath. A verified BUNDLE_GEMFILE replaces the default manifest in its directory.
  • Audits allowlisted RubyGems (GEMRC, ~/.gemrc, system and interpreter etc/gemrc) and Bundler (process, global, project-local and manifest) settings per ambient and project context. Credentials, including dynamic BUNDLE_* host keys and URL userinfo, are reported as configured only; ~/.gem/credentials is checked for existence only. Emits findings ruby-001 through ruby-004.
  • Bundler config uses Bundler's own line-reader semantics; lines where it and YAML disagree are reported as unsupported rather than guessed.
  • Reuses the Go/Cargo/Composer/NuGet guarded filesystem access, developer selection, source coverage and collection limits. Shared, unreadable, cancelled or budget-limited inputs, and incomplete interpreter discovery, leave their sources or audit partial so prior evidence is never retired on unproven absence.
  • Adds one executor method, EnvNames(prefix), which returns environment variable names only (never values) for dynamic Bundler key classification.
  • Adds the shared producer/backend golden fixture and parser, scanner, configuration, limit, cancellation and redaction regressions, plus a FuzzRubyMetadata target.

The collector never executes Ruby, Bundler, gemspecs, Gemfiles, plugins or discovered commands, and never reads credential contents. .gem archives are read bounded and never extracted; YAML tags are never instantiated and aliases never expanded. Community output remains unchanged. Updates README and scan-coverage documentation. Release versioning stays unchanged.

Type of change

  • Bug fix
  • Enhancement
  • Documentation

Testing

  • Native macOS 27.0, Linux arm64 and Windows x64 scans against projects and gems created with official RubyGems/Bundler commands.
  • Normal developer-user send-telemetry --force-scan uploads on all three platforms, independently checked through deployed REST and MCP.
  • Valid JSON, producer/backend contract parity and no secret canaries in emitted or returned data.
  • make lint, full race-enabled make test, and isolated-home make smoke (45/45).
  • go vet, tidy without dependency drift, formatting and diff checks.
  • CGO-disabled Linux, macOS and Windows builds.
  • Native Linux producer golden parity and native Windows guarded-reader tests.
  • All 17 CI checks passed at 8fe99ce, including fuzzing and StepSecurity checks.

Native and deployed checks cover package identities, evidence and version/platform grouping, configuration precedence/redaction, uninstall and whole-project removal, partial-source stale retention and recovery, pagination, filter/cursor contracts, device counts, the fresh configuration index and actual MCP parity. A 50-project workload per platform was measured and published without losing package locations.

The final fix handles Linux multiarch gem roots, canonical runtime/config aliases and guarded Windows volume-directory discovery. Focused before/after probes and the existing scanner suites pass. Local gosec reports 24 existing findings, with none in the changed source files. The golden remains unchanged at 6fbfc578…9424.

Remaining release follow-ups: the dedicated automated Ruby integration-test suite and console/OpenAPI work. Less common runtime-manager layouts are source/test coverage, not a claim that each manager was installed natively. No production deployment was performed.

Related Issues

Builds on the NuGet scanner from #239 (merged) and the Composer configuration helpers from #238 (merged).

…g audit

Adds passive Ruby inventory and RubyGems/Bundler configuration audit to
enterprise telemetry as optional ruby_inventory and ruby_config_audit
sections, collected in a new ruby_scan phase.

Declared, locked, installed and cached evidence stay separate. Sources
that are shared, unreadable, cancelled or budget-limited are partial so
prior evidence is never retired on unproven absence. The collector never
executes Ruby, Bundler, gemspecs or Gemfiles, never extracts archives,
and reports credentials as configured only.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Credential-read protections, gemspec selection and discovery completeness have unresolved correctness and privacy issues.

3 open issues
What changed in this PR

Adds passive Ruby package inventory and RubyGems/Bundler configuration auditing to enterprise telemetry, building on the existing guarded collectors. Community output remains unchanged.

Changes:

  • Collects declared, locked, installed and cached Ruby package evidence.
  • Adds configuration auditing, credential redaction and a ruby_scan telemetry phase.
  • Adds parser, scanner, golden-contract and fuzz coverage, plus documentation.
File Description
SCAN_COVERAGE.md Documents Ruby evidence, discovery, limits and privacy.
README.md Introduces Ruby configuration auditing.
internal/​telemetry/​telemetry.go Adds Ruby payload sections and collection phase.
internal/​telemetry/​telemetry_out_test.go Checks Ruby payload compatibility and omission.
internal/​safepath/​reader_test.go Tests guarded Windows volume-root listing.
internal/​safepath/​open_windows.go Allows directory opens at Windows volume roots.
internal/​model/​ruby_golden_test.go Validates the shared Ruby wire contract.
internal/​model/​model.go Defines Ruby inventory and audit models.
internal/​executor/​user_aware.go Delegates process environment-name listing.
internal/​executor/​mock.go Implements mock environment-name listing.
internal/​executor/​executor.go Adds the EnvNames executor method.
internal/​executor/​env_prefix_test.go Tests environment-name listing across executors.
internal/​detector/​rubyscan_test.go Covers inventory, guards, limits and cancellation.
internal/​detector/​rubymetadata.go Parses Ruby manifests, locks, stubs and archives.
internal/​detector/​rubymetadata_test.go Tests metadata parsing and bounded archive handling.
internal/​detector/​ruby_golden_test.go Checks scanner output against the shared fixture.
internal/​detector/​fuzz_test.go Adds Ruby metadata fuzz coverage.
internal/​detector/​configaudit/​rubyconfig.go Collects and audits RubyGems/Bundler settings.
internal/​detector/​configaudit/​rubyconfig_test.go Tests redaction, precedence and unsupported input.
internal/​detector/​composerscan_test.go Extends the shared test executor with EnvNames.

Review effort: Balanced

continue
}
visited[c.key(path)] = true
ref := c.referenced(path, "manifest", f.src)
ref.path = v.text
case k == "name" && v.kind == 's':
ref.name = v.text
case k == "glob" && v.kind == 's', k == "development_group" && (v.kind == 's' || v.kind == 'y'):
Comment thread internal/detector/rubyscan.go Outdated
Comment on lines +340 to +348
entries, _, err := c.fs(p, 0).ReadDirLimit(p, maxRubyListEntries)
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
reason := c.reason(err, p)
c.degrade(nil, reason)
reasons = append(reasons, reason)
}
continue
}
@ashishkurmi
ashishkurmi added this pull request to the merge queue Oct 26, 2026
Merged via the queue into step-security:main with commit 9a48ff2 Oct 26, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants