Gems come from one trusted source
ruby/single-gem-source@v1
The Gemfile resolves gems from at most one source, and that source is one the repository trusts. The Gemfile is Ruby code, not a declaration, so it is read by pattern. A second source that is found is a real finding, but finding only one doesn't prove there is only one: a source named inside a condition, a loop or an eval isn't detected.
| Id | ruby/single-gem-source |
| Version | v1 |
| Category | ruby |
| Default severity | error |
| Interpreter | python3 |
| Timeout | 30 seconds |
| Violations tolerated | 0 |
| Collects | ruby |
Collectors
This guardrail doesn't gather anything itself. It relies on the collectors below, which the CLI runs once per build before any check, and reads what they found from GUARDRAIL_FACTS. If a collector collects nothing, this guardrail is skipped, not failed.
| Collector | Gathers | Inputs it is given |
|---|---|---|
ruby | The Bundler project in the project directory: the Ruby version it asks for, the gem sources it resolves from, the gems each manifest declares with their groups and origins, and the lock file beside them. The Gemfile is Ruby rather than a declaration, so what it declares conditionally is not seen and scanned says so. | projectDir |
The inputs above are this guardrail's own inputs, passed straight through to the collector. Setting one in buildnote.json changes what is collected, and two guardrails configured the same way share a single collection.
Configuration
{
"guardrails": {
"failOn": "error",
"checks": [
{
"use": "ruby/single-gem-source@v1",
"severity": "error",
"with": {
"projectDir": ".",
"allowedSources": "https://rubygems.org"
},
"exemptions": []
}
]
}
}Inputs
| Input | Description | Default | Environment variable |
|---|---|---|---|
projectDir | Directory holding the project, relative to the directory the CLI runs in. | . | GUARDRAIL_INPUT_PROJECTDIR |
allowedSources | Comma separated gem sources the repository trusts. A source not on this list is a violation. Trailing slashes and letter case are ignored when comparing. | https://rubygems.org | GUARDRAIL_INPUT_ALLOWEDSOURCES |
How to fix
Keep one source line at the top of the Gemfile, and name any other source only on the gems that need it:
source "https://rubygems.org"
gem "company-internal", source: "https://gems.internal.example.com"With two sources for the whole Gemfile, Bundler resolves each gem from whichever source offers the higher version. Anyone who can publish company-internal to the public index can then take over the install without touching this repository. This is exactly what a dependency confusion attack relies on.
More in ruby
ruby/lockfile-committed. The project commitsGemfile.lock, so an install of the same commit resolves the same gem versions.ruby/no-git-dependencies. No gem the manifests declare is fetched from a git repository instead of a gem source. TheGemfileis Ruby code, not a declaration, so it is read by pattern. A git gem that is found is a real finding, but finding none doesn't prove there are none: a gem added inside a condition, a loop or anevalisn't detected.ruby/version-declared. The project declares the Ruby version it is built and run against, and that version is no older than the configured floor.