Dependency Safety
When adding, updating, or reviewing dependencies in Doorkeeper, use this skill to ensure gems are safe, necessary, and properly constrained.
Runtime Dependencies
Doorkeeper has a single runtime dependency: railties >= 5. This is intentional — a library should minimize its dependency footprint.
Before adding a new runtime dependency, ask:
- Is the functionality available in Ruby stdlib or Rails already?
- Can it be an optional dependency (required only if the host app includes it)?
- Is the gem well-maintained (recent releases, multiple maintainers, good test coverage)?
- Does it introduce native extensions that complicate installation?
Prefer optional dependencies over hard requirements. Example: jwt is optional — only needed for private_key_jwt client authentication. Check lib/doorkeeper.rb for the autoload pattern.
Checking for Vulnerabilities
gem install bundler-audit
bundle-audit update
bundle-audit check
Severity mapping:
- Critical/High CVE in a runtime dependency → must fix immediately
- Critical/High CVE in a dev dependency → fix when convenient (doesn't affect users)
- Medium/Low → assess whether exploitable in Doorkeeper's context
Version Constraints
In doorkeeper.gemspec (runtime):
- Use permissive constraints:
gem.add_dependency "railties", ">= 5" - Doorkeeper supports a wide range of Rails versions — don't over-constrain
In Gemfile (development):
- Pin major versions with pessimistic operator:
gem "rspec-rails", "~> 8.0" Gemfile.lockis gitignored (standard for gems — host apps control the resolved versions)
In gemfiles/*.gemfile (CI matrix):
- Each file tests a specific Rails version
- Keep in sync with CI matrix in
.github/workflows/ci.yml - Lockfiles (
gemfiles/*.lock) are also gitignored
Adding a New Development Dependency
- Add to
doorkeeper.gemspecunderadd_development_dependencywith a version constraint - Add to
Gemfileif it needs a specific version or group - Run
bundle installto verify resolution succeeds - Verify CI still passes across the gemfile matrix
Adding an Optional Runtime Dependency
Pattern from existing code (jwt gem in lib/doorkeeper/oauth/client_authentication/private_key_jwt.rb):
# Defined as a private class method, called at the point the dependency is needed:
def self.require_jwt!
require "jwt"
rescue LoadError
raise LoadError,
"private_key_jwt client authentication requires the 'jwt' gem (>= 2.7); " \
"add it to your Gemfile to use this method"
end
private_class_method :require_jwt!
The re-raised LoadError keeps the diagnosis in server logs rather than leaking it to the OAuth client as an error response. Call the method at the point the dependency is first needed, not at file load time.
Dependency Health Indicators
| Signal | Good | Concerning |
|---|---|---|
| Last release | < 6 months ago | > 2 years ago |
| Open issues/PRs | Actively triaged | Hundreds unaddressed |
| Maintainers | Multiple | Single individual |
| Downloads | Established usage | Very low |
| License | MIT, Apache, BSD | GPL (viral), None |
| Dependencies | Few, well-known | Deep tree, obscure gems |
Verification
After dependency changes:
bundle installsucceedsbundle exec rake specpassesbundle-audit checkshows no new vulnerabilitiesbundle exec rubocoppasses- Check CI gemfiles still resolve