secure_headers-3.7.1/ 0000755 0000041 0000041 00000000000 13156767433 014552 5 ustar www-data www-data secure_headers-3.7.1/Rakefile 0000644 0000041 0000041 00000001047 13156767433 016221 0 ustar www-data www-data #!/usr/bin/env rake
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
require 'net/http'
require 'net/https'
desc "Run RSpec"
RSpec::Core::RakeTask.new do |t|
t.verbose = false
t.rspec_opts = "--format progress"
end
task default: :spec
begin
require 'rdoc/task'
rescue LoadError
require 'rdoc/rdoc'
require 'rake/rdoctask'
RDoc::Task = Rake::RDocTask
end
RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'SecureHeaders'
rdoc.options << '--line-numbers'
rdoc.rdoc_files.include('lib/**/*.rb')
end
secure_headers-3.7.1/Gemfile 0000644 0000041 0000041 00000000545 13156767433 016051 0 ustar www-data www-data source "https://rubygems.org"
gemspec
group :test do
gem "tins", "~> 1.6.0" # 1.7 requires ruby 2.0
gem "pry-nav"
gem "json", "~> 1"
gem "rack", "~> 1"
gem "rspec"
gem "coveralls"
gem "term-ansicolor", "< 1.4"
end
group :guard do
gem "guard-rspec", platforms: [:ruby_19, :ruby_20, :ruby_21, :ruby_22]
gem "growl"
gem "rb-fsevent"
end
secure_headers-3.7.1/secure_headers.gemspec 0000644 0000041 0000041 00000001612 13156767433 021100 0 ustar www-data www-data # -*- encoding: utf-8 -*-
Gem::Specification.new do |gem|
gem.name = "secure_headers"
gem.version = "3.7.1"
gem.authors = ["Neil Matatall"]
gem.email = ["neil.matatall@gmail.com"]
gem.description = 'Manages application of security headers with many safe defaults.'
gem.summary = 'Add easily configured security headers to responses
including content-security-policy, x-frame-options,
strict-transport-security, etc.'
gem.homepage = "https://github.com/twitter/secureheaders"
gem.license = "Apache Public License 2.0"
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.require_paths = ["lib"]
gem.add_development_dependency "rake"
gem.add_dependency "useragent"
end
secure_headers-3.7.1/.rspec 0000644 0000041 0000041 00000000030 13156767433 015660 0 ustar www-data www-data --order rand
--warnings
secure_headers-3.7.1/.ruby-version 0000644 0000041 0000041 00000000006 13156767433 017213 0 ustar www-data www-data 2.3.3
secure_headers-3.7.1/CODE_OF_CONDUCT.md 0000644 0000041 0000041 00000006224 13156767433 017355 0 ustar www-data www-data # Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at neil.matatall@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
secure_headers-3.7.1/spec/ 0000755 0000041 0000041 00000000000 13156767433 015504 5 ustar www-data www-data secure_headers-3.7.1/spec/spec_helper.rb 0000644 0000041 0000041 00000006202 13156767433 020322 0 ustar www-data www-data require 'rubygems'
require 'rspec'
require 'rack'
require 'pry-nav'
require 'coveralls'
Coveralls.wear!
require File.join(File.dirname(__FILE__), '..', 'lib', 'secure_headers')
USER_AGENTS = {
edge: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246",
firefox: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1",
firefox46: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:46.0) Gecko/20100101 Firefox/46.0",
chrome: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5',
ie: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
opera: 'Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00',
ios5: "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
ios6: "Mozilla/5.0 (iPhone; CPU iPhone OS 614 like Mac OS X) AppleWebKit/536.26 (KHTML like Gecko) Version/6.0 Mobile/10B350 Safari/8536.25",
safari5: "Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko ) Version/5.1 Mobile/9B176 Safari/7534.48.3",
safari5_1: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10",
safari6: "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/536.30.1 (KHTML like Gecko) Version/6.0.5 Safari/536.30.1",
safari10: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/602.2.11 (KHTML, like Gecko) Version/10.0.1 Safari/602.2.11"
}
def expect_default_values(hash)
expect(hash[SecureHeaders::ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'")
expect(hash[SecureHeaders::XFrameOptions::HEADER_NAME]).to eq(SecureHeaders::XFrameOptions::DEFAULT_VALUE)
expect(hash[SecureHeaders::XDownloadOptions::HEADER_NAME]).to eq(SecureHeaders::XDownloadOptions::DEFAULT_VALUE)
expect(hash[SecureHeaders::StrictTransportSecurity::HEADER_NAME]).to eq(SecureHeaders::StrictTransportSecurity::DEFAULT_VALUE)
expect(hash[SecureHeaders::XXssProtection::HEADER_NAME]).to eq(SecureHeaders::XXssProtection::DEFAULT_VALUE)
expect(hash[SecureHeaders::XContentTypeOptions::HEADER_NAME]).to eq(SecureHeaders::XContentTypeOptions::DEFAULT_VALUE)
expect(hash[SecureHeaders::XPermittedCrossDomainPolicies::HEADER_NAME]).to eq(SecureHeaders::XPermittedCrossDomainPolicies::DEFAULT_VALUE)
expect(hash[SecureHeaders::ReferrerPolicy::HEADER_NAME]).to be_nil
expect(hash[SecureHeaders::ExpectCertificateTransparency::HEADER_NAME]).to be_nil
end
module SecureHeaders
class Configuration
class << self
def clear_configurations
@configurations = nil
end
end
end
end
def reset_config
SecureHeaders::Configuration.clear_configurations
end
def capture_warning
begin
old_stderr = $stderr
$stderr = StringIO.new
yield
result = $stderr.string
ensure
$stderr = old_stderr
end
result
end
secure_headers-3.7.1/spec/lib/ 0000755 0000041 0000041 00000000000 13156767433 016252 5 ustar www-data www-data secure_headers-3.7.1/spec/lib/secure_headers_spec.rb 0000644 0000041 0000041 00000061271 13156767433 022601 0 ustar www-data www-data require 'spec_helper'
module SecureHeaders
describe SecureHeaders do
before(:each) do
reset_config
end
let(:request) { Rack::Request.new("HTTP_X_FORWARDED_SSL" => "on") }
it "raises a NotYetConfiguredError if default has not been set" do
expect do
SecureHeaders.header_hash_for(request)
end.to raise_error(Configuration::NotYetConfiguredError)
end
it "raises a NotYetConfiguredError if trying to opt-out of unconfigured headers" do
expect do
SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyConfig::CONFIG_KEY)
end.to raise_error(Configuration::NotYetConfiguredError)
end
it "raises and ArgumentError when referencing an override that has not been set" do
expect do
Configuration.default
SecureHeaders.use_secure_headers_override(request, :missing)
end.to raise_error(ArgumentError)
end
describe "#header_hash_for" do
it "allows you to opt out of individual headers via API" do
Configuration.default do |config|
config.csp = { default_src: %w('self')}
config.csp_report_only = config.csp
end
SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyConfig::CONFIG_KEY)
SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyReportOnlyConfig::CONFIG_KEY)
SecureHeaders.opt_out_of_header(request, XContentTypeOptions::CONFIG_KEY)
hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy-Report-Only']).to be_nil
expect(hash['Content-Security-Policy']).to be_nil
expect(hash['X-Content-Type-Options']).to be_nil
end
it "Carries options over when using overrides" do
Configuration.default do |config|
config.x_download_options = OPT_OUT
config.x_permitted_cross_domain_policies = OPT_OUT
end
Configuration.override(:api) do |config|
config.x_frame_options = OPT_OUT
end
SecureHeaders.use_secure_headers_override(request, :api)
hash = SecureHeaders.header_hash_for(request)
expect(hash['X-Download-Options']).to be_nil
expect(hash['X-Permitted-Cross-Domain-Policies']).to be_nil
expect(hash['X-Frame-Options']).to be_nil
end
it "allows you to opt out entirely" do
# configure the disabled-by-default headers to ensure they also do not get set
Configuration.default do |config|
config.csp = { :default_src => ["example.com"] }
config.csp_report_only = config.csp
config.hpkp = {
report_only: false,
max_age: 10000000,
include_subdomains: true,
report_uri: "https://report-uri.io/example-hpkp",
pins: [
{sha256: "abc"},
{sha256: "123"}
]
}
end
SecureHeaders.opt_out_of_all_protection(request)
hash = SecureHeaders.header_hash_for(request)
ALL_HEADER_CLASSES.each do |klass|
expect(hash[klass::CONFIG_KEY]).to be_nil
end
expect(hash.count).to eq(0)
end
it "allows you to override X-Frame-Options settings" do
Configuration.default
SecureHeaders.override_x_frame_options(request, XFrameOptions::DENY)
hash = SecureHeaders.header_hash_for(request)
expect(hash[XFrameOptions::HEADER_NAME]).to eq(XFrameOptions::DENY)
end
it "allows you to override opting out" do
Configuration.default do |config|
config.x_frame_options = OPT_OUT
config.csp = OPT_OUT
end
SecureHeaders.override_x_frame_options(request, XFrameOptions::SAMEORIGIN)
SecureHeaders.override_content_security_policy_directives(request, default_src: %w(https:), script_src: %w('self'))
hash = SecureHeaders.header_hash_for(request)
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src https:; script-src 'self'")
expect(hash[XFrameOptions::HEADER_NAME]).to eq(XFrameOptions::SAMEORIGIN)
end
it "produces a UA-specific CSP when overriding (and busting the cache)" do
Configuration.default do |config|
config.csp = {
default_src: %w('self'),
child_src: %w('self')
}
end
firefox_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:firefox]))
# append an unsupported directive
SecureHeaders.override_content_security_policy_directives(firefox_request, {plugin_types: %w(flash)})
# append a supported directive
SecureHeaders.override_content_security_policy_directives(firefox_request, {script_src: %w('self')})
hash = SecureHeaders.header_hash_for(firefox_request)
# child-src is translated to frame-src
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; frame-src 'self'; script-src 'self'")
end
it "produces a hash of headers with default config" do
Configuration.default
hash = SecureHeaders.header_hash_for(request)
expect_default_values(hash)
end
it "does not set the HSTS header if request is over HTTP" do
plaintext_request = Rack::Request.new({})
Configuration.default do |config|
config.hsts = "max-age=123456"
end
expect(SecureHeaders.header_hash_for(plaintext_request)[StrictTransportSecurity::HEADER_NAME]).to be_nil
end
it "does not set the HPKP header if request is over HTTP" do
plaintext_request = Rack::Request.new({})
Configuration.default do |config|
config.hpkp = {
max_age: 1_000_000,
include_subdomains: true,
report_uri: '//example.com/uri-directive',
pins: [
{ sha256: 'abc' },
{ sha256: '123' }
]
}
end
expect(SecureHeaders.header_hash_for(plaintext_request)[PublicKeyPins::HEADER_NAME]).to be_nil
end
context "content security policy" do
let(:chrome_request) {
Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:chrome]))
}
it "appends a value to csp directive" do
Configuration.default do |config|
config.csp = {
default_src: %w('self'),
script_src: %w(mycdn.com 'unsafe-inline')
}
end
SecureHeaders.append_content_security_policy_directives(request, script_src: %w(anothercdn.com))
hash = SecureHeaders.header_hash_for(request)
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline' anothercdn.com")
end
it "appends child-src to frame-src" do
Configuration.default do |config|
config.csp = {
default_src: %w('self'),
frame_src: %w(frame_src.com)
}
end
SecureHeaders.append_content_security_policy_directives(chrome_request, child_src: %w(child_src.com))
hash = SecureHeaders.header_hash_for(chrome_request)
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; child-src frame_src.com child_src.com")
end
it "appends frame-src to child-src" do
Configuration.default do |config|
config.csp = {
default_src: %w('self'),
child_src: %w(child_src.com)
}
end
safari_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:safari6]))
SecureHeaders.append_content_security_policy_directives(safari_request, frame_src: %w(frame_src.com))
hash = SecureHeaders.header_hash_for(safari_request)
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; frame-src child_src.com frame_src.com")
end
it "supports named appends" do
Configuration.default do |config|
config.csp = {
default_src: %w('self')
}
end
Configuration.named_append(:moar_default_sources) do |request|
{ default_src: %w(https:)}
end
Configuration.named_append(:how_about_a_script_src_too) do |request|
{ script_src: %w('unsafe-inline')}
end
SecureHeaders.use_content_security_policy_named_append(request, :moar_default_sources)
SecureHeaders.use_content_security_policy_named_append(request, :how_about_a_script_src_too)
hash = SecureHeaders.header_hash_for(request)
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self' https:; script-src 'self' https: 'unsafe-inline'")
end
it "appends a nonce to a missing script-src value" do
Configuration.default do |config|
config.csp = {
default_src: %w('self')
}
end
SecureHeaders.content_security_policy_script_nonce(request) # should add the value to the header
hash = SecureHeaders.header_hash_for(chrome_request)
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/\Adefault-src 'self'; script-src 'self' 'nonce-.*'\z/)
end
it "appends a hash to a missing script-src value" do
Configuration.default do |config|
config.csp = {
default_src: %w('self')
}
end
SecureHeaders.append_content_security_policy_directives(request, script_src: %w('sha256-abc123'))
hash = SecureHeaders.header_hash_for(chrome_request)
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/\Adefault-src 'self'; script-src 'self' 'sha256-abc123'\z/)
end
it "overrides individual directives" do
Configuration.default do |config|
config.csp = {
default_src: %w('self')
}
end
SecureHeaders.override_content_security_policy_directives(request, default_src: %w('none'))
hash = SecureHeaders.header_hash_for(request)
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'none'")
end
it "overrides non-existant directives" do
Configuration.default do |config|
config.csp = {
default_src: %w(https:)
}
end
SecureHeaders.override_content_security_policy_directives(request, img_src: [ContentSecurityPolicy::DATA_PROTOCOL])
hash = SecureHeaders.header_hash_for(request)
expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).to be_nil
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src https:; img-src data:")
end
it "does not append a nonce when the browser does not support it" do
Configuration.default do |config|
config.csp = {
default_src: %w('self'),
script_src: %w(mycdn.com 'unsafe-inline'),
style_src: %w('self')
}
end
safari_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:safari5]))
SecureHeaders.content_security_policy_script_nonce(safari_request)
hash = SecureHeaders.header_hash_for(safari_request)
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline'; style-src 'self'")
end
it "appends a nonce to the script-src when used" do
Configuration.default do |config|
config.csp = {
default_src: %w('self'),
script_src: %w(mycdn.com),
style_src: %w('self')
}
end
nonce = SecureHeaders.content_security_policy_script_nonce(chrome_request)
# simulate the nonce being used multiple times in a request:
SecureHeaders.content_security_policy_script_nonce(chrome_request)
SecureHeaders.content_security_policy_script_nonce(chrome_request)
SecureHeaders.content_security_policy_script_nonce(chrome_request)
hash = SecureHeaders.header_hash_for(chrome_request)
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src mycdn.com 'nonce-#{nonce}'; style-src 'self'")
end
it "uses a nonce for safari 10+" do
Configuration.default do |config|
config.csp = {
default_src: %w('self'),
script_src: %w(mycdn.com)
}
end
safari_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:safari10]))
nonce = SecureHeaders.content_security_policy_script_nonce(safari_request)
hash = SecureHeaders.header_hash_for(safari_request)
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src mycdn.com 'nonce-#{nonce}'")
end
it "supports the deprecated `report_only: true` format" do
expect(Kernel).to receive(:warn).once
Configuration.default do |config|
config.csp = {
default_src: %w('self'),
report_only: true
}
end
expect(Configuration.get.csp).to eq(OPT_OUT)
expect(Configuration.get.csp_report_only).to be_a(ContentSecurityPolicyReportOnlyConfig)
hash = SecureHeaders.header_hash_for(request)
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to be_nil
expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).to eq("default-src 'self'")
end
it "Raises an error if csp_report_only is used with `report_only: false`" do
expect do
Configuration.default do |config|
config.csp_report_only = {
default_src: %w('self'),
report_only: false
}
end
end.to raise_error(ContentSecurityPolicyConfigError)
end
context "setting two headers" do
before(:each) do
Configuration.default do |config|
config.csp = {
default_src: %w('self')
}
config.csp_report_only = config.csp
end
end
it "sets identical values when the configs are the same" do
Configuration.default do |config|
config.csp = {
default_src: %w('self')
}
config.csp_report_only = {
default_src: %w('self')
}
end
hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy']).to eq("default-src 'self'")
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
end
it "sets different headers when the configs are different" do
Configuration.default do |config|
config.csp = {
default_src: %w('self')
}
config.csp_report_only = config.csp.merge({script_src: %w('self')})
end
hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy']).to eq("default-src 'self'")
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src 'self'")
end
it "allows you to opt-out of enforced CSP" do
Configuration.default do |config|
config.csp = SecureHeaders::OPT_OUT
config.csp_report_only = {
default_src: %w('self')
}
end
hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy']).to be_nil
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
end
it "opts-out of enforced CSP when only csp_report_only is set" do
expect(Kernel).to receive(:warn).once
Configuration.default do |config|
config.csp_report_only = {
default_src: %w('self')
}
end
hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy']).to be_nil
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
end
it "allows you to set csp_report_only before csp" do
expect(Kernel).to receive(:warn).once
Configuration.default do |config|
config.csp_report_only = {
default_src: %w('self')
}
config.csp = config.csp_report_only.merge({script_src: %w('unsafe-inline')})
end
hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src 'self' 'unsafe-inline'")
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
end
it "allows appending to the enforced policy" do
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :enforced)
hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
end
it "allows appending to the report only policy" do
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :report_only)
hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy']).to eq("default-src 'self'")
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
end
it "allows appending to both policies" do
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :both)
hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
end
it "allows overriding the enforced policy" do
SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :enforced)
hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src anothercdn.com")
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
end
it "allows overriding the report only policy" do
SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :report_only)
hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy']).to eq("default-src 'self'")
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src anothercdn.com")
end
it "allows overriding both policies" do
SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :both)
hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src anothercdn.com")
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src anothercdn.com")
end
context "when inferring which config to modify" do
it "updates the enforced header when configured" do
Configuration.default do |config|
config.csp = {
default_src: %w('self')
}
end
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
expect(hash['Content-Security-Policy-Report-Only']).to be_nil
end
it "updates the report only header when configured" do
Configuration.default do |config|
config.csp = OPT_OUT
config.csp_report_only = {
default_src: %w('self')
}
end
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
expect(hash['Content-Security-Policy']).to be_nil
end
it "updates both headers if both are configured" do
Configuration.default do |config|
config.csp = {
default_src: %w(enforced.com)
}
config.csp_report_only = {
default_src: %w(reportonly.com)
}
end
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
hash = SecureHeaders.header_hash_for(request)
expect(hash['Content-Security-Policy']).to eq("default-src enforced.com; script-src enforced.com anothercdn.com")
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src reportonly.com; script-src reportonly.com anothercdn.com")
end
end
end
end
end
context "validation" do
it "validates your hsts config upon configuration" do
expect do
Configuration.default do |config|
config.hsts = 'lol'
end
end.to raise_error(STSConfigError)
end
it "validates your csp config upon configuration" do
expect do
Configuration.default do |config|
config.csp = { ContentSecurityPolicy::DEFAULT_SRC => '123456' }
end
end.to raise_error(ContentSecurityPolicyConfigError)
end
it "raises errors for unknown directives" do
expect do
Configuration.default do |config|
config.csp = { made_up_directive: '123456' }
end
end.to raise_error(ContentSecurityPolicyConfigError)
end
it "validates your xfo config upon configuration" do
expect do
Configuration.default do |config|
config.x_frame_options = "NOPE"
end
end.to raise_error(XFOConfigError)
end
it "validates your xcto config upon configuration" do
expect do
Configuration.default do |config|
config.x_content_type_options = "lol"
end
end.to raise_error(XContentTypeOptionsConfigError)
end
it "validates your clear site data config upon configuration" do
expect do
Configuration.default do |config|
config.clear_site_data = 1
end
end.to raise_error(ClearSiteDataConfigError)
end
it "validates your x_xss config upon configuration" do
expect do
Configuration.default do |config|
config.x_xss_protection = "lol"
end
end.to raise_error(XXssProtectionConfigError)
end
it "validates your xdo config upon configuration" do
expect do
Configuration.default do |config|
config.x_download_options = "lol"
end
end.to raise_error(XDOConfigError)
end
it "validates your x_permitted_cross_domain_policies config upon configuration" do
expect do
Configuration.default do |config|
config.x_permitted_cross_domain_policies = "lol"
end
end.to raise_error(XPCDPConfigError)
end
it "validates your referrer_policy config upon configuration" do
expect do
Configuration.default do |config|
config.referrer_policy = "lol"
end
end.to raise_error(ReferrerPolicyConfigError)
end
it "validates your hpkp config upon configuration" do
expect do
Configuration.default do |config|
config.hpkp = "lol"
end
end.to raise_error(PublicKeyPinsConfigError)
end
it "validates your cookies config upon configuration" do
expect do
Configuration.default do |config|
config.cookies = { secure: "lol" }
end
end.to raise_error(CookiesConfigError)
end
end
end
end
secure_headers-3.7.1/spec/lib/secure_headers/ 0000755 0000041 0000041 00000000000 13156767433 021233 5 ustar www-data www-data secure_headers-3.7.1/spec/lib/secure_headers/view_helpers_spec.rb 0000644 0000041 0000041 00000007474 13156767433 025302 0 ustar www-data www-data require "spec_helper"
require "erb"
class Message < ERB
include SecureHeaders::ViewHelpers
def self.template
<<-TEMPLATE
<% hashed_javascript_tag(raise_error_on_unrecognized_hash = true) do %>
console.log(1)
<% end %>
<% hashed_style_tag do %>
body {
background-color: black;
}
<% end %>
<% nonced_javascript_tag do %>
body {
console.log(1)
}
<% end %>
<% nonced_style_tag do %>
body {
background-color: black;
}
<% end %>
TEMPLATE
end
def initialize(request, options = {})
@virtual_path = "/asdfs/index"
@_request = request
@template = self.class.template
super(@template)
end
def capture(*args)
yield(*args)
end
def content_tag(type, content = nil, options = nil, &block)
content = if block_given?
capture(block)
end
if options.is_a?(Hash)
options = options.map {|k,v| " #{k}=#{v}"}
end
"<#{type}#{options}>#{content}#{type}>"
end
def result
super(binding)
end
def request
@_request
end
end
module SecureHeaders
describe ViewHelpers do
let(:app) { lambda { |env| [200, env, "app"] } }
let(:middleware) { Middleware.new(app) }
let(:request) { Rack::Request.new("HTTP_USER_AGENT" => USER_AGENTS[:chrome]) }
let(:filename) { "app/views/asdfs/index.html.erb" }
before(:all) do
Configuration.default do |config|
config.csp = {
:default_src => %w('self'),
:script_src => %w('self'),
:style_src => %w('self')
}
end
end
after(:each) do
Configuration.instance_variable_set(:@script_hashes, nil)
Configuration.instance_variable_set(:@style_hashes, nil)
end
it "raises an error when using hashed content without precomputed hashes" do
expect {
Message.new(request).result
}.to raise_error(ViewHelpers::UnexpectedHashedScriptException)
end
it "raises an error when using hashed content with precomputed hashes, but none for the given file" do
Configuration.instance_variable_set(:@script_hashes, filename.reverse => ["'sha256-123'"])
expect {
Message.new(request).result
}.to raise_error(ViewHelpers::UnexpectedHashedScriptException)
end
it "raises an error when using previously unknown hashed content with precomputed hashes for a given file" do
Configuration.instance_variable_set(:@script_hashes, filename => ["'sha256-123'"])
expect {
Message.new(request).result
}.to raise_error(ViewHelpers::UnexpectedHashedScriptException)
end
it "adds known hash values to the corresponding headers when the helper is used" do
begin
allow(SecureRandom).to receive(:base64).and_return("abc123")
expected_hash = "sha256-3/URElR9+3lvLIouavYD/vhoICSNKilh15CzI/nKqg8="
Configuration.instance_variable_set(:@script_hashes, filename => ["'#{expected_hash}'"])
expected_style_hash = "sha256-7oYK96jHg36D6BM042er4OfBnyUDTG3pH1L8Zso3aGc="
Configuration.instance_variable_set(:@style_hashes, filename => ["'#{expected_style_hash}'"])
# render erb that calls out to helpers.
Message.new(request).result
_, env = middleware.call request.env
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/script-src[^;]*'#{Regexp.escape(expected_hash)}'/)
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/script-src[^;]*'nonce-abc123'/)
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/style-src[^;]*'nonce-abc123'/)
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/style-src[^;]*'#{Regexp.escape(expected_style_hash)}'/)
end
end
end
end
secure_headers-3.7.1/spec/lib/secure_headers/headers/ 0000755 0000041 0000041 00000000000 13156767433 022646 5 ustar www-data www-data secure_headers-3.7.1/spec/lib/secure_headers/headers/x_download_options_spec.rb 0000644 0000041 0000041 00000001477 13156767433 030127 0 ustar www-data www-data require 'spec_helper'
module SecureHeaders
describe XDownloadOptions do
specify { expect(XDownloadOptions.make_header).to eq([XDownloadOptions::HEADER_NAME, XDownloadOptions::DEFAULT_VALUE]) }
specify { expect(XDownloadOptions.make_header('noopen')).to eq([XDownloadOptions::HEADER_NAME, 'noopen']) }
context "invalid configuration values" do
it "accepts noopen" do
expect do
XDownloadOptions.validate_config!("noopen")
end.not_to raise_error
end
it "accepts nil" do
expect do
XDownloadOptions.validate_config!(nil)
end.not_to raise_error
end
it "doesn't accept anything besides noopen" do
expect do
XDownloadOptions.validate_config!("open")
end.to raise_error(XDOConfigError)
end
end
end
end
secure_headers-3.7.1/spec/lib/secure_headers/headers/referrer_policy_spec.rb 0000644 0000041 0000041 00000003775 13156767433 027414 0 ustar www-data www-data require 'spec_helper'
module SecureHeaders
describe ReferrerPolicy do
specify { expect(ReferrerPolicy.make_header).to eq([ReferrerPolicy::HEADER_NAME, "origin-when-cross-origin"]) }
specify { expect(ReferrerPolicy.make_header('no-referrer')).to eq([ReferrerPolicy::HEADER_NAME, "no-referrer"]) }
context "valid configuration values" do
it "accepts 'no-referrer'" do
expect do
ReferrerPolicy.validate_config!("no-referrer")
end.not_to raise_error
end
it "accepts 'no-referrer-when-downgrade'" do
expect do
ReferrerPolicy.validate_config!("no-referrer-when-downgrade")
end.not_to raise_error
end
it "accepts 'same-origin'" do
expect do
ReferrerPolicy.validate_config!("same-origin")
end.not_to raise_error
end
it "accepts 'strict-origin'" do
expect do
ReferrerPolicy.validate_config!("strict-origin")
end.not_to raise_error
end
it "accepts 'strict-origin-when-cross-origin'" do
expect do
ReferrerPolicy.validate_config!("strict-origin-when-cross-origin")
end.not_to raise_error
end
it "accepts 'origin'" do
expect do
ReferrerPolicy.validate_config!("origin")
end.not_to raise_error
end
it "accepts 'origin-when-cross-origin'" do
expect do
ReferrerPolicy.validate_config!("origin-when-cross-origin")
end.not_to raise_error
end
it "accepts 'unsafe-url'" do
expect do
ReferrerPolicy.validate_config!("unsafe-url")
end.not_to raise_error
end
it "accepts nil" do
expect do
ReferrerPolicy.validate_config!(nil)
end.not_to raise_error
end
end
context 'invlaid configuration values' do
it "doesn't accept invalid values" do
expect do
ReferrerPolicy.validate_config!("open")
end.to raise_error(ReferrerPolicyConfigError)
end
end
end
end
secure_headers-3.7.1/spec/lib/secure_headers/headers/x_frame_options_spec.rb 0000644 0000041 0000041 00000002023 13156767433 027376 0 ustar www-data www-data require 'spec_helper'
module SecureHeaders
describe XFrameOptions do
describe "#value" do
specify { expect(XFrameOptions.make_header).to eq([XFrameOptions::HEADER_NAME, XFrameOptions::DEFAULT_VALUE]) }
specify { expect(XFrameOptions.make_header("DENY")).to eq([XFrameOptions::HEADER_NAME, "DENY"]) }
context "with invalid configuration" do
it "allows SAMEORIGIN" do
expect do
XFrameOptions.validate_config!("SAMEORIGIN")
end.not_to raise_error
end
it "allows DENY" do
expect do
XFrameOptions.validate_config!("DENY")
end.not_to raise_error
end
it "allows ALLOW-FROM*" do
expect do
XFrameOptions.validate_config!("ALLOW-FROM: example.com")
end.not_to raise_error
end
it "does not allow garbage" do
expect do
XFrameOptions.validate_config!("I like turtles")
end.to raise_error(XFOConfigError)
end
end
end
end
end
secure_headers-3.7.1/spec/lib/secure_headers/headers/strict_transport_security_spec.rb 0000644 0000041 0000041 00000002354 13156767433 031564 0 ustar www-data www-data require 'spec_helper'
module SecureHeaders
describe StrictTransportSecurity do
describe "#value" do
specify { expect(StrictTransportSecurity.make_header).to eq([StrictTransportSecurity::HEADER_NAME, StrictTransportSecurity::DEFAULT_VALUE]) }
specify { expect(StrictTransportSecurity.make_header("max-age=1234; includeSubdomains; preload")).to eq([StrictTransportSecurity::HEADER_NAME, "max-age=1234; includeSubdomains; preload"]) }
context "with an invalid configuration" do
context "with a string argument" do
it "raises an exception with an invalid max-age" do
expect do
StrictTransportSecurity.validate_config!('max-age=abc123')
end.to raise_error(STSConfigError)
end
it "raises an exception if max-age is not supplied" do
expect do
StrictTransportSecurity.validate_config!('includeSubdomains')
end.to raise_error(STSConfigError)
end
it "raises an exception with an invalid format" do
expect do
StrictTransportSecurity.validate_config!('max-age=123includeSubdomains')
end.to raise_error(STSConfigError)
end
end
end
end
end
end
secure_headers-3.7.1/spec/lib/secure_headers/headers/content_security_policy_spec.rb 0000644 0000041 0000041 00000030151 13156767433 031165 0 ustar www-data www-data require 'spec_helper'
module SecureHeaders
describe ContentSecurityPolicy do
let (:default_opts) do
{
default_src: %w(https:),
img_src: %w(https: data:),
script_src: %w('unsafe-inline' 'unsafe-eval' https: data:),
style_src: %w('unsafe-inline' https: about:),
report_uri: %w(/csp_report)
}
end
describe "#name" do
context "when in report-only mode" do
specify { expect(ContentSecurityPolicy.new(default_opts.merge(report_only: true)).name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME) }
end
context "when in enforce mode" do
specify { expect(ContentSecurityPolicy.new(default_opts).name).to eq(ContentSecurityPolicyConfig::HEADER_NAME) }
end
end
describe "#value" do
it "discards 'none' values if any other source expressions are present" do
csp = ContentSecurityPolicy.new(default_opts.merge(child_src: %w('self' 'none')))
expect(csp.value).not_to include("'none'")
end
it "discards source expressions (besides unsafe-* and non-host source values) when * is present" do
csp = ContentSecurityPolicy.new(default_src: %w(* 'unsafe-inline' 'unsafe-eval' http: https: example.org data: blob:))
expect(csp.value).to eq("default-src * 'unsafe-inline' 'unsafe-eval' data: blob:")
end
it "minifies source expressions based on overlapping wildcards" do
config = {
default_src: %w(a.example.org b.example.org *.example.org https://*.example.org)
}
csp = ContentSecurityPolicy.new(config)
expect(csp.value).to eq("default-src *.example.org")
end
it "removes http/s schemes from hosts" do
csp = ContentSecurityPolicy.new(default_src: %w(https://example.org))
expect(csp.value).to eq("default-src example.org")
end
it "does not remove schemes from report-uri values" do
csp = ContentSecurityPolicy.new(default_src: %w(https:), report_uri: %w(https://example.org))
expect(csp.value).to eq("default-src https:; report-uri https://example.org")
end
it "does not remove schemes when :preserve_schemes is true" do
csp = ContentSecurityPolicy.new(default_src: %w(https://example.org), :preserve_schemes => true)
expect(csp.value).to eq("default-src https://example.org")
end
it "removes nil from source lists" do
csp = ContentSecurityPolicy.new(default_src: ["https://example.org", nil])
expect(csp.value).to eq("default-src example.org")
end
it "does not add a directive if the value is an empty array (or all nil)" do
csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], script_src: [nil])
expect(csp.value).to eq("default-src example.org")
end
it "does not add a directive if the value is nil" do
csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], script_src: nil)
expect(csp.value).to eq("default-src example.org")
end
it "does add a boolean directive if the value is true" do
csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], block_all_mixed_content: true, upgrade_insecure_requests: true)
expect(csp.value).to eq("default-src example.org; block-all-mixed-content; upgrade-insecure-requests")
end
it "does not add a boolean directive if the value is false" do
csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], block_all_mixed_content: true, upgrade_insecure_requests: false)
expect(csp.value).to eq("default-src example.org; block-all-mixed-content")
end
it "deduplicates any source expressions" do
csp = ContentSecurityPolicy.new(default_src: %w(example.org example.org example.org))
expect(csp.value).to eq("default-src example.org")
end
it "creates maximally strict sandbox policy when passed no sandbox token values" do
csp = ContentSecurityPolicy.new(default_src: %w(example.org), sandbox: [])
expect(csp.value).to eq("default-src example.org; sandbox")
end
it "creates maximally strict sandbox policy when passed true" do
csp = ContentSecurityPolicy.new(default_src: %w(example.org), sandbox: true)
expect(csp.value).to eq("default-src example.org; sandbox")
end
it "creates sandbox policy when passed valid sandbox token values" do
csp = ContentSecurityPolicy.new(default_src: %w(example.org), sandbox: %w(allow-forms allow-scripts))
expect(csp.value).to eq("default-src example.org; sandbox allow-forms allow-scripts")
end
it "does not emit a warning when using frame-src" do
expect(Kernel).to_not receive(:warn)
ContentSecurityPolicy.new(default_src: %w('self'), frame_src: %w('self')).value
end
it "emits a warning when child-src and frame-src are supplied but are not equal" do
expect(Kernel).to receive(:warn).with(/both :child_src and :frame_src supplied and do not match./)
ContentSecurityPolicy.new(default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)).value
end
it "will still set inconsistent child/frame-src values to be less surprising" do
expect(Kernel).to receive(:warn).at_least(:once)
firefox = ContentSecurityPolicy.new({default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)}, USER_AGENTS[:firefox]).value
firefox_transitional = ContentSecurityPolicy.new({default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)}, USER_AGENTS[:firefox46]).value
expect(firefox).not_to eq(firefox_transitional)
expect(firefox).to match(/frame-src/)
expect(firefox).not_to match(/child-src/)
expect(firefox_transitional).to match(/child-src/)
expect(firefox_transitional).not_to match(/frame-src/)
end
it "supports strict-dynamic" do
csp = ContentSecurityPolicy.new({default_src: %w('self'), script_src: [ContentSecurityPolicy::STRICT_DYNAMIC], script_nonce: 123456}, USER_AGENTS[:chrome])
expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456'")
end
context "browser sniffing" do
let (:complex_opts) do
(ContentSecurityPolicy::ALL_DIRECTIVES - [:frame_src]).each_with_object({}) do |directive, hash|
hash[directive] = ["#{directive.to_s.gsub("_", "-")}.com"]
end.merge({
block_all_mixed_content: true,
upgrade_insecure_requests: true,
script_src: %w(script-src.com),
script_nonce: 123456,
sandbox: %w(allow-forms),
plugin_types: %w(application/pdf)
})
end
it "does not filter any directives for Chrome" do
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:chrome])
expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; plugin-types application/pdf; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
end
it "does not filter any directives for Opera" do
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:opera])
expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; plugin-types application/pdf; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
end
it "filters blocked-all-mixed-content, child-src, and plugin-types for firefox" do
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox])
expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; frame-src child-src.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
end
it "filters blocked-all-mixed-content, frame-src, and plugin-types for firefox 46 and higher" do
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox46])
expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
end
it "child-src value is copied to frame-src, adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for Edge" do
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:edge])
expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
end
it "child-src value is copied to frame-src, adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for safari" do
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari6])
expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
end
it "adds 'unsafe-inline', filters blocked-all-mixed-content, upgrade-insecure-requests, nonce sources, and hash sources for safari 10 and higher" do
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari10])
expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; plugin-types application/pdf; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; report-uri report-uri.com")
end
it "falls back to standard Firefox defaults when the useragent version is not present" do
ua = USER_AGENTS[:firefox].dup
allow(ua).to receive(:version).and_return(nil)
policy = ContentSecurityPolicy.new(complex_opts, ua)
expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; frame-src child-src.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
end
end
end
end
end
secure_headers-3.7.1/spec/lib/secure_headers/headers/public_key_pins_spec.rb 0000644 0000041 0000041 00000002703 13156767433 027366 0 ustar www-data www-data require 'spec_helper'
module SecureHeaders
describe PublicKeyPins do
specify { expect(PublicKeyPins.new(max_age: 1234, report_only: true).name).to eq("Public-Key-Pins-Report-Only") }
specify { expect(PublicKeyPins.new(max_age: 1234).name).to eq("Public-Key-Pins") }
specify { expect(PublicKeyPins.new(max_age: 1234).value).to eq("max-age=1234") }
specify { expect(PublicKeyPins.new(max_age: 1234).value).to eq("max-age=1234") }
specify do
config = { max_age: 1234, pins: [{ sha256: 'base64encodedpin1' }, { sha256: 'base64encodedpin2' }] }
header_value = "max-age=1234; pin-sha256=\"base64encodedpin1\"; pin-sha256=\"base64encodedpin2\""
expect(PublicKeyPins.new(config).value).to eq(header_value)
end
context "with an invalid configuration" do
it "raises an exception when max-age is not provided" do
expect do
PublicKeyPins.validate_config!(foo: 'bar')
end.to raise_error(PublicKeyPinsConfigError)
end
it "raises an exception with an invalid max-age" do
expect do
PublicKeyPins.validate_config!(max_age: 'abc123')
end.to raise_error(PublicKeyPinsConfigError)
end
it 'raises an exception with less than 2 pins' do
expect do
config = { max_age: 1234, pins: [{ sha256: 'base64encodedpin' }] }
PublicKeyPins.validate_config!(config)
end.to raise_error(PublicKeyPinsConfigError)
end
end
end
end
secure_headers-3.7.1/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb 0000644 0000041 0000041 00000002710 13156767433 033160 0 ustar www-data www-data require 'spec_helper'
module SecureHeaders
describe XPermittedCrossDomainPolicies do
specify { expect(XPermittedCrossDomainPolicies.make_header).to eq([XPermittedCrossDomainPolicies::HEADER_NAME, "none"]) }
specify { expect(XPermittedCrossDomainPolicies.make_header('master-only')).to eq([XPermittedCrossDomainPolicies::HEADER_NAME, 'master-only']) }
context "valid configuration values" do
it "accepts 'all'" do
expect do
XPermittedCrossDomainPolicies.validate_config!("all")
end.not_to raise_error
end
it "accepts 'by-ftp-filename'" do
expect do
XPermittedCrossDomainPolicies.validate_config!("by-ftp-filename")
end.not_to raise_error
end
it "accepts 'by-content-type'" do
expect do
XPermittedCrossDomainPolicies.validate_config!("by-content-type")
end.not_to raise_error
end
it "accepts 'master-only'" do
expect do
XPermittedCrossDomainPolicies.validate_config!("master-only")
end.not_to raise_error
end
it "accepts nil" do
expect do
XPermittedCrossDomainPolicies.validate_config!(nil)
end.not_to raise_error
end
end
context 'invlaid configuration values' do
it "doesn't accept invalid values" do
expect do
XPermittedCrossDomainPolicies.validate_config!("open")
end.to raise_error(XPCDPConfigError)
end
end
end
end
secure_headers-3.7.1/spec/lib/secure_headers/headers/policy_management_spec.rb 0000644 0000041 0000041 00000021671 13156767433 027707 0 ustar www-data www-data require 'spec_helper'
module SecureHeaders
describe PolicyManagement do
let (:default_opts) do
{
default_src: %w(https:),
img_src: %w(https: data:),
script_src: %w('unsafe-inline' 'unsafe-eval' https: data:),
style_src: %w('unsafe-inline' https: about:),
report_uri: %w(/csp_report)
}
end
describe "#validate_config!" do
it "accepts all keys" do
# (pulled from README)
config = {
# "meta" values. these will shaped the header, but the values are not included in the header.
report_only: true, # default: false
preserve_schemes: true, # default: false. Schemes are removed from host sources to save bytes and discourage mixed content.
# directive values: these values will directly translate into source directives
default_src: %w(https: 'self'),
frame_src: %w('self' *.twimg.com itunes.apple.com),
child_src: %w('self' *.twimg.com itunes.apple.com),
connect_src: %w(wss:),
font_src: %w('self' data:),
img_src: %w(mycdn.com data:),
manifest_src: %w(manifest.com),
media_src: %w(utoob.com),
object_src: %w('self'),
script_src: %w('self'),
style_src: %w('unsafe-inline'),
base_uri: %w('self'),
form_action: %w('self' github.com),
frame_ancestors: %w('none'),
plugin_types: %w(application/x-shockwave-flash),
block_all_mixed_content: true, # see [http://www.w3.org/TR/mixed-content/](http://www.w3.org/TR/mixed-content/)
upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
report_uri: %w(https://example.com/uri-directive)
}
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(config))
end
it "requires a :default_src value" do
expect do
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %('self')))
end.to raise_error(ContentSecurityPolicyConfigError)
end
it "requires :report_only to be a truthy value" do
expect do
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(report_only: "steve")))
end.to raise_error(ContentSecurityPolicyConfigError)
end
it "requires :preserve_schemes to be a truthy value" do
expect do
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(preserve_schemes: "steve")))
end.to raise_error(ContentSecurityPolicyConfigError)
end
it "requires :block_all_mixed_content to be a boolean value" do
expect do
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(block_all_mixed_content: "steve")))
end.to raise_error(ContentSecurityPolicyConfigError)
end
it "requires :upgrade_insecure_requests to be a boolean value" do
expect do
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(upgrade_insecure_requests: "steve")))
end.to raise_error(ContentSecurityPolicyConfigError)
end
it "requires all source lists to be an array of strings" do
expect do
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: "steve"))
end.to raise_error(ContentSecurityPolicyConfigError)
end
it "allows nil values" do
expect do
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), script_src: ["https:", nil]))
end.to_not raise_error
end
it "rejects unknown directives / config" do
expect do
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), default_src_totally_mispelled: "steve"))
end.to raise_error(ContentSecurityPolicyConfigError)
end
# this is mostly to ensure people don't use the antiquated shorthands common in other configs
it "performs light validation on source lists" do
expect do
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w(self none inline eval)))
end.to raise_error(ContentSecurityPolicyConfigError)
end
it "rejects anything not of the form allow-* as a sandbox value" do
expect do
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: ["steve"])))
end.to raise_error(ContentSecurityPolicyConfigError)
end
it "accepts anything of the form allow-* as a sandbox value " do
expect do
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: ["allow-foo"])))
end.to_not raise_error
end
it "accepts true as a sandbox policy" do
expect do
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: true)))
end.to_not raise_error
end
it "rejects anything not of the form type/subtype as a plugin-type value" do
expect do
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(plugin_types: ["steve"])))
end.to raise_error(ContentSecurityPolicyConfigError)
end
it "accepts anything of the form type/subtype as a plugin-type value " do
expect do
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(plugin_types: ["application/pdf"])))
end.to_not raise_error
end
end
describe "#combine_policies" do
it "combines the default-src value with the override if the directive was unconfigured" do
Configuration.default do |config|
config.csp = {
default_src: %w(https:)
}
end
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, script_src: %w(anothercdn.com))
csp = ContentSecurityPolicy.new(combined_config)
expect(csp.name).to eq(ContentSecurityPolicyConfig::HEADER_NAME)
expect(csp.value).to eq("default-src https:; script-src https: anothercdn.com")
end
it "combines directives where the original value is nil and the hash is frozen" do
Configuration.default do |config|
config.csp = {
default_src: %w('self'),
report_only: false
}.freeze
end
report_uri = "https://report-uri.io/asdf"
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_uri: [report_uri])
csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
expect(csp.value).to include("report-uri #{report_uri}")
end
it "does not combine the default-src value for directives that don't fall back to default sources" do
Configuration.default do |config|
config.csp = {
default_src: %w('self'),
report_only: false
}.freeze
end
non_default_source_additions = ContentSecurityPolicy::NON_FETCH_SOURCES.each_with_object({}) do |directive, hash|
hash[directive] = %w("http://example.org)
end
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, non_default_source_additions)
ContentSecurityPolicy::NON_FETCH_SOURCES.each do |directive|
expect(combined_config[directive]).to eq(%w("http://example.org))
end
ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox]).value
end
it "overrides the report_only flag" do
Configuration.default do |config|
config.csp = {
default_src: %w('self'),
report_only: false
}
end
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_only: true)
csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
expect(csp.name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME)
end
it "overrides the :block_all_mixed_content flag" do
Configuration.default do |config|
config.csp = {
default_src: %w(https:),
block_all_mixed_content: false
}
end
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, block_all_mixed_content: true)
csp = ContentSecurityPolicy.new(combined_config)
expect(csp.value).to eq("default-src https:; block-all-mixed-content")
end
it "raises an error if appending to a OPT_OUT policy" do
Configuration.default do |config|
config.csp = OPT_OUT
end
expect do
ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, script_src: %w(anothercdn.com))
end.to raise_error(ContentSecurityPolicyConfigError)
end
end
end
end
secure_headers-3.7.1/spec/lib/secure_headers/headers/x_xss_protection_spec.rb 0000644 0000041 0000041 00000003217 13156767433 027622 0 ustar www-data www-data require 'spec_helper'
module SecureHeaders
describe XXssProtection do
specify { expect(XXssProtection.make_header).to eq([XXssProtection::HEADER_NAME, XXssProtection::DEFAULT_VALUE]) }
specify { expect(XXssProtection.make_header("1; mode=block; report=https://www.secure.com/reports")).to eq([XXssProtection::HEADER_NAME, '1; mode=block; report=https://www.secure.com/reports']) }
context "with invalid configuration" do
it "should raise an error when providing a string that is not valid" do
expect do
XXssProtection.validate_config!("asdf")
end.to raise_error(XXssProtectionConfigError)
expect do
XXssProtection.validate_config!("asdf; mode=donkey")
end.to raise_error(XXssProtectionConfigError)
end
context "when using a hash value" do
it "should allow string values ('1' or '0' are the only valid strings)" do
expect do
XXssProtection.validate_config!('1')
end.not_to raise_error
end
it "should raise an error if no value key is supplied" do
expect do
XXssProtection.validate_config!("mode=block")
end.to raise_error(XXssProtectionConfigError)
end
it "should raise an error if an invalid key is supplied" do
expect do
XXssProtection.validate_config!("123")
end.to raise_error(XXssProtectionConfigError)
end
it "should raise an error if mode != block" do
expect do
XXssProtection.validate_config!("1; mode=donkey")
end.to raise_error(XXssProtectionConfigError)
end
end
end
end
end
secure_headers-3.7.1/spec/lib/secure_headers/headers/clear_site_data_spec.rb 0000644 0000041 0000041 00000004466 13156767433 027322 0 ustar www-data www-data require 'spec_helper'
module SecureHeaders
describe ClearSiteData do
describe "make_header" do
it "returns nil with nil config" do
expect(described_class.make_header).to be_nil
end
it "returns nil with empty config" do
expect(described_class.make_header([])).to be_nil
end
it "returns nil with opt-out config" do
expect(described_class.make_header(OPT_OUT)).to be_nil
end
it "returns all types with `true` config" do
name, value = described_class.make_header(true)
expect(name).to eq(ClearSiteData::HEADER_NAME)
expect(value).to eq(
%("cache", "cookies", "storage", "executionContexts")
)
end
it "returns specified types" do
name, value = described_class.make_header(["foo", "bar"])
expect(name).to eq(ClearSiteData::HEADER_NAME)
expect(value).to eq(%("foo", "bar"))
end
end
describe "validate_config!" do
it "succeeds for `true` config" do
expect do
described_class.validate_config!(true)
end.not_to raise_error
end
it "succeeds for `nil` config" do
expect do
described_class.validate_config!(nil)
end.not_to raise_error
end
it "succeeds for opt-out config" do
expect do
described_class.validate_config!(OPT_OUT)
end.not_to raise_error
end
it "succeeds for empty config" do
expect do
described_class.validate_config!([])
end.not_to raise_error
end
it "succeeds for Array of Strings config" do
expect do
described_class.validate_config!(["foo"])
end.not_to raise_error
end
it "fails for Array of non-String config" do
expect do
described_class.validate_config!([1])
end.to raise_error(ClearSiteDataConfigError)
end
it "fails for other types of config" do
expect do
described_class.validate_config!(:cookies)
end.to raise_error(ClearSiteDataConfigError)
end
end
describe "make_header_value" do
it "returns a string of quoted values that are comma separated" do
value = described_class.make_header_value(["foo", "bar"])
expect(value).to eq(%("foo", "bar"))
end
end
end
end
secure_headers-3.7.1/spec/lib/secure_headers/headers/x_content_type_options_spec.rb 0000644 0000041 0000041 00000001671 13156767433 031027 0 ustar www-data www-data require 'spec_helper'
module SecureHeaders
describe XContentTypeOptions do
describe "#value" do
specify { expect(XContentTypeOptions.make_header).to eq([XContentTypeOptions::HEADER_NAME, XContentTypeOptions::DEFAULT_VALUE]) }
specify { expect(XContentTypeOptions.make_header("nosniff")).to eq([XContentTypeOptions::HEADER_NAME, "nosniff"]) }
context "invalid configuration values" do
it "accepts nosniff" do
expect do
XContentTypeOptions.validate_config!("nosniff")
end.not_to raise_error
end
it "accepts nil" do
expect do
XContentTypeOptions.validate_config!(nil)
end.not_to raise_error
end
it "doesn't accept anything besides no-sniff" do
expect do
XContentTypeOptions.validate_config!("donkey")
end.to raise_error(XContentTypeOptionsConfigError)
end
end
end
end
end
secure_headers-3.7.1/spec/lib/secure_headers/headers/expect_certificate_spec.rb 0000644 0000041 0000041 00000003664 13156767433 030050 0 ustar www-data www-data # frozen_string_literal: true
require "spec_helper"
module SecureHeaders
describe ExpectCertificateTransparency do
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: true).value).to eq("enforce; max-age=1234") }
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: false).value).to eq("max-age=1234") }
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: "yolocopter").value).to eq("max-age=1234") }
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, report_uri: "https://report-uri.io/expect-ct").value).to eq("max-age=1234; report-uri=\"https://report-uri.io/expect-ct\"") }
specify do
config = { enforce: true, max_age: 1234, report_uri: "https://report-uri.io/expect-ct" }
header_value = "enforce; max-age=1234; report-uri=\"https://report-uri.io/expect-ct\""
expect(ExpectCertificateTransparency.new(config).value).to eq(header_value)
end
context "with an invalid configuration" do
it "raises an exception when configuration isn't a hash" do
expect do
ExpectCertificateTransparency.validate_config!(%w(a))
end.to raise_error(ExpectCertificateTransparencyConfigError)
end
it "raises an exception when max-age is not provided" do
expect do
ExpectCertificateTransparency.validate_config!(foo: "bar")
end.to raise_error(ExpectCertificateTransparencyConfigError)
end
it "raises an exception with an invalid max-age" do
expect do
ExpectCertificateTransparency.validate_config!(max_age: "abc123")
end.to raise_error(ExpectCertificateTransparencyConfigError)
end
it "raises an exception with an invalid enforce value" do
expect do
ExpectCertificateTransparency.validate_config!(enforce: "brokenstring")
end.to raise_error(ExpectCertificateTransparencyConfigError)
end
end
end
end
secure_headers-3.7.1/spec/lib/secure_headers/headers/cookie_spec.rb 0000644 0000041 0000041 00000014232 13156767433 025460 0 ustar www-data www-data require 'spec_helper'
module SecureHeaders
describe Cookie do
let(:raw_cookie) { "_session=thisisatest" }
it "does not tamper with cookies when unconfigured" do
cookie = Cookie.new(raw_cookie, {})
expect(cookie.to_s).to eq(raw_cookie)
end
it "preserves existing attributes" do
cookie = Cookie.new("_session=thisisatest; secure", secure: true)
expect(cookie.to_s).to eq("_session=thisisatest; secure")
end
it "prevents duplicate flagging of attributes" do
cookie = Cookie.new("_session=thisisatest; secure", secure: true)
expect(cookie.to_s.scan(/secure/i).count).to eq(1)
end
context "Secure cookies" do
context "when configured with a boolean" do
it "flags cookies as Secure" do
cookie = Cookie.new(raw_cookie, secure: true)
expect(cookie.to_s).to eq("_session=thisisatest; secure")
end
end
context "when configured with a Hash" do
it "flags cookies as Secure when whitelisted" do
cookie = Cookie.new(raw_cookie, secure: { only: ["_session"]})
expect(cookie.to_s).to eq("_session=thisisatest; secure")
end
it "does not flag cookies as Secure when excluded" do
cookie = Cookie.new(raw_cookie, secure: { except: ["_session"] })
expect(cookie.to_s).to eq("_session=thisisatest")
end
end
end
context "HttpOnly cookies" do
context "when configured with a boolean" do
it "flags cookies as HttpOnly" do
cookie = Cookie.new(raw_cookie, httponly: true)
expect(cookie.to_s).to eq("_session=thisisatest; HttpOnly")
end
end
context "when configured with a Hash" do
it "flags cookies as HttpOnly when whitelisted" do
cookie = Cookie.new(raw_cookie, httponly: { only: ["_session"]})
expect(cookie.to_s).to eq("_session=thisisatest; HttpOnly")
end
it "does not flag cookies as HttpOnly when excluded" do
cookie = Cookie.new(raw_cookie, httponly: { except: ["_session"] })
expect(cookie.to_s).to eq("_session=thisisatest")
end
end
end
context "SameSite cookies" do
it "flags SameSite=Lax" do
cookie = Cookie.new(raw_cookie, samesite: { lax: { only: ["_session"] } })
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
end
it "flags SameSite=Lax when configured with a boolean" do
cookie = Cookie.new(raw_cookie, samesite: { lax: true})
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
end
it "does not flag cookies as SameSite=Lax when excluded" do
cookie = Cookie.new(raw_cookie, samesite: { lax: { except: ["_session"] } })
expect(cookie.to_s).to eq("_session=thisisatest")
end
it "flags SameSite=Strict" do
cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] } })
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
end
it "does not flag cookies as SameSite=Strict when excluded" do
cookie = Cookie.new(raw_cookie, samesite: { strict: { except: ["_session"] } })
expect(cookie.to_s).to eq("_session=thisisatest")
end
it "flags SameSite=Strict when configured with a boolean" do
cookie = Cookie.new(raw_cookie, samesite: { strict: true})
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
end
it "flags properly when both lax and strict are configured" do
raw_cookie = "_session=thisisatest"
cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] }, lax: { only: ["_additional_session"] } })
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
end
it "ignores configuration if the cookie is already flagged" do
raw_cookie = "_session=thisisatest; SameSite=Strict"
cookie = Cookie.new(raw_cookie, samesite: { lax: true })
expect(cookie.to_s).to eq(raw_cookie)
end
end
end
context "with an invalid configuration" do
it "raises an exception when not configured with a Hash" do
expect do
Cookie.validate_config!("configuration")
end.to raise_error(CookiesConfigError)
end
it "raises an exception when configured without a boolean/Hash" do
expect do
Cookie.validate_config!(secure: "true")
end.to raise_error(CookiesConfigError)
end
it "raises an exception when both only and except filters are provided" do
expect do
Cookie.validate_config!(secure: { only: [], except: [] })
end.to raise_error(CookiesConfigError)
end
it "raises an exception when SameSite is not configured with a Hash" do
expect do
Cookie.validate_config!(samesite: true)
end.to raise_error(CookiesConfigError)
end
it "raises an exception when SameSite lax and strict enforcement modes are configured with booleans" do
expect do
Cookie.validate_config!(samesite: { lax: true, strict: true})
end.to raise_error(CookiesConfigError)
end
it "raises an exception when SameSite lax and strict enforcement modes are configured with booleans" do
expect do
Cookie.validate_config!(samesite: { lax: true, strict: { only: ["_anything"] } })
end.to raise_error(CookiesConfigError)
end
it "raises an exception when both only and except filters are provided to SameSite configurations" do
expect do
Cookie.validate_config!(samesite: { lax: { only: ["_anything"], except: ["_anythingelse"] } })
end.to raise_error(CookiesConfigError)
end
it "raises an exception when both lax and strict only filters are provided to SameSite configurations" do
expect do
Cookie.validate_config!(samesite: { lax: { only: ["_anything"] }, strict: { only: ["_anything"] } })
end.to raise_error(CookiesConfigError)
end
it "raises an exception when both lax and strict only filters are provided to SameSite configurations" do
expect do
Cookie.validate_config!(samesite: { lax: { except: ["_anything"] }, strict: { except: ["_anything"] } })
end.to raise_error(CookiesConfigError)
end
end
end
secure_headers-3.7.1/spec/lib/secure_headers/configuration_spec.rb 0000644 0000041 0000041 00000005667 13156767433 025457 0 ustar www-data www-data require 'spec_helper'
module SecureHeaders
describe Configuration do
before(:each) do
reset_config
Configuration.default
end
it "has a default config" do
expect(Configuration.get(Configuration::DEFAULT_CONFIG)).to_not be_nil
end
it "has an 'noop' config" do
expect(Configuration.get(Configuration::NOOP_CONFIGURATION)).to_not be_nil
end
it "precomputes headers upon creation" do
default_config = Configuration.get(Configuration::DEFAULT_CONFIG)
header_hash = default_config.cached_headers.each_with_object({}) do |(key, value), hash|
header_name, header_value = if key == :csp
value["Chrome"]
else
value
end
hash[header_name] = header_value
end
expect_default_values(header_hash)
end
it "copies config values when duping" do
Configuration.override(:test_override, Configuration::NOOP_CONFIGURATION) do
# do nothing, just copy it
end
config = Configuration.get(:test_override)
noop = Configuration.get(Configuration::NOOP_CONFIGURATION)
[:csp, :csp_report_only, :cookies].each do |key|
expect(config.send(key)).to eq(noop.send(key))
end
end
it "regenerates cached headers when building an override" do
Configuration.override(:test_override) do |config|
config.x_content_type_options = OPT_OUT
end
expect(Configuration.get.cached_headers).to_not eq(Configuration.get(:test_override).cached_headers)
end
it "stores an override of the global config" do
Configuration.override(:test_override) do |config|
config.x_frame_options = "DENY"
end
expect(Configuration.get(:test_override)).to_not be_nil
end
it "deep dup's config values when overriding so the original cannot be modified" do
Configuration.override(:override) do |config|
config.csp[:default_src] << "'self'"
end
default = Configuration.get
override = Configuration.get(:override)
expect(override.csp.directive_value(:default_src)).not_to be(default.csp.directive_value(:default_src))
end
it "allows you to override an override" do
Configuration.override(:override) do |config|
config.csp = { default_src: %w('self')}
end
Configuration.override(:second_override, :override) do |config|
config.csp = config.csp.merge(script_src: %w(example.org))
end
original_override = Configuration.get(:override)
expect(original_override.csp.to_h).to eq(default_src: %w('self'))
override_config = Configuration.get(:second_override)
expect(override_config.csp.to_h).to eq(default_src: %w('self'), script_src: %w('self' example.org))
end
it "deprecates the secure_cookies configuration" do
expect(Kernel).to receive(:warn).with(/\[DEPRECATION\]/)
Configuration.default do |config|
config.secure_cookies = true
end
end
end
end
secure_headers-3.7.1/spec/lib/secure_headers/middleware_spec.rb 0000644 0000041 0000041 00000011020 13156767433 024701 0 ustar www-data www-data require "spec_helper"
module SecureHeaders
describe Middleware do
let(:app) { lambda { |env| [200, env, "app"] } }
let(:cookie_app) { lambda { |env| [200, env.merge("Set-Cookie" => "foo=bar"), "app"] } }
let(:middleware) { Middleware.new(app) }
let(:cookie_middleware) { Middleware.new(cookie_app) }
before(:each) do
reset_config
Configuration.default
end
it "warns if the hpkp report-uri host is the same as the current host" do
report_host = "report-uri.io"
Configuration.default do |config|
config.hpkp = {
max_age: 10000000,
pins: [
{sha256: 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c'},
{sha256: '73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f'}
],
report_uri: "https://#{report_host}/example-hpkp"
}
end
expect(Kernel).to receive(:warn).with(Middleware::HPKP_SAME_HOST_WARNING)
middleware.call(Rack::MockRequest.env_for("https://#{report_host}", {}))
end
it "sets the headers" do
_, env = middleware.call(Rack::MockRequest.env_for("https://looocalhost", {}))
expect_default_values(env)
end
it "respects overrides" do
request = Rack::Request.new("HTTP_X_FORWARDED_SSL" => "on")
SecureHeaders.override_x_frame_options(request, "DENY")
_, env = middleware.call request.env
expect(env[XFrameOptions::HEADER_NAME]).to eq("DENY")
end
it "uses named overrides" do
Configuration.override("my_custom_config") do |config|
config.csp[:script_src] = %w(example.org)
end
request = Rack::Request.new({})
SecureHeaders.use_secure_headers_override(request, "my_custom_config")
expect(request.env[SECURE_HEADERS_CONFIG]).to be(Configuration.get("my_custom_config"))
_, env = middleware.call request.env
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match("example.org")
end
context "secure_cookies" do
context "cookies should be flagged" do
it "flags cookies as secure" do
capture_warning do
Configuration.default { |config| config.secure_cookies = true }
end
request = Rack::Request.new("HTTPS" => "on")
_, env = cookie_middleware.call request.env
expect(env['Set-Cookie']).to eq("foo=bar; secure")
end
end
context "cookies should not be flagged" do
it "does not flags cookies as secure" do
capture_warning do
Configuration.default { |config| config.secure_cookies = false }
end
request = Rack::Request.new("HTTPS" => "on")
_, env = cookie_middleware.call request.env
expect(env['Set-Cookie']).to eq("foo=bar")
end
end
end
context "cookies" do
it "flags cookies from configuration" do
Configuration.default { |config| config.cookies = { secure: true, httponly: true } }
request = Rack::Request.new("HTTPS" => "on")
_, env = cookie_middleware.call request.env
expect(env['Set-Cookie']).to eq("foo=bar; secure; HttpOnly")
end
it "flags cookies with a combination of SameSite configurations" do
cookie_middleware = Middleware.new(lambda { |env| [200, env.merge("Set-Cookie" => ["_session=foobar", "_guest=true"]), "app"] })
Configuration.default { |config| config.cookies = { samesite: { lax: { except: ["_session"] }, strict: { only: ["_session"] } } } }
request = Rack::Request.new("HTTPS" => "on")
_, env = cookie_middleware.call request.env
expect(env['Set-Cookie']).to match("_session=foobar; SameSite=Strict")
expect(env['Set-Cookie']).to match("_guest=true; SameSite=Lax")
end
it "disables secure cookies for non-https requests" do
Configuration.default { |config| config.cookies = { secure: true } }
request = Rack::Request.new("HTTPS" => "off")
_, env = cookie_middleware.call request.env
expect(env['Set-Cookie']).to eq("foo=bar")
end
it "sets the secure cookie flag correctly on interleaved http/https requests" do
Configuration.default { |config| config.cookies = { secure: true } }
request = Rack::Request.new("HTTPS" => "off")
_, env = cookie_middleware.call request.env
expect(env['Set-Cookie']).to eq("foo=bar")
request = Rack::Request.new("HTTPS" => "on")
_, env = cookie_middleware.call request.env
expect(env['Set-Cookie']).to eq("foo=bar; secure")
end
end
end
end
secure_headers-3.7.1/.travis.yml 0000644 0000041 0000041 00000000444 13156767433 016665 0 ustar www-data www-data language: ruby
rvm:
- ruby-head
- 2.4.0
- 2.3.3
- 2.2
- 2.1
- 2.0.0
- 1.9.3
- jruby-19mode
- jruby-head
matrix:
allow_failures:
- rvm: jruby-head
- rvm: ruby-head
before_install: gem update bundler
bundler_args: --without guard -j 3
sudo: false
cache: bundler
secure_headers-3.7.1/lib/ 0000755 0000041 0000041 00000000000 13156767433 015320 5 ustar www-data www-data secure_headers-3.7.1/lib/secure_headers.rb 0000644 0000041 0000041 00000027150 13156767433 020633 0 ustar www-data www-data require "secure_headers/configuration"
require "secure_headers/hash_helper"
require "secure_headers/headers/cookie"
require "secure_headers/headers/public_key_pins"
require "secure_headers/headers/content_security_policy"
require "secure_headers/headers/x_frame_options"
require "secure_headers/headers/strict_transport_security"
require "secure_headers/headers/x_xss_protection"
require "secure_headers/headers/x_content_type_options"
require "secure_headers/headers/x_download_options"
require "secure_headers/headers/x_permitted_cross_domain_policies"
require "secure_headers/headers/referrer_policy"
require "secure_headers/headers/clear_site_data"
require "secure_headers/headers/expect_certificate_transparency"
require "secure_headers/middleware"
require "secure_headers/railtie"
require "secure_headers/view_helper"
require "useragent"
require "singleton"
# All headers (except for hpkp) have a default value. Provide SecureHeaders::OPT_OUT
# or ":optout_of_protection" as a config value to disable a given header
module SecureHeaders
class NoOpHeaderConfig
include Singleton
def boom(arg = nil)
raise "Illegal State: attempted to modify NoOpHeaderConfig. Create a new config instead."
end
def to_h
{}
end
def dup
self.class.instance
end
def opt_out?
true
end
alias_method :[], :boom
alias_method :[]=, :boom
alias_method :keys, :boom
end
OPT_OUT = NoOpHeaderConfig.instance
SECURE_HEADERS_CONFIG = "secure_headers_request_config".freeze
NONCE_KEY = "secure_headers_content_security_policy_nonce".freeze
HTTPS = "https".freeze
CSP = ContentSecurityPolicy
ALL_HEADER_CLASSES = [
ExpectCertificateTransparency,
ClearSiteData,
ContentSecurityPolicyConfig,
ContentSecurityPolicyReportOnlyConfig,
StrictTransportSecurity,
PublicKeyPins,
ReferrerPolicy,
XContentTypeOptions,
XDownloadOptions,
XFrameOptions,
XPermittedCrossDomainPolicies,
XXssProtection
].freeze
ALL_HEADERS_BESIDES_CSP = (
ALL_HEADER_CLASSES -
[ContentSecurityPolicyConfig, ContentSecurityPolicyReportOnlyConfig]
).freeze
# Headers set on http requests (excludes STS and HPKP)
HTTP_HEADER_CLASSES =
(ALL_HEADER_CLASSES - [StrictTransportSecurity, PublicKeyPins]).freeze
class << self
# Public: override a given set of directives for the current request. If a
# value already exists for a given directive, it will be overridden.
#
# If CSP was previously OPT_OUT, a new blank policy is used.
#
# additions - a hash containing directives. e.g.
# script_src: %w(another-host.com)
def override_content_security_policy_directives(request, additions, target = nil)
config, target = config_and_target(request, target)
if [:both, :enforced].include?(target)
if config.csp.opt_out?
config.csp = ContentSecurityPolicyConfig.new({})
end
config.csp.merge!(additions)
end
if [:both, :report_only].include?(target)
if config.csp_report_only.opt_out?
config.csp_report_only = ContentSecurityPolicyReportOnlyConfig.new({})
end
config.csp_report_only.merge!(additions)
end
override_secure_headers_request_config(request, config)
end
# Public: appends source values to the current configuration. If no value
# is set for a given directive, the value will be merged with the default-src
# value. If a value exists for the given directive, the values will be combined.
#
# additions - a hash containing directives. e.g.
# script_src: %w(another-host.com)
def append_content_security_policy_directives(request, additions, target = nil)
config, target = config_and_target(request, target)
if [:both, :enforced].include?(target) && !config.csp.opt_out?
config.csp.append(additions)
end
if [:both, :report_only].include?(target) && !config.csp_report_only.opt_out?
config.csp_report_only.append(additions)
end
override_secure_headers_request_config(request, config)
end
def use_content_security_policy_named_append(request, name)
additions = SecureHeaders::Configuration.named_appends(name).call(request)
append_content_security_policy_directives(request, additions)
end
# Public: override X-Frame-Options settings for this request.
#
# value - deny, sameorigin, or allowall
#
# Returns the current config
def override_x_frame_options(request, value)
config = config_for(request)
config.update_x_frame_options(value)
override_secure_headers_request_config(request, config)
end
# Public: opts out of setting a given header by creating a temporary config
# and setting the given headers config to OPT_OUT.
def opt_out_of_header(request, header_key)
config = config_for(request)
config.opt_out(header_key)
override_secure_headers_request_config(request, config)
end
# Public: opts out of setting all headers by telling secure_headers to use
# the NOOP configuration.
def opt_out_of_all_protection(request)
use_secure_headers_override(request, Configuration::NOOP_CONFIGURATION)
end
# Public: Builds the hash of headers that should be applied base on the
# request.
#
# StrictTransportSecurity and PublicKeyPins are not applied to http requests.
# See #config_for to determine which config is used for a given request.
#
# Returns a hash of header names => header values. The value
# returned is meant to be merged into the header value from `@app.call(env)`
# in Rack middleware.
def header_hash_for(request)
prevent_dup = true
config = config_for(request, prevent_dup)
headers = config.cached_headers
user_agent = UserAgent.parse(request.user_agent)
if !config.csp.opt_out? && config.csp.modified?
headers = update_cached_csp(config.csp, headers, user_agent)
end
if !config.csp_report_only.opt_out? && config.csp_report_only.modified?
headers = update_cached_csp(config.csp_report_only, headers, user_agent)
end
header_classes_for(request).each_with_object({}) do |klass, hash|
if header = headers[klass::CONFIG_KEY]
header_name, value = if klass == ContentSecurityPolicyConfig || klass == ContentSecurityPolicyReportOnlyConfig
csp_header_for_ua(header, user_agent)
else
header
end
hash[header_name] = value
end
end
end
# Public: specify which named override will be used for this request.
# Raises an argument error if no named override exists.
#
# name - the name of the previously configured override.
def use_secure_headers_override(request, name)
if config = Configuration.get(name)
override_secure_headers_request_config(request, config)
else
raise ArgumentError.new("no override by the name of #{name} has been configured")
end
end
# Public: gets or creates a nonce for CSP.
#
# The nonce will be added to script_src
#
# Returns the nonce
def content_security_policy_script_nonce(request)
content_security_policy_nonce(request, ContentSecurityPolicy::SCRIPT_SRC)
end
# Public: gets or creates a nonce for CSP.
#
# The nonce will be added to style_src
#
# Returns the nonce
def content_security_policy_style_nonce(request)
content_security_policy_nonce(request, ContentSecurityPolicy::STYLE_SRC)
end
# Public: Retreives the config for a given header type:
#
# Checks to see if there is an override for this request, then
# Checks to see if a named override is used for this request, then
# Falls back to the global config
def config_for(request, prevent_dup = false)
config = request.env[SECURE_HEADERS_CONFIG] ||
Configuration.get(Configuration::DEFAULT_CONFIG)
# Global configs are frozen, per-request configs are not. When we're not
# making modifications to the config, prevent_dup ensures we don't dup
# the object unnecessarily. It's not necessarily frozen to begin with.
if config.frozen? && !prevent_dup
config.dup
else
config
end
end
private
TARGETS = [:both, :enforced, :report_only]
def raise_on_unknown_target(target)
unless TARGETS.include?(target)
raise "Unrecognized target: #{target}. Must be [:both, :enforced, :report_only]"
end
end
def config_and_target(request, target)
config = config_for(request)
target = guess_target(config) unless target
raise_on_unknown_target(target)
[config, target]
end
def guess_target(config)
if !config.csp.opt_out? && !config.csp_report_only.opt_out?
:both
elsif !config.csp.opt_out?
:enforced
elsif !config.csp_report_only.opt_out?
:report_only
else
:both
end
end
# Private: gets or creates a nonce for CSP.
#
# Returns the nonce
def content_security_policy_nonce(request, script_or_style)
request.env[NONCE_KEY] ||= SecureRandom.base64(32).chomp
nonce_key = script_or_style == ContentSecurityPolicy::SCRIPT_SRC ? :script_nonce : :style_nonce
append_content_security_policy_directives(request, nonce_key => request.env[NONCE_KEY])
request.env[NONCE_KEY]
end
# Private: convenience method for specifying which configuration object should
# be used for this request.
#
# Returns the config.
def override_secure_headers_request_config(request, config)
request.env[SECURE_HEADERS_CONFIG] = config
end
# Private: determines which headers are applicable to a given request.
#
# Returns a list of classes whose corresponding header values are valid for
# this request.
def header_classes_for(request)
if request.scheme == HTTPS
ALL_HEADER_CLASSES
else
HTTP_HEADER_CLASSES
end
end
def update_cached_csp(config, headers, user_agent)
headers = Configuration.send(:deep_copy, headers)
headers[config.class::CONFIG_KEY] = {}
variation = ContentSecurityPolicy.ua_to_variation(user_agent)
headers[config.class::CONFIG_KEY][variation] = ContentSecurityPolicy.make_header(config, user_agent)
headers
end
# Private: chooses the applicable CSP header for the provided user agent.
#
# headers - a hash of header_config_key => [header_name, header_value]
#
# Returns a CSP [header, value] array
def csp_header_for_ua(headers, user_agent)
headers[ContentSecurityPolicy.ua_to_variation(user_agent)]
end
end
# These methods are mixed into controllers and delegate to the class method
# with the same name.
def use_secure_headers_override(name)
SecureHeaders.use_secure_headers_override(request, name)
end
def content_security_policy_script_nonce
SecureHeaders.content_security_policy_script_nonce(request)
end
def content_security_policy_style_nonce
SecureHeaders.content_security_policy_style_nonce(request)
end
def opt_out_of_header(header_key)
SecureHeaders.opt_out_of_header(request, header_key)
end
def append_content_security_policy_directives(additions)
SecureHeaders.append_content_security_policy_directives(request, additions)
end
def override_content_security_policy_directives(additions)
SecureHeaders.override_content_security_policy_directives(request, additions)
end
def override_x_frame_options(value)
SecureHeaders.override_x_frame_options(request, value)
end
def use_content_security_policy_named_append(name)
SecureHeaders.use_content_security_policy_named_append(request, name)
end
end
secure_headers-3.7.1/lib/tasks/ 0000755 0000041 0000041 00000000000 13156767433 016445 5 ustar www-data www-data secure_headers-3.7.1/lib/tasks/tasks.rake 0000644 0000041 0000041 00000005065 13156767433 020444 0 ustar www-data www-data INLINE_SCRIPT_REGEX = /(
```
```
Content-Security-Policy: ...
script-src 'nonce-/jRAxuLJsDXAxqhNBB7gg7h55KETtDQBXe4ZL+xIXwI=' ...;
style-src 'nonce-/jRAxuLJsDXAxqhNBB7gg7h55KETtDQBXe4ZL+xIXwI=' ...;
```
`script`/`style-nonce` can be used to whitelist inline content. To do this, call the `content_security_policy_script_nonce` or `content_security_policy_style_nonce` then set the nonce attributes on the various tags.
```erb
```
secure_headers-3.7.1/CONTRIBUTING.md 0000644 0000041 0000041 00000003526 13156767433 017011 0 ustar www-data www-data ## Contributing
[fork]: https://github.com/twitter/secureheaders/fork
[pr]: https://github.com/twitter/secureheaders/compare
[style]: https://github.com/styleguide/ruby
[code-of-conduct]: CODE_OF_CONDUCT.md
Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms.
## Submitting a pull request
0. [Fork][fork] and clone the repository
0. Configure and install the dependencies: `bundle install`
0. Make sure the tests pass on your machine: `bundle exec rspec spec`
0. Create a new branch: `git checkout -b my-branch-name`
0. Make your change, add tests, and make sure the tests still pass and that no warnings are raised
0. Push to your fork and [submit a pull request][pr]
0. Pat your self on the back and wait for your pull request to be reviewed and merged.
Here are a few things you can do that will increase the likelihood of your pull request being accepted:
- Write tests.
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
## Releasing
0. Ensure CI is green
0. Pull the latest code
0. Increment the version
0. Run `gem build darrrr.gemspec`
0. Bump the Gemfile and Gemfile.lock versions for an app which relies on this gem
0. Test behavior locally, branch deploy, whatever needs to happen
0. Run `bundle exec rake release`
## Resources
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
secure_headers-3.7.1/LICENSE 0000644 0000041 0000041 00000002065 13156767433 015562 0 ustar www-data www-data Copyright 2013, 2014, 2015, 2016, 2017 Twitter, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
secure_headers-3.7.1/.ruby-gemset 0000644 0000041 0000041 00000000016 13156767433 017013 0 ustar www-data www-data secureheaders
secure_headers-3.7.1/.github/ 0000755 0000041 0000041 00000000000 13156767433 016112 5 ustar www-data www-data secure_headers-3.7.1/.github/PULL_REQUEST_TEMPLATE.md 0000644 0000041 0000041 00000000700 13156767433 021710 0 ustar www-data www-data ## All PRs:
* [ ] Has tests
* [ ] Documentation updated
## Adding a new header
Generally, adding a new header is always OK.
* Is the header supported by any user agent? If so, which?
* What does it do?
* What are the valid values for the header?
* Where does the specification live?
## Adding a new CSP directive
* Is the directive supported by any user agent? If so, which?
* What does it do?
* What are the valid values for the directive?
secure_headers-3.7.1/.github/ISSUE_TEMPLATE.md 0000644 0000041 0000041 00000002053 13156767433 020617 0 ustar www-data www-data # Feature Requests
## Adding a new header
Generally, adding a new header is always OK.
* Is the header supported by any user agent? If so, which?
* What does it do?
* What are the valid values for the header?
* Where does the specification live?
## Adding a new CSP directive
* Is the directive supported by any user agent? If so, which?
* What does it do?
* What are the valid values for the directive?
---
# Bugs
Console errors and deprecation warnings are considered bugs that should be addressed with more precise UA sniffing. Bugs caused by incorrect or invalid UA sniffing are also bugs.
### Expected outcome
Describe what you expected to happen
1. I configure CSP to do X
1. When I inspect the response headers, the CSP should have included X
### Actual outcome
1. The generated policy did not include X
### Config
Please provide the configuration (`SecureHeaders::Configuration.default`) you are using including any overrides (`SecureHeaders::Configuration.override`).
### Generated headers
Provide a sample response containing the headers
secure_headers-3.7.1/CHANGELOG.md 0000644 0000041 0000041 00000052144 13156767433 016371 0 ustar www-data www-data ## 3.7.1
Fix support for the sandbox attribute of CSP. `true` and `[]` represent the maximally restricted policy (`sandbox;`) and validate other values.
## 3.7.0
Adds support for the `Expect-CT` header (@jacobbednarz: https://github.com/twitter/secureheaders/pull/322)
## 3.6.7
Actually set manifest-src when configured. https://github.com/twitter/secureheaders/pull/339 Thanks @carlosantoniodasilva!
## 3.6.6
wat?
## 3.6.5
Update clear-site-data header to use current format specified by the specification.
## 3.6.4
Fix case where mixing frame-src/child-src dynamically would behave in unexpected ways: https://github.com/twitter/secureheaders/pull/325
## 3.6.3
Remove deprecation warning when setting `frame-src`. It is no longer deprecated.
## 3.6.2
Now that Safari 10 supports nonces and it appears to work, enable the nonce feature for safari.
## 3.6.1
Improved memory use via minor improvements clever hacks that are sadly needed.
Thanks @carlosantoniodasilva!
## 3.6.0
Add support for the clear-site-data header
## 3.5.1
* Fix bug that can occur when useragent library version is older, resulting in a nil version sometimes.
* Add constant for `strict-dynamic`
## 3.5.0
This release adds support for setting two CSP headers (enforced/report-only) and management around them.
## 3.4.1 Named Appends
### Small bugfix
If your CSP did not define a script/style-src and you tried to use a script/style nonce, the nonce would be added to the page but it would not be added to the CSP. A workaround is to define a script/style src but now it should add the missing directive (and populate it with the default-src).
### Named Appends
Named Appends are blocks of code that can be reused and composed during requests. e.g. If a certain partial is rendered conditionally, and the csp needs to be adjusted for that partial, you can create a named append for that situation. The value returned by the block will be passed into `append_content_security_policy_directives`. The current request object is passed as an argument to the block for even more flexibility.
```ruby
def show
if include_widget?
@widget = widget.render
use_content_security_policy_named_append(:widget_partial)
end
end
SecureHeaders::Configuration.named_append(:widget_partial) do |request|
if request.controller_instance.current_user.in_test_bucket?
SecureHeaders.override_x_frame_options(request, "DENY")
{ child_src: %w(beta.thirdpartyhost.com) }
else
{ child_src: %w(thirdpartyhost.com) }
end
end
```
You can use as many named appends as you would like per request, but be careful because order of inclusion matters. Consider the following:
```ruby
SecureHeader::Configuration.default do |config|
config.csp = { default_src: %w('self')}
end
SecureHeaders::Configuration.named_append(:A) do |request|
{ default_src: %w(myhost.com) }
end
SecureHeaders::Configuration.named_append(:B) do |request|
{ script_src: %w('unsafe-eval') }
end
```
The following code will produce different policies due to the way policies are normalized (e.g. providing a previously undefined directive that inherits from `default-src`, removing host source values when `*` is provided. Removing `'none'` when additional values are present, etc.):
```ruby
def index
use_content_security_policy_named_append(:A)
use_content_security_policy_named_append(:B)
# produces default-src 'self' myhost.com; script-src 'self' myhost.com 'unsafe-eval';
end
def show
use_content_security_policy_named_append(:B)
use_content_security_policy_named_append(:A)
# produces default-src 'self' myhost.com; script-src 'self' 'unsafe-eval';
end
```
## 3.4.0 the frame-src/child-src transition for Firefox.
Handle the `child-src`/`frame-src` transition semi-intelligently across versions. I think the code best descibes the behavior here:
```ruby
if supported_directives.include?(:child_src)
@config[:child_src] = @config[:child_src] || @config[:frame_src]
else
@config[:frame_src] = @config[:frame_src] || @config[:child_src]
end
```
Also, @koenpunt noticed that we were [loading view helpers](https://github.com/twitter/secureheaders/pull/272) in a way that Rails 5 did not like.
## 3.3.2 minor fix to silence warnings when using rake
[@dankohn](https://github.com/twitter/secureheaders/issues/257) was seeing "already initialized" errors in his output. This change conditionally defines the constants.
## 3.3.1 bugfix for boolean CSP directives
[@stefansundin](https://github.com/twitter/secureheaders/pull/253) noticed that supplying `false` to "boolean" CSP directives (e.g. `upgrade-insecure-requests` and `block-all-mixed-content`) would still include the value.
## 3.3.0 referrer-policy support
While not officially part of the spec and not implemented anywhere, support for the experimental [`referrer-policy` header](https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-header) was [preemptively added](https://github.com/twitter/secureheaders/pull/249).
Additionally, two minor enhancements were added this version:
1. [Warn when the HPKP report host is the same as the current host](https://github.com/twitter/secureheaders/pull/246). By definition any generated reports would be reporting to a known compromised connection.
1. [Filter unsupported CSP directives when using Edge](https://github.com/twitter/secureheaders/pull/247). Previously, this was causing many warnings in the developer console.
## 3.2.0 Cookie settings and CSP hash sources
### Cookies
SecureHeaders supports `Secure`, `HttpOnly` and [`SameSite`](https://tools.ietf.org/html/draft-west-first-party-cookies-07) cookies. These can be defined in the form of a boolean, or as a Hash for more refined configuration.
__Note__: Regardless of the configuration specified, Secure cookies are only enabled for HTTPS requests.
#### Boolean-based configuration
Boolean-based configuration is intended to globally enable or disable a specific cookie attribute.
```ruby
config.cookies = {
secure: true, # mark all cookies as Secure
httponly: false, # do not mark any cookies as HttpOnly
}
```
#### Hash-based configuration
Hash-based configuration allows for fine-grained control.
```ruby
config.cookies = {
secure: { except: ['_guest'] }, # mark all but the `_guest` cookie as Secure
httponly: { only: ['_rails_session'] }, # only mark the `_rails_session` cookie as HttpOnly
}
```
#### SameSite cookie configuration
SameSite cookies permit either `Strict` or `Lax` enforcement mode options.
```ruby
config.cookies = {
samesite: {
strict: true # mark all cookies as SameSite=Strict
}
}
```
`Strict` and `Lax` enforcement modes can also be specified using a Hash.
```ruby
config.cookies = {
samesite: {
strict: { only: ['_rails_session'] },
lax: { only: ['_guest'] }
}
}
```
#### Hash
`script`/`style-src` hashes can be used to whitelist inline content that is static. This has the benefit of allowing inline content without opening up the possibility of dynamic javascript like you would with a `nonce`.
You can add hash sources directly to your policy :
```ruby
::SecureHeaders::Configuration.default do |config|
config.csp = {
default_src: %w('self')
# this is a made up value but browsers will show the expected hash in the console.
script_src: %w(sha256-123456)
}
end
```
You can also use the automated inline script detection/collection/computation of hash source values in your app.
```bash
rake secure_headers:generate_hashes
```
This will generate a file (`config/secure_headers_generated_hashes.yml` by default, you can override by setting `ENV["secure_headers_generated_hashes_file"]`) containing a mapping of file names with the array of hash values found on that page. When ActionView renders a given file, we check if there are any known hashes for that given file. If so, they are added as values to the header.
```yaml
---
scripts:
app/views/asdfs/index.html.erb:
- "'sha256-yktKiAsZWmc8WpOyhnmhQoDf9G2dAZvuBBC+V0LGQhg='"
styles:
app/views/asdfs/index.html.erb:
- "'sha256-SLp6LO3rrKDJwsG9uJUxZapb4Wp2Zhj6Bu3l+d9rnAY='"
- "'sha256-HSGHqlRoKmHAGTAJ2Rq0piXX4CnEbOl1ArNd6ejp2TE='"
```
##### Helpers
**This will not compute dynamic hashes** by design. The output of both helpers will be a plain `script`/`style` tag without modification and the known hashes for a given file will be added to `script-src`/`style-src` when `hashed_javascript_tag` and `hashed_style_tag` are used. You can use `raise_error_on_unrecognized_hash = true` to be extra paranoid that you have precomputed hash values for all of your inline content. By default, this will raise an error in non-production environments.
```erb
<%= hashed_style_tag do %>
body {
background-color: black;
}
<% end %>
<%= hashed_style_tag do %>
body {
font-size: 30px;
font-color: green;
}
<% end %>
<%= hashed_javascript_tag do %>
console.log(1)
<% end %>
```
```
Content-Security-Policy: ...
script-src 'sha256-yktKiAsZWmc8WpOyhnmhQoDf9G2dAZvuBBC+V0LGQhg=' ... ;
style-src 'sha256-SLp6LO3rrKDJwsG9uJUxZapb4Wp2Zhj6Bu3l+d9rnAY=' 'sha256-HSGHqlRoKmHAGTAJ2Rq0piXX4CnEbOl1ArNd6ejp2TE=' ...;
```
## 3.1.2 Bug fix for regression
See https://github.com/twitter/secureheaders/pull/239
This meant that when header caches were regenerated upon calling `SecureHeaders.override(:name)` and using it with `use_secure_headers_override` would result in default values for anything other than CSP/HPKP.
## 3.1.1 Bug fix for regression
See https://github.com/twitter/secureheaders/pull/235
`idempotent_additions?` would return false when comparing `OPT_OUT` with `OPT_OUT`, causing `header_hash_for` to return a header cache with `{ nil => nil }` which cause the middleware to blow up when `{ nil => nil }` was merged into the rack header hash.
This is a regression in 3.1.0 only.
Now it returns true. I've added a test case to ensure that `header_hash_for` will never return such an element.
## 3.1.0 Adding secure cookie support
New feature: marking all cookies as secure. Added by @jmera in https://github.com/twitter/secureheaders/pull/231. In the future, we'll probably add the ability to whitelist individual cookies that should not be marked secure. PRs welcome.
Internal refactoring: In https://github.com/twitter/secureheaders/pull/232, we changed the way dynamic CSP is handled internally. The biggest benefit is that highly dynamic policies (which can happen with multiple `append/override` calls per request) are handled better:
1. Only the CSP header cache is busted when using a dynamic policy. All other headers are preserved and don't need to be generated. Dynamic X-Frame-Options changes modify the cache directly.
1. Idempotency checks for policy modifications are deferred until the end of the request lifecycle and only happen once, instead of per `append/override` call. The idempotency check itself is fairly expensive itself.
1. CSP header string is produced at most once per request.
## 3.0.3
Bug fix for handling policy merges where appending a non-default source value (report-uri, plugin-types, frame-ancestors, base-uri, and form-action) would be combined with the default-src value. Appending a directive that doesn't exist in the current policy combines the new value with `default-src` to mimic the actual behavior of the addition. However, this does not make sense for non-default-src values (a.k.a. "fetch directives") and can lead to unexpected behavior like a `report-uri` value of `*`. Previously, this config:
```
{
default_src => %w(*)
}
```
When appending:
```
{
report_uri => %w(https://report-uri.io/asdf)
}
```
Would result in `default-src *; report-uri *` which doesn't make any sense at all.
## 3.0.2
Bug fix for handling CSP configs that supply a frozen hash. If a directive value is `nil`, then appending to a config with a frozen hash would cause an error since we're trying to modify a frozen hash. See https://github.com/twitter/secureheaders/pull/223.
## 3.0.1
Adds `upgrade-insecure-requests` support for requests from Firefox and Chrome (and Opera). See [the spec](https://www.w3.org/TR/upgrade-insecure-requests/) for details.
## 3.0.0
secure_headers 3.0.0 is a near-complete, not-entirely-backward-compatible rewrite. Please see the [upgrade guide](https://github.com/twitter/secureheaders/blob/master/upgrading-to-3-0.md) for an in-depth explanation of the changes and the suggested upgrade path.
## 2.5.1 - 2016-02-16 18:11:11 UTC - Remove noisy deprecation warning
See https://github.com/twitter/secureheaders/issues/203 and https://github.com/twitter/secureheaders/commit/cfad0e52285353b88e46fe384e7cd60bf2a01735
>> Upon upgrading to secure_headers 2.5.0, I get a flood of these deprecations when running my tests:
> [DEPRECATION] secure_header_options_for will not be supported in secure_headers
/cc @bquorning
## 2.5.0 - 2016-01-06 22:11:02 UTC - 2.x deprecation warning release
This release contains deprecation warnings for those wishing to upgrade to the 3.x series. With this release, fixing all deprecation warnings will make your configuration compatible when you decide to upgrade to the soon-to-be-released 3.x series (currently in pre-release stage).
No changes to functionality should be observed unless you were using procs as CSP config values.
## 2.4.4 - 2015-12-03 23:29:42 UTC - Bug fix release
If you use the `header_hash` method for setting your headers in middleware and you opted out of a header (via setting the value to `false`), you would run into an exception as described in https://github.com/twitter/secureheaders/pull/193
```
NoMethodError:
undefined method `name' for nil:NilClass
# ./lib/secure_headers.rb:63:in `block in header_hash'
# ./lib/secure_headers.rb:54:in `each'
# ./lib/secure_headers.rb:54:in `inject'
# ./lib/secure_headers.rb:54:in `header_hash'
```
## 2.4.3 - 2015-10-23 18:35:43 UTC - Performance improvement
@igrep reported an anti-patter in use regarding [UserAgentParser](https://github.com/ua-parser/uap-ruby). This caused UserAgentParser to reload it's entire configuration set *twice** per request. Moving this to a cached constant prevents the constant reinstantiation and will improve performance.
https://github.com/twitter/secureheaders/issues/187
## 2.4.2 - 2015-10-20 20:22:08 UTC - Bug fix release
A nasty regression meant that many CSP configuration values were "reset" after the first request, one of these being the "enforce" flag. See https://github.com/twitter/secureheaders/pull/184 for the full list of fields that were affected. Thanks to @spdawson for reporting this https://github.com/twitter/secureheaders/issues/183
## 2.4.1 - 2015-10-14 22:57:41 UTC - More UA sniffing
This release may change the output of headers based on per browser support. Unsupported directives will be omitted based on the user agent per request. See https://github.com/twitter/secureheaders/pull/179
p.s. this will likely be the last non-bugfix release for the 2.x line. 3.x will be a major change. Sneak preview: https://github.com/twitter/secureheaders/pull/181
## 2.4.0 - 2015-10-01 23:05:38 UTC - Some internal changes affecting behavior, but not functionality
If you leveraged `secure_headers` automatic filling of empty directives, the header value will change but it should not affect how the browser applies the policy. The content of CSP reports may change if you do not update your policy.
before
===
```ruby
config.csp = {
:default_src => "'self'"
}
```
would produce `default-src 'self'; connect-src 'self'; frame-src 'self' ... etc.`
after
===
```ruby
config.csp = {
:default_src => "'self'"
}
```
will produce `default-src 'self'`
The reason for this is that a `default-src` violation was basically impossible to handle. Chrome sends an `effective-directive` which helps indicate what kind of violation occurred even if it fell back to `default-src`. This is part of the [CSP Level 2 spec](http://www.w3.org/TR/CSP2/#violation-report-effective-directive) so hopefully other browsers will implement this soon.
Workaround
===
Just set the values yourself, but really a `default-src` of anything other than `'none'` implies the policy can be tightened dramatically. "ZOMG don't you work for github and doesn't github send a `default-src` of `*`???" Yes, this is true. I disagree with this but at the same time, github defines every single known directive that a browser supports so `default-src` will only apply if a new directive is introduced, and we'd rather fail open. For now.
```ruby
config.csp = {
:default_src => "'self'",
:connect_src => "'self'",
:frame_src => "'self'"
... etc.
}
```
Besides, relying on `default-src` is often not what you want and encourages an overly permissive policy. I've seen it. Seriously. `default-src 'unsafe-inline' 'unsafe-eval' https: http:;` That's terrible.
## 2.3.0 - 2015-09-30 19:43:09 UTC - Add header_hash feature for use in middleware.
See https://github.com/twitter/secureheaders/issues/167 and https://github.com/twitter/secureheaders/pull/168
tl;dr is that there is a class method `SecureHeaders::header_hash` that will return a hash of header name => value pairs useful for merging with the rack header hash in middleware.
## 2.2.4 - 2015-08-26 23:31:37 UTC - Print deprecation warning for 1.8.7 users
As discussed in https://github.com/twitter/secureheaders/issues/154
## 2.2.3 - 2015-08-14 20:26:12 UTC - Adds ability to opt-out of automatically adding data: sources to img-src
See https://github.com/twitter/secureheaders/pull/161
## 2.2.2 - 2015-07-02 21:18:38 UTC - Another option for config granularity.
See https://github.com/twitter/secureheaders/pull/147
Allows you to override a controller method that returns a config in the context of the executing action.
## 2.2.1 - 2015-06-24 21:01:57 UTC - When using nonces, do not include the nonce for safari / IE
See https://github.com/twitter/secureheaders/pull/150
Safari will generate a warning that it doesn't support nonces. Safari will fall back to the `unsafe-inline`. Things will still work, but an ugly message is printed to the console.
This opts out safari and IE users from the inline script protection. I haven't verified any IE behavior yet, so I'm just assuming it doesn't work.
## 2.2.0 - 2015-06-18 22:01:23 UTC - Pass controller reference to callable config value expressions.
https://github.com/twitter/secureheaders/pull/148
Facilitates better per-request config:
`:enforce => lambda { |controller| controller.current_user.beta_testing? }`
**NOTE** if you used `lambda` config values, this will raise an exception until you add the controller reference:
bad:
`lambda { true }`
good:
`lambda { |controller| true }`
`proc { true }`
`proc { |controller| true }`
## v2.1.0 - 2015-05-07 18:34:56 UTC - Add hpkp support
Includes https://github.com/twitter/secureheaders/pull/143 (which is really just https://github.com/twitter/secureheaders/pull/132) from @thirstscolr
## v2.0.2 - 2015-05-05 03:09:44 UTC - Add report_uri constant value
Just a small change that adds a constant that was missing as reported in https://github.com/twitter/secureheaders/issues/141
## v2.0.1 - 2015-03-20 18:46:47 UTC - View Helpers Fixed
Fixes an issue where view helpers (for nonces, hashes, etc) weren't available in views.
## 2.0.0 - 2015-01-23 20:23:56 UTC - 2.0
This release contains support for more csp level 2 features such as the new directives, the script hash integration, and more.
It also sets a new header by default: `X-Permitted-Cross-Domain-Policies`
Support for hpkp is not included in this release as the implementations are still very unstable.
:rocket:
## v.2.0.0.pre2 - 2014-12-06 01:55:42 UTC - Adds X-Permitted-Cross-Domain-Policies support by default
The only change between this and the first pre release is that the X-Permitted-Cross-Domain-Policies support is included.
## v1.4.0 - 2014-12-06 01:54:48 UTC - Deprecate features in preparation for 2.0
This removes the forwarder and "experimental" feature. The forwarder wasn't well maintained and created a lot of headaches. Also, it was using an outdated certificate pack for compatibility. That's bad. The experimental feature wasn't really used and it complicated the codebase a lot. It's also a questionably useful API that is very confusing.
## v2.0.0.pre - 2014-11-14 00:54:07 UTC - 2.0.0.pre - CSP level 2 support
This release is intended to be ready for CSP level 2. Mainly, this means there is direct support for hash/nonce of inline content and includes many new directives (which do not inherit from default-src)
## v1.3.4 - 2014-10-13 22:05:44 UTC -
* Adds X-Download-Options support
* Adds support for X-XSS-Protection reporting
* Defers loading of rails engine for faster boot times
## v1.3.3 - 2014-08-15 02:30:24 UTC - hsts preload confirmation value support
@agl just made a new option for HSTS representing confirmation that a site wants to be included in a browser's preload list (https://hstspreload.appspot.com).
This just adds a new 'preload' option to the HSTS settings to specify that option.
## v1.3.2 - 2014-08-14 00:01:32 UTC - Add app tagging support
Tagging Requests
It's often valuable to send extra information in the report uri that is not available in the reports themselves. Namely, "was the policy enforced" and "where did the report come from"
```ruby
{
:tag_report_uri => true,
:enforce => true,
:app_name => 'twitter',
:report_uri => 'csp_reports'
}
```
Results in
```
report-uri csp_reports?enforce=true&app_name=twitter
```
secure_headers-3.7.1/README.md 0000644 0000041 0000041 00000021365 13156767433 016040 0 ustar www-data www-data # Secure Headers [](http://travis-ci.org/twitter/secureheaders) [](https://codeclimate.com/github/twitter/secureheaders) [](https://coveralls.io/r/twitter/secureheaders)
**The 3.x branch was recently merged**. See the [upgrading to 3.x doc](upgrading-to-3-0.md) for instructions on how to upgrade including the differences and benefits of using the 3.x branch.
**The [2.x branch](https://github.com/twitter/secureheaders/tree/2.x) will be maintained**. The documentation below only applies to the 3.x branch. See the 2.x [README](https://github.com/twitter/secureheaders/blob/2.x/README.md) for the old way of doing things.
The gem will automatically apply several headers that are related to security. This includes:
- Content Security Policy (CSP) - Helps detect/prevent XSS, mixed-content, and other classes of attack. [CSP 2 Specification](http://www.w3.org/TR/CSP2/)
- https://csp.withgoogle.com
- https://csp.withgoogle.com/docs/strict-csp.html
- https://csp-evaluator.withgoogle.com
- HTTP Strict Transport Security (HSTS) - Ensures the browser never visits the http version of a website. Protects from SSLStrip/Firesheep attacks. [HSTS Specification](https://tools.ietf.org/html/rfc6797)
- X-Frame-Options (XFO) - Prevents your content from being framed and potentially clickjacked. [X-Frame-Options Specification](https://tools.ietf.org/html/rfc7034)
- X-XSS-Protection - [Cross site scripting heuristic filter for IE/Chrome](https://msdn.microsoft.com/en-us/library/dd565647\(v=vs.85\).aspx)
- X-Content-Type-Options - [Prevent content type sniffing](https://msdn.microsoft.com/library/gg622941\(v=vs.85\).aspx)
- X-Download-Options - [Prevent file downloads opening](https://msdn.microsoft.com/library/jj542450(v=vs.85).aspx)
- X-Permitted-Cross-Domain-Policies - [Restrict Adobe Flash Player's access to data](https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html)
- Referrer-Policy - [Referrer Policy draft](https://w3c.github.io/webappsec-referrer-policy/)
- Public Key Pinning - Pin certificate fingerprints in the browser to prevent man-in-the-middle attacks due to compromised Certificate Authorities. [Public Key Pinning Specification](https://tools.ietf.org/html/rfc7469)
- Expect-CT - Only use certificates that are present in the certificate transparency logs. [Expect-CT draft specification](https://datatracker.ietf.org/doc/draft-stark-expect-ct/).
- Clear-Site-Data - Clearing browser data for origin. [Clear-Site-Data specification](https://w3c.github.io/webappsec-clear-site-data/).
It can also mark all http cookies with the Secure, HttpOnly and SameSite attributes (when configured to do so).
`secure_headers` is a library with a global config, per request overrides, and rack middleware that enables you customize your application settings.
## Documentation
- [Named overrides and appends](docs/named_overrides_and_appends.md)
- [Per action configuration](docs/per_action_configuration.md)
- [Cookies](docs/cookies.md)
- [HPKP](docs/HPKP.md)
- [Hashes](docs/hashes.md)
- [Sinatra Config](docs/sinatra.md)
## Getting Started
### Rails 3+
For Rails 3+ applications, `secure_headers` has a `railtie` that should automatically include the middleware. If for some reason the middleware is not being included follow the instructions for Rails 2.
### Rails 2
For Rails 2 or non-rails applications, an explicit statement is required to use the middleware component.
```ruby
use SecureHeaders::Middleware
```
## Configuration
If you do not supply a `default` configuration, exceptions will be raised. If you would like to use a default configuration (which is fairly locked down), just call `SecureHeaders::Configuration.default` without any arguments or block.
All `nil` values will fallback to their default values. `SecureHeaders::OPT_OUT` will disable the header entirely.
```ruby
SecureHeaders::Configuration.default do |config|
config.cookies = {
secure: true, # mark all cookies as "Secure"
httponly: true, # mark all cookies as "HttpOnly"
samesite: {
lax: true # mark all cookies as SameSite=lax
}
}
# Add "; preload" and submit the site to hstspreload.org for best protection.
config.hsts = "max-age=#{20.years.to_i}; includeSubdomains"
config.x_frame_options = "DENY"
config.x_content_type_options = "nosniff"
config.x_xss_protection = "1; mode=block"
config.x_download_options = "noopen"
config.x_permitted_cross_domain_policies = "none"
config.referrer_policy = "origin-when-cross-origin"
config.clear_site_data = [
"cache",
"cookies",
"storage",
"executionContexts"
]
config.expect_certificate_transparency = {
enforce: false,
max_age: 1.day.to_i,
report_uri: "https://report-uri.io/example-ct"
}
config.csp = {
# "meta" values. these will shaped the header, but the values are not included in the header.
# report_only: true, # default: false [DEPRECATED from 3.5.0: instead, configure csp_report_only]
preserve_schemes: true, # default: false. Schemes are removed from host sources to save bytes and discourage mixed content.
# directive values: these values will directly translate into source directives
default_src: %w(https: 'self'),
base_uri: %w('self'),
block_all_mixed_content: true, # see http://www.w3.org/TR/mixed-content/
child_src: %w('self'), # if child-src isn't supported, the value for frame-src will be set.
connect_src: %w(wss:),
font_src: %w('self' data:),
form_action: %w('self' github.com),
frame_ancestors: %w('none'),
img_src: %w(mycdn.com data:),
manifest_src: %w('self'),
media_src: %w(utoob.com),
object_src: %w('self'),
plugin_types: %w(application/x-shockwave-flash),
script_src: %w('self'),
style_src: %w('unsafe-inline'),
upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
report_uri: %w(https://report-uri.io/example-csp)
}
# This is available only from 3.5.0; use the `report_only: true` setting for 3.4.1 and below.
config.csp_report_only = config.csp.merge({
img_src: %w(somewhereelse.com),
report_uri: %w(https://report-uri.io/example-csp-report-only)
})
config.hpkp = {
report_only: false,
max_age: 60.days.to_i,
include_subdomains: true,
report_uri: "https://report-uri.io/example-hpkp",
pins: [
{sha256: "abc"},
{sha256: "123"}
]
}
end
```
## Default values
All headers except for PublicKeyPins and ClearSiteData have a default value. The default set of headers is:
```
Content-Security-Policy: default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'
Strict-Transport-Security: max-age=631138519
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Frame-Options: sameorigin
X-Permitted-Cross-Domain-Policies: none
X-Xss-Protection: 1; mode=block
```
### Default CSP
By default, the above CSP will be applied to all requests. If you **only** want to set a Report-Only header, opt-out of the default enforced header for clarity. The configuration will assume that if you only supply `csp_report_only` that you intended to opt-out of `csp` but that's for the sake of backwards compatibility and it will be removed in the future.
```ruby
Configuration.default do |config|
config.csp = SecureHeaders::OPT_OUT # If this line is omitted, we will assume you meant to opt out.
config.csp_report_only = {
default_src: %w('self')
}
end
```
## Similar libraries
* Rack [rack-secure_headers](https://github.com/frodsan/rack-secure_headers)
* Node.js (express) [helmet](https://github.com/helmetjs/helmet) and [hood](https://github.com/seanmonstar/hood)
* Node.js (hapi) [blankie](https://github.com/nlf/blankie)
* J2EE Servlet >= 3.0 [headlines](https://github.com/sourceclear/headlines)
* ASP.NET - [NWebsec](https://github.com/NWebsec/NWebsec/wiki)
* Python - [django-csp](https://github.com/mozilla/django-csp) + [commonware](https://github.com/jsocol/commonware/); [django-security](https://github.com/sdelements/django-security)
* Go - [secureheader](https://github.com/kr/secureheader)
* Elixir [secure_headers](https://github.com/anotherhale/secure_headers)
* Dropwizard [dropwizard-web-security](https://github.com/palantir/dropwizard-web-security)
* Ember.js [ember-cli-content-security-policy](https://github.com/rwjblue/ember-cli-content-security-policy/)
* PHP [secure-headers](https://github.com/BePsvPT/secure-headers)
## License
Copyright 2013-2014 Twitter, Inc and other contributors.
Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
secure_headers-3.7.1/Guardfile 0000644 0000041 0000041 00000000564 13156767433 016404 0 ustar www-data www-data guard :rspec, cmd: "bundle exec rspec", all_on_start: true, all_after_pass: true do
require "guard/rspec/dsl"
dsl = Guard::RSpec::Dsl.new(self)
# RSpec files
rspec = dsl.rspec
watch(rspec.spec_helper) { rspec.spec_dir }
watch(rspec.spec_support) { rspec.spec_dir }
watch(rspec.spec_files)
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
end