Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 254
Add Configuration.disable! to completely disable secure_headers#568
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
d81007d05020284c0e94acbbcaadb93ac8d8f188daaf7f61b5ab4183ccae7096a78f95d922c87File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -125,6 +125,29 @@ end | ||
| However, I would consider these headers anyways depending on your load and bandwidth requirements. | ||
| ## Disabling secure_headers | ||
| If you want to disable `secure_headers` entirely (e.g., for specific environments or deployment scenarios), you can use `Configuration.disable!`: | ||
| ```ruby | ||
| if ENV["ENABLE_STRICT_HEADERS"] | ||
| SecureHeaders::Configuration.default do |config| | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is a configuration setting, this can only be done at server startup right? I think that's implied but would it be valuable to make that clearer in case someone gets the idea that they could disable this during runtime? I could go either way on whether or not that's overkill Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. I think that's worth documenting. Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've also made it so that they're mutually exclusive preventing one from trying to configure and then disable. | ||
| # your configuration here | ||
| end | ||
| else | ||
| SecureHeaders::Configuration.disable! | ||
| end | ||
| ``` | ||
| **Important**: This configuration must be set during application startup (e.g., in an initializer). Once you call either `Configuration.default` or `Configuration.disable!`, the choice cannot be changed at runtime. Attempting to call `disable!` after `default` (or vice versa) will raise an `AlreadyConfiguredError`. | ||
| When disabled, no security headers will be set by the gem. This is useful when: | ||
| - You're gradually rolling out secure_headers across different customers or deployments | ||
| - You need to migrate existing custom headers to secure_headers | ||
| - You want environment-specific control over security headers | ||
| Note: When `disable!` is used, you don't need to configure a default configuration. The gem will not raise a `NotYetConfiguredError`. | ||
| ## Acknowledgements | ||
| This project originated within the Security team at Twitter. An archived fork from the point of transition is here: https://github.com/twitter-archive/secure_headers. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -9,23 +9,53 @@ class AlreadyConfiguredError < StandardError; end | ||
| class NotYetConfiguredError < StandardError; end | ||
| class IllegalPolicyModificationError < StandardError; end | ||
| class << self | ||
| # Public: Disable secure_headers entirely. When disabled, no headers will be set. | ||
| # | ||
| # Note: This must be called before Configuration.default. Calling it after | ||
| # Configuration.default has been set will raise an AlreadyConfiguredError. | ||
| # | ||
| # Returns nothing | ||
| # Raises AlreadyConfiguredError if Configuration.default has already been called | ||
| def disable! | ||
fletchto99 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. fletchto99 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if defined?(@default_config) | ||
| raise AlreadyConfiguredError, "Configuration already set, cannot disable" | ||
| end | ||
| @disabled = true | ||
| @noop_config = create_noop_config.freeze | ||
| # Ensure the built-in NOOP override is available even if `default` has never been called | ||
| @overrides ||= {} | ||
| unless @overrides.key?(NOOP_OVERRIDE) | ||
| @overrides[NOOP_OVERRIDE] = method(:create_noop_config_block) | ||
| end | ||
| end | ||
| # Public: Check if secure_headers is disabled | ||
| # | ||
| # Returns boolean | ||
| def disabled? | ||
| defined?(@disabled) && @disabled | ||
| end | ||
| # Public: Set the global default configuration. | ||
| # | ||
| # Optionally supply a block to override the defaults set by this library. | ||
| # | ||
| # Returns the newly created config. | ||
| # Raises AlreadyConfiguredError if Configuration.disable! has already been called | ||
| def default(&block) | ||
| if disabled? | ||
| raise AlreadyConfiguredError, "Configuration has been disabled, cannot set default" | ||
| end | ||
| if defined?(@default_config) | ||
| raise AlreadyConfiguredError, "Policy already configured" | ||
| end | ||
| # Define a built-in override that clears all configuration options and | ||
| # results in no security headers being set. | ||
| override(NOOP_OVERRIDE) do |config| | ||
| CONFIG_ATTRIBUTES.each do |attr| | ||
| config.instance_variable_set("@#{attr}", OPT_OUT) | ||
| end | ||
| end | ||
| override(NOOP_OVERRIDE, &method(:create_noop_config_block)) | ||
| new_config = new(&block).freeze | ||
| new_config.validate_config! | ||
| @@ -101,6 +131,7 @@ def deep_copy(config) | ||
| # of ensuring that the default config is never mutated and is dup(ed) | ||
| # before it is used in a request. | ||
| def default_config | ||
| return @noop_config if disabled? | ||
| unless defined?(@default_config) | ||
| raise NotYetConfiguredError, "Default policy not yet configured" | ||
| end | ||
| @@ -116,6 +147,19 @@ def deep_copy_if_hash(value) | ||
| value | ||
| end | ||
| end | ||
| # Private: Creates a NOOP configuration that opts out of all headers | ||
| def create_noop_config | ||
| new(&method(:create_noop_config_block)) | ||
| end | ||
| # Private: Block for creating NOOP configuration | ||
| # Used by both create_noop_config and the NOOP_OVERRIDE mechanism | ||
| def create_noop_config_block(config) | ||
| CONFIG_ATTRIBUTES.each do |attr| | ||
| config.instance_variable_set("@#{attr}", OPT_OUT) | ||
| end | ||
| end | ||
| end | ||
| CONFIG_ATTRIBUTES_TO_HEADER_CLASSES = { | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think this is important enough to be in the README or would it make sense to document it elsewhere?