-
Notifications
You must be signed in to change notification settings - Fork 25
Rubygems and Bundler Best Practices Guide
This document aims to provide comprehensive guidance on security supply-chain best practices when using Ruby and Bundler for package management. It covers:
- An overview of security features in Ruby and Bundler in the context of supply-chain security
- Explicit recommendations for best practices
- Details or links to official documentation to implement these recommendations
This guide is intended to complement official Ruby and Bundler documentation, not replace it.
- Ruby and Bundler Best Practices Guide
Follow the principle of least privilege for your CI configuration.
Recommendations:
- If using GitHub Actions, use a workflow without access to secrets and with read-only permissions where possible.
- Consider using the OpenSSF Scorecard Action to flag non-read permissions on your project.
Before using a new dependency, assess its origin, trustworthiness, and security posture.
Recommendations:
- Be aware of typosquatting attacks. Verify the correct spelling of gem names and identify the official repository.
- Use tools like Ruby Security to check for known vulnerabilities in gems.
- Assess the gem's activity, number of contributors, and overall health on platforms like GitHub.
In Ruby projects, dependencies are declared in the Gemfile
. The Gemfile.lock
file lists all direct and transitive dependencies with their exact versions.
Recommendations:
-
Always use SSL (HTTPS) when specifying gem sources in your Gemfile. This ensures that gem downloads are encrypted and protects against man-in-the-middle attacks.
Example of a secure Gemfile source definition:
source 'https://rubygems.org'
Avoid using non-SSL sources:
# Insecure - Do not use source 'http://rubygems.org'
-
If you must use a custom gem server, ensure it supports SSL and use the HTTPS URL:
source 'https://your-custom-gem-server.com'
-
For git sources, always use HTTPS or SSH URLs:
gem 'rails', git: 'https://github.com/rails/rails.git' # or gem 'rails', git: '[email protected]:rails/rails.git'
-
Regularly audit your Gemfile to ensure all sources are using secure connections.
Proper version pinning in your Gemfile is crucial for balancing security, stability, and flexibility in your Ruby projects. The right approach can vary depending on the nature of your project and the gems you're using.
A conservative and generally safe approach to version pinning is using the ~>
operator, commonly referred to as the "tilde-greater-than" operator.
This operator allows for a controlled range of version updates, providing a balance between stability and receiving important updates.
Recommendations:
Use the ~>
operator for most gems. For example:
gem 'rails', '~> 6.1.0'
This allows for updates to patch versions (6.1.1, 6.1.2, etc.) but not to minor or major versions. It's equivalent to specifying '>= 6.1.0' and '< 6.2.0'.
Some gems don't follow Semantic Versioning (SemVer) and require different pinning strategies:
-
Calendar Versioning (CalVer) versioning: Some gems use date-based version numbers . In these cases, you might pin to a specific year or month:
gem 'footty', '~> 2024.5.10'
-
Framework-dependent gems: Some gems version according to the major version of a related framework. For example, the
activerecord-oracle_enhanced-adapter
gem versions track Rails versions:gem 'activerecord-oracle_enhanced-adapter', '~> 7.0.0' # follows the major/minor of rails
-
Rapidly evolving gems: For gems under active development, you might want to be more permissive:
gem 'some_new_gem', '~> 0.2' # allow minor version updates
-
Very stable gems: For extremely stable gems, you might be comfortable with a more relaxed constraint:
gem 'rake', '~> 13.0' # allow any 13.x version
-
Regularly review and update your gem versions, especially for security updates.
-
Use
bundle outdated
to check for available updates and understand which gems are held back by your constraints. -
Consider using Dependabot or similar tools to automate version updates and security patches.
-
For application projects, commit your
Gemfile.lock
to version control to ensure consistent builds across environments. -
For library projects (gems), specify a broader range of compatible versions in your
.gemspec
file to maximize compatibility, but use stricter pinning in your Gemfile for development. -
Always test thoroughly after updating dependencies, even patch versions.
-
Be cautious with pre-release versions (alpha, beta, rc) in production environments.
Remember, while these guidelines provide a good starting point, the best pinning strategy can vary based on your specific project needs, the reliability of the gems you're using, and your team's capacity for managing updates.
Ensuring reproducible installations is crucial for security and consistency.
The Gemfile.lock
file lists all direct and transitive dependencies with their exact versions.
Recommendations:
- Always commit the
Gemfile.lock
file to your repository for application projects. - For libraries (gems), do not commit the
Gemfile.lock
file. Instead, specify version constraints in the gemspec file. - Use
bundle install
to generate or update theGemfile.lock
file. - In CI and production environments, use
bundle install --deployment
to ensure exact dependency versions are installed.
Vendoring dependencies (storing them directly in your project) is possible but not recommended for most Ruby projects due to maintenance overhead.
Regularly updating dependencies is crucial for security.
Recommendations:
- Use tools like Dependabot or Bundler-audit to automate dependency updates and security checks.
- Regularly run
bundle update
and review changes before committing. - Consider using
bundle outdated
to check for available updates manually.
Bundler-audit is a crucial tool for maintaining security in Ruby projects. It checks for vulnerable versions of gems in your Gemfile.lock
against a database of known security advisories.
Recommendations:
-
Install bundler-audit:
gem install bundler-audit
-
Regularly update the advisory database:
bundle audit update
-
Run bundler-audit in your project directory:
bundle audit check
-
Integrate bundler-audit into your CI/CD pipeline to catch vulnerabilities early.
-
Address any vulnerabilities found by bundler-audit promptly. This may involve updating gems, applying patches, or finding alternative gems if updates are not available.
-
Use the
--ignore
flag to temporarily ignore specific vulnerabilities if they cannot be immediately addressed, but ensure to document and revisit these:bundle audit check --ignore CVE-2020-12345
-
Consider using the
--format
option to generate reports in different formats (like JSON or YAML) for integration with other tools:bundle audit check --format json
Example of integrating bundler-audit in a GitHub Actions workflow:
name: Security Audit
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 0 * * 0'
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0.0
- name: Install bundler-audit
run: gem install bundler-audit
- name: Update audit database
run: bundle audit update
- name: Run audit
run: bundle audit check
By regularly using bundler-audit, you can catch and address security vulnerabilities in your dependencies early, significantly improving your project's overall security posture.
Follow the OpenSSF recommendations for vulnerability disclosure.
Recommendations:
- Create a
SECURITY.md
file in your repository with instructions for reporting vulnerabilities. - When disclosing vulnerabilities, use CVE numbers and provide detailed information about affected versions and remediation steps.
Publishing gems on RubyGems.org requires a user account.
Recommendations:
- Enable multi-factor authentication (MFA) on your RubyGems.org account.
- Use a strong, unique password for your RubyGems.org account.
Ruby gems can be cryptographically signed to ensure integrity.
Recommendations:
- Sign your gems using
gem cert --build [email protected]
. - When installing gems, use the
--trust-policy
flag to verify signatures:gem install --trust-policy HighSecurity gem_name
.
Recommendations:
- Use
gem push
to publish your gem to RubyGems.org. - For CI/CD pipelines, use RubyGems API keys with limited scope and expiration.
To mitigate risks associated with dependency confusion attacks:
- Use a private gem server like Gemfury or Geminabox for internal gems.
- Configure Bundler to use your private gem server by adding it to your
.gemrc
file or using thesource
directive in yourGemfile
. - Use gem naming conventions that clearly distinguish your internal gems from public ones.
Gemstash is a tool that acts as a local RubyGems server, providing a cache and proxy for RubyGems.org. By using Gemstash, you can improve the resilience of your development and deployment processes against potential RubyGems.org outages.
- Faster gem installation: Gemstash caches gems locally, speeding up subsequent installations.
- Offline access: Once cached, gems can be accessed even without an internet connection.
- Reduced dependency on RubyGems.org: Minimizes the impact of RubyGems.org outages on your development and deployment processes.
- Private gem hosting: Gemstash can also host private gems, providing a centralized gem server for your organization.
-
Install Gemstash:
gem install gemstash
-
Start the Gemstash server:
gemstash start
-
Configure Bundler to use Gemstash as a source. In your
Gemfile
, add:source "http://localhost:9292"
-
Or, configure Bundler globally:
bundle config mirror.https://rubygems.org http://localhost:9292
-
Monitoring: Set up monitoring for your Gemstash server to ensure it's always available.
-
Backup: Regularly backup your Gemstash cache to prevent loss of data.
-
CI/CD Integration: Configure your CI/CD pipelines to use Gemstash for more reliable builds.
-
Fallback mechanism: Configure your systems to fall back to RubyGems.org if Gemstash is unavailable:
source "http://localhost:9292" source "https://rubygems.org"
By implementing Gemstash, you can significantly improve the reliability of your Ruby development and deployment processes, reducing your dependence on the availability of RubyGems.org.
Ruby allows for easy modification of existing classes and modules, which can lead to security risks if not used carefully.
Recommendations:
- Prefer using Refinements over global monkeypatching when extending or modifying existing classes.
- If you must use monkeypatching, clearly document the changes and potential impacts.
- Be cautious when using gems that heavily rely on monkeypatching, as they can introduce unexpected behavior and potential security vulnerabilities.
Example of using Refinements:
module StringExtensions
refine String do
def to_slug
downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '')
end
end
end
class MyClass
using StringExtensions
def process_title(title)
title.to_slug
end
end
This approach limits the scope of the modification and reduces the risk of unintended side effects.
Remember to always keep your Ruby version up to date and follow general secure coding practices to maintain the overall security of your Ruby projects.
Based on https://github.com/ossf/package-manager-best-practices/blob/main/published/npm.md