-
Notifications
You must be signed in to change notification settings - Fork 2
Disallow direct env access #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
ef26da4
9c8aff8
845829e
a06d817
cfbfa9e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| module RuboCop | ||
| module Cop | ||
| module Neeto | ||
| # Rails had `secrets.yml` which provided a single source of truth for all | ||
| # environment variables and their fallback values. Rails deprecated this in | ||
| # favor of encrypted credentials, so we created Secvault | ||
| # (https://github.com/neetozone/secvault) to maintain centralized configuration. | ||
| # Direct usage of `ENV` bypasses this system, making it harder to track what | ||
| # environment variables are being used and their defaults. This cop enforces | ||
| # that all environment variable access goes through `Secvault.secrets`. | ||
| # | ||
| # @example DirectEnvAccess: true (default) | ||
| # # Enforces the usage of `Secvault.secrets` over direct `ENV` access. | ||
| # | ||
| # # bad | ||
| # api_key = ENV['STRIPE_API_KEY'] | ||
| # | ||
| # # bad | ||
| # default_timezone = ENV['DEFAULT_TIMEZONE'] || 'UTC' | ||
| # | ||
| # # good | ||
| # api_key = Secvault.secrets.stripe_api_key | ||
| # | ||
| # # good | ||
| # default_timezone = Secvault.secrets.default_timezone | ||
| # | ||
| # # good (ENV access is permitted in directories other than the app directory) | ||
| # config.log_level = ENV.fetch('LOG_LEVEL', 'info') | ||
| # | ||
| class DirectEnvAccess < Base | ||
| MSG = "Do not use ENV directly. " \ | ||
| "Use Secvault.secrets to maintain a single source of truth for configuration." | ||
prabodh-panda marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| def_node_matcher :env_access?, <<~PATTERN | ||
| (send (const {nil? cbase} :ENV) _ ...) | ||
| PATTERN | ||
|
|
||
| def on_send(node) | ||
|
||
| return unless env_access?(node) | ||
|
|
||
| add_offense(node) | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| RSpec.describe RuboCop::Cop::Neeto::DirectEnvAccess, :config do | ||
| let(:config) { RuboCop::Config.new } | ||
|
|
||
| it "registers an offense when ENV is accessed with bracket notation" do | ||
| snippet = <<~RUBY | ||
| api_key = ENV['STRIPE_API_KEY'] | ||
| ^^^^^^^^^^^^^^^^^^^^^ #{offense} | ||
| RUBY | ||
| expect_offense(snippet) | ||
| end | ||
|
|
||
| it "registers an offense when ENV.fetch is used" do | ||
| snippet = <<~RUBY | ||
| default_timezone = ENV.fetch('DEFAULT_TIMEZONE', 'UTC') | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{offense} | ||
| RUBY | ||
| expect_offense(snippet) | ||
| end | ||
|
|
||
| it "registers an offense when ENV.fetch is used without a default" do | ||
| snippet = <<~RUBY | ||
| api_key = ENV.fetch('STRIPE_API_KEY') | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{offense} | ||
| RUBY | ||
| expect_offense(snippet) | ||
| end | ||
|
|
||
| it "registers multiple offenses when ENV is accessed multiple times" do | ||
| snippet = <<~RUBY | ||
| api_key = ENV['STRIPE_API_KEY'] | ||
| ^^^^^^^^^^^^^^^^^^^^^ #{offense} | ||
| timeout = ENV.fetch('REQUEST_TIMEOUT', '30') | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{offense} | ||
| RUBY | ||
| expect_offense(snippet) | ||
| end | ||
|
|
||
| it "registers an offense when ENV is accessed with :: prefix" do | ||
| snippet = <<~RUBY | ||
| api_key = ::ENV['STRIPE_API_KEY'] | ||
| ^^^^^^^^^^^^^^^^^^^^^^^ #{offense} | ||
| RUBY | ||
| expect_offense(snippet) | ||
| end | ||
|
|
||
| it "does not register an offense when Secvault.secrets is used" do | ||
| snippet = <<~RUBY | ||
| api_key = Secvault.secrets.stripe_api_key | ||
| RUBY | ||
| expect_no_offenses(snippet) | ||
| end | ||
|
|
||
| it "does not register an offense for non-ENV constants" do | ||
| snippet = <<~RUBY | ||
| value = SOME_CONSTANT['key'] | ||
| RUBY | ||
| expect_no_offenses(snippet) | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def offense | ||
| "Neeto/DirectEnvAccess: #{RuboCop::Cop::Neeto::DirectEnvAccess::MSG}" | ||
| end | ||
| end |
Uh oh!
There was an error while loading. Please reload this page.