added XPermittedCrossDomainPolicies header support
This commit is contained in:
Родитель
97e1751c31
Коммит
f3739695e9
|
@ -6,6 +6,7 @@ The gem will automatically apply several headers that are related to security.
|
|||
- X-Frame-Options (XFO) - Prevents your content from being framed and potentially clickjacked. [X-Frame-Options draft](https://tools.ietf.org/html/draft-ietf-websec-x-frame-options-02)
|
||||
- X-XSS-Protection - [Cross site scripting heuristic filter for IE/Chrome](http://msdn.microsoft.com/en-us/library/dd565647\(v=vs.85\).aspx)
|
||||
- X-Content-Type-Options - [Prevent content type sniffing](http://msdn.microsoft.com/en-us/library/ie/gg622941\(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)
|
||||
|
||||
This gem has integration with Rails, but works for any Ruby code. See the sinatra example section.
|
||||
|
||||
|
@ -48,6 +49,7 @@ The following methods are going to be called, unless they are provided in a `ski
|
|||
* `:set_x_frame_options_header`
|
||||
* `:set_x_xss_protection_header`
|
||||
* `:set_x_content_type_options_header`
|
||||
* `:set_x_permitted_cross_domain_policies_header`
|
||||
|
||||
### Bonus Features
|
||||
|
||||
|
@ -65,6 +67,7 @@ This gem makes a few assumptions about how you will use some features. For exam
|
|||
config.x_frame_options = 'DENY'
|
||||
config.x_content_type_options = "nosniff"
|
||||
config.x_xss_protection = {:value => 1, :mode => 'block'}
|
||||
config.x_permitted_cross_domain_policies = 'none'
|
||||
config.csp = {
|
||||
:default_src => "https: self",
|
||||
:frame_src => "https: http:.twimg.com http://itunes.apple.com",
|
||||
|
@ -106,6 +109,7 @@ This configuration will likely work for most applications without modification.
|
|||
:x_frame_options => {:value => 'SAMEORIGIN'}
|
||||
:x_xss_protection => {:value => 1, :mode => 'block'} # set the :mode option to false to use "warning only" mode
|
||||
:x_content_type_options => {:value => 'nosniff'}
|
||||
:x_permitted_cross_domain_policies => {:value => 'none'}
|
||||
```
|
||||
|
||||
### Content Security Policy (CSP)
|
||||
|
@ -335,6 +339,7 @@ require 'secure_headers'
|
|||
config.x_frame_options = 'DENY'
|
||||
config.x_content_type_options = "nosniff"
|
||||
config.x_xss_protection = {:value => 1, :mode => false}
|
||||
config.x_permitted_cross_domain_policies = 'none'
|
||||
config.csp = {
|
||||
:default_src => "https: inline eval",
|
||||
:report_uri => '//example.com/uri-directive',
|
||||
|
@ -390,6 +395,7 @@ def before_load
|
|||
config.x_frame_options = 'DENY'
|
||||
config.x_content_type_options = "nosniff"
|
||||
config.x_xss_protection = {:value => '1', :mode => false}
|
||||
config.x_permitted_cross_domain_policies = 'none'
|
||||
config.csp = {
|
||||
:default_src => "https: inline eval",
|
||||
:report_uri => '//example.com/uri-directive',
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
config.x_frame_options = 'SAMEORIGIN'
|
||||
config.x_content_type_options = "nosniff"
|
||||
config.x_xss_protection = {:value => 1, :mode => 'block'}
|
||||
config.x_permitted_cross_domain_policies = 'none'
|
||||
csp = {
|
||||
:default_src => "self",
|
||||
:script_src => "self nonce",
|
||||
|
|
|
@ -67,6 +67,11 @@ describe OtherThingsController, :type => :controller do
|
|||
expect(@env['X-Content-Type-Options']).to eq("nosniff")
|
||||
end
|
||||
|
||||
it "sets the X-Permitted-Cross-Domain-Policies" do
|
||||
get '/'
|
||||
expect(@env['X-Permitted-Cross-Domain-Policies']).to eq("none")
|
||||
end
|
||||
|
||||
context "using IE" do
|
||||
it "sets the X-Content-Type-Options header" do
|
||||
@env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
|
||||
|
|
|
@ -38,6 +38,11 @@ describe ThingsController, :type => :controller do
|
|||
expect(response.headers['X-Content-Type-Options']).to eq("nosniff")
|
||||
end
|
||||
|
||||
it "sets the X-Permitted-Cross-Domain-Policies" do
|
||||
get :index
|
||||
expect(response.headers['X-Permitted-Cross-Domain-Policies']).to eq("none")
|
||||
end
|
||||
|
||||
context "using IE" do
|
||||
it "sets the X-Content-Type-Options header" do
|
||||
request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
|
||||
|
|
|
@ -34,6 +34,11 @@ describe OtherThingsController, :type => :controller do
|
|||
expect(response.headers['X-Content-Type-Options']).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
|
||||
end
|
||||
|
||||
it "sets the X-Permitted-Cross-Domain-Policies" do
|
||||
get :index
|
||||
expect(response.headers['X-Permitted-Cross-Domain-Policies']).to eq("none")
|
||||
end
|
||||
|
||||
context "using IE" do
|
||||
it "sets the X-Content-Type-Options header" do
|
||||
request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
|
||||
|
|
|
@ -38,6 +38,11 @@ describe ThingsController, :type => :controller do
|
|||
expect(response.headers['X-Content-Type-Options']).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
|
||||
end
|
||||
|
||||
it "sets the X-Permitted-Cross-Domain-Policies" do
|
||||
get :index
|
||||
expect(response.headers['X-Permitted-Cross-Domain-Policies']).to eq("none")
|
||||
end
|
||||
|
||||
context "using IE" do
|
||||
it "sets the X-Content-Type-Options header" do
|
||||
request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
|
||||
|
|
|
@ -5,7 +5,8 @@ module SecureHeaders
|
|||
module Configuration
|
||||
class << self
|
||||
attr_accessor :hsts, :x_frame_options, :x_content_type_options,
|
||||
:x_xss_protection, :csp, :x_download_options, :script_hashes
|
||||
:x_xss_protection, :csp, :x_download_options, :script_hashes,
|
||||
:x_permitted_cross_domain_policies
|
||||
|
||||
def configure &block
|
||||
instance_eval &block
|
||||
|
@ -46,6 +47,7 @@ module SecureHeaders
|
|||
before_filter :set_x_xss_protection_header
|
||||
before_filter :set_x_content_type_options_header
|
||||
before_filter :set_x_download_options_header
|
||||
before_filter :set_x_permitted_cross_domain_policies_header
|
||||
end
|
||||
|
||||
# we can't use ||= because I'm overloading false => disable, nil => default
|
||||
|
@ -63,6 +65,7 @@ module SecureHeaders
|
|||
set_x_xss_protection_header(options[:x_xss_protection])
|
||||
set_x_content_type_options_header(options[:x_content_type_options])
|
||||
set_x_download_options_header(options[:x_download_options])
|
||||
set_x_permitted_cross_domain_policies_header(options[:x_permitted_cross_domain_policies])
|
||||
end
|
||||
|
||||
# set_csp_header - uses the request accessor and SecureHeader::Configuration settings
|
||||
|
@ -137,6 +140,10 @@ module SecureHeaders
|
|||
set_a_header(:x_download_options, XDownloadOptions, options)
|
||||
end
|
||||
|
||||
def set_x_permitted_cross_domain_policies_header(options=self.class.secure_headers_options[:x_permitted_cross_domain_policies])
|
||||
set_a_header(:x_permitted_cross_domain_policies, XPermittedCrossDomainPolicies, options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_a_header(name, klass, options=nil)
|
||||
|
@ -167,6 +174,7 @@ 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/railtie"
|
||||
require "secure_headers/hash_helper"
|
||||
require "secure_headers/view_helper"
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
module SecureHeaders
|
||||
class XPCDPBuildError < StandardError; end
|
||||
class XPermittedCrossDomainPolicies < Header
|
||||
module Constants
|
||||
XPCDP_HEADER_NAME = "X-Permitted-Cross-Domain-Policies"
|
||||
DEFAULT_VALUE = 'none'
|
||||
VALID_POLICIES = %w(all none master-only by-content-type by-ftp-filename)
|
||||
end
|
||||
include Constants
|
||||
|
||||
def initialize(config = nil)
|
||||
@config = config
|
||||
validate_config unless @config.nil?
|
||||
end
|
||||
|
||||
def name
|
||||
XPCDP_HEADER_NAME
|
||||
end
|
||||
|
||||
def value
|
||||
case @config
|
||||
when NilClass
|
||||
DEFAULT_VALUE
|
||||
when String
|
||||
@config
|
||||
else
|
||||
@config[:value]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_config
|
||||
value = @config.is_a?(Hash) ? @config[:value] : @config
|
||||
unless VALID_POLICIES.include?(value.downcase)
|
||||
raise XPCDPBuildError.new("Value can only be one of #{VALID_POLICIES.join(', ')}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,64 @@
|
|||
module SecureHeaders
|
||||
describe XPermittedCrossDomainPolicies do
|
||||
specify { expect(XPermittedCrossDomainPolicies.new.name).to eq(XPermittedCrossDomainPolicies::Constants::XPCDP_HEADER_NAME)}
|
||||
specify { expect(XPermittedCrossDomainPolicies.new.value).to eq("none")}
|
||||
specify { expect(XPermittedCrossDomainPolicies.new('master-only').value).to eq('master-only')}
|
||||
specify { expect(XPermittedCrossDomainPolicies.new(:value => 'master-only').value).to eq('master-only') }
|
||||
|
||||
context "valid configuration values" do
|
||||
it "accepts 'all'" do
|
||||
expect {
|
||||
XPermittedCrossDomainPolicies.new("all")
|
||||
}.not_to raise_error
|
||||
|
||||
expect {
|
||||
XPermittedCrossDomainPolicies.new(:value => "all")
|
||||
}.not_to raise_error
|
||||
end
|
||||
|
||||
it "accepts 'by-ftp-filename'" do
|
||||
expect {
|
||||
XPermittedCrossDomainPolicies.new("by-ftp-filename")
|
||||
}.not_to raise_error
|
||||
|
||||
expect {
|
||||
XPermittedCrossDomainPolicies.new(:value => "by-ftp-filename")
|
||||
}.not_to raise_error
|
||||
end
|
||||
|
||||
it "accepts 'by-content-type'" do
|
||||
expect {
|
||||
XPermittedCrossDomainPolicies.new("by-content-type")
|
||||
}.not_to raise_error
|
||||
|
||||
expect {
|
||||
XPermittedCrossDomainPolicies.new(:value => "by-content-type")
|
||||
}.not_to raise_error
|
||||
end
|
||||
it "accepts 'master-only'" do
|
||||
expect {
|
||||
XPermittedCrossDomainPolicies.new("master-only")
|
||||
}.not_to raise_error
|
||||
|
||||
expect {
|
||||
XPermittedCrossDomainPolicies.new(:value => "master-only")
|
||||
}.not_to raise_error
|
||||
end
|
||||
|
||||
it "accepts nil" do
|
||||
expect {
|
||||
XPermittedCrossDomainPolicies.new
|
||||
}.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'invlaid configuration values' do
|
||||
|
||||
it "doesn't accept invalid values" do
|
||||
expect {
|
||||
XPermittedCrossDomainPolicies.new("open")
|
||||
}.to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,7 +18,7 @@ describe SecureHeaders do
|
|||
allow(subject).to receive(:request).and_return(request)
|
||||
end
|
||||
|
||||
ALL_HEADERS = Hash[[:hsts, :csp, :x_frame_options, :x_content_type_options, :x_xss_protection].map{|header| [header, false]}]
|
||||
ALL_HEADERS = Hash[[:hsts, :csp, :x_frame_options, :x_content_type_options, :x_xss_protection, :x_permitted_cross_domain_policies].map{|header| [header, false]}]
|
||||
|
||||
def stub_user_agent val
|
||||
allow(request).to receive_message_chain(:env, :[]).and_return(val)
|
||||
|
@ -36,6 +36,7 @@ describe SecureHeaders do
|
|||
config.x_xss_protection = nil
|
||||
config.csp = nil
|
||||
config.x_download_options = nil
|
||||
config.x_permitted_cross_domain_policies = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -46,6 +47,7 @@ describe SecureHeaders do
|
|||
subject.set_x_content_type_options_header
|
||||
subject.set_x_xss_protection_header
|
||||
subject.set_x_download_options_header
|
||||
subject.set_x_permitted_cross_domain_policies_header
|
||||
end
|
||||
|
||||
describe "#set_header" do
|
||||
|
@ -64,7 +66,7 @@ describe SecureHeaders do
|
|||
USER_AGENTS.each do |name, useragent|
|
||||
it "sets all default headers for #{name} (smoke test)" do
|
||||
stub_user_agent(useragent)
|
||||
number_of_headers = 6
|
||||
number_of_headers = 7
|
||||
expect(subject).to receive(:set_header).exactly(number_of_headers).times # a request for a given header
|
||||
subject.set_csp_header
|
||||
subject.set_x_frame_options_header
|
||||
|
@ -72,6 +74,7 @@ describe SecureHeaders do
|
|||
subject.set_x_xss_protection_header
|
||||
subject.set_x_content_type_options_header
|
||||
subject.set_x_download_options_header
|
||||
subject.set_x_permitted_cross_domain_policies_header
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -96,6 +99,11 @@ describe SecureHeaders do
|
|||
subject.set_x_frame_options_header(false)
|
||||
end
|
||||
|
||||
it "does not set the X-Permitted-Cross-Domain-Policies header if disabled" do
|
||||
should_not_assign_header(XPCDP_HEADER_NAME)
|
||||
subject.set_x_permitted_cross_domain_policies_header(false)
|
||||
end
|
||||
|
||||
it "does not set the HSTS header if disabled" do
|
||||
should_not_assign_header(HSTS_HEADER_NAME)
|
||||
subject.set_hsts_header(false)
|
||||
|
@ -133,6 +141,7 @@ describe SecureHeaders do
|
|||
config.x_xss_protection = false
|
||||
config.csp = false
|
||||
config.x_download_options = false
|
||||
config.x_permitted_cross_domain_policies = false
|
||||
end
|
||||
expect(subject).not_to receive(:set_header)
|
||||
set_security_headers(subject)
|
||||
|
@ -249,4 +258,16 @@ describe SecureHeaders do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#set_x_permitted_cross_domain_policies_header" do
|
||||
it "sets the X-Permitted-Cross-Domain-Policies header" do
|
||||
should_assign_header(XPCDP_HEADER_NAME, SecureHeaders::XPermittedCrossDomainPolicies::Constants::DEFAULT_VALUE)
|
||||
subject.set_x_permitted_cross_domain_policies_header
|
||||
end
|
||||
|
||||
it "allows a custom X-Permitted-Cross-Domain-Policies header" do
|
||||
should_assign_header(XPCDP_HEADER_NAME, "master-only")
|
||||
subject.set_x_permitted_cross_domain_policies_header(:value => 'master-only')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,6 +13,7 @@ include ::SecureHeaders::XFrameOptions::Constants
|
|||
include ::SecureHeaders::XXssProtection::Constants
|
||||
include ::SecureHeaders::XContentTypeOptions::Constants
|
||||
include ::SecureHeaders::XDownloadOptions::Constants
|
||||
include ::SecureHeaders::XPermittedCrossDomainPolicies::Constants
|
||||
|
||||
USER_AGENTS = {
|
||||
:firefox => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1',
|
||||
|
|
Загрузка…
Ссылка в новой задаче