-
Notifications
You must be signed in to change notification settings - Fork 253
Kyfast/report to directive #557
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature-7.2
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -88,9 +88,50 @@ SecureHeaders::Configuration.default do |config| | |||||
| img_src: %w(somewhereelse.com), | ||||||
| report_uri: %w(https://report-uri.io/example-csp-report-only) | ||||||
| }) | ||||||
|
|
||||||
| # Optional: Use the modern report-to directive (with Reporting-Endpoints header) | ||||||
| config.csp = config.csp.merge({ | ||||||
| report_to: "csp-endpoint" | ||||||
| }) | ||||||
|
|
||||||
| # When using report-to, configure the reporting endpoints header | ||||||
| config.reporting_endpoints = { | ||||||
| "csp-endpoint": "https://report-uri.io/example-csp", | ||||||
| "csp-report-only": "https://report-uri.io/example-csp-report-only" | ||||||
| } | ||||||
| end | ||||||
| ``` | ||||||
|
|
||||||
| ### CSP Reporting | ||||||
|
|
||||||
| SecureHeaders supports both the legacy `report-uri` and the modern `report-to` directives for CSP violation reporting: | ||||||
|
|
||||||
| #### report-uri (Legacy) | ||||||
| The `report-uri` directive sends violations to a URL endpoint. It's widely supported but limited to POST requests with JSON payloads. | ||||||
|
|
||||||
| ```ruby | ||||||
| config.csp = { | ||||||
| default_src: %w('self'), | ||||||
| report_uri: %w(https://example.com/csp-report) | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| #### report-to (Modern) | ||||||
| The `report-to` directive specifies a named reporting endpoint defined in the `Reporting-Endpoints` header. This enables more flexible reporting through the HTTP Reporting API standard. | ||||||
|
|
||||||
| ```ruby | ||||||
| config.csp = { | ||||||
| default_src: %w('self'), | ||||||
| report_to: "csp-endpoint" | ||||||
| } | ||||||
|
|
||||||
| config.reporting_endpoints = { | ||||||
| "csp-endpoint": "https://example.com/reports" | ||||||
|
||||||
| "csp-endpoint": "https://example.com/reports" | |
| "csp-endpoint" => "https://example.com/reports" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # frozen_string_literal: true | ||
| module SecureHeaders | ||
| class ReportingEndpointsConfigError < StandardError; end | ||
| class ReportingEndpoints | ||
| HEADER_NAME = "reporting-endpoints".freeze | ||
|
|
||
| class << self | ||
| # Public: generate a Reporting-Endpoints header. | ||
| # | ||
| # The config should be a Hash of endpoint names to URLs. | ||
| # Example: { "csp-endpoint" => "https://example.com/reports" } | ||
| # | ||
| # Returns nil if config is OPT_OUT or nil, or a header name and | ||
| # formatted header value based on the config. | ||
| def make_header(config = nil) | ||
| return if config.nil? || config == OPT_OUT | ||
| validate_config!(config) | ||
| [HEADER_NAME, format_endpoints(config)] | ||
| end | ||
|
|
||
| def validate_config!(config) | ||
| case config | ||
| when nil, OPT_OUT | ||
| # valid | ||
| when Hash | ||
| config.each_pair do |name, url| | ||
| unless name.is_a?(String) && !name.empty? | ||
| raise ReportingEndpointsConfigError.new("Endpoint name must be a non-empty string, got: #{name.inspect}") | ||
| end | ||
| unless url.is_a?(String) && !url.empty? | ||
| raise ReportingEndpointsConfigError.new("Endpoint URL must be a non-empty string, got: #{url.inspect}") | ||
| end | ||
| unless url.start_with?("https://") | ||
| raise ReportingEndpointsConfigError.new("Endpoint URLs must use https, got: #{url.inspect}") | ||
| end | ||
| end | ||
| else | ||
| raise TypeError.new("Must be a Hash of endpoint names to URLs. Found #{config.class}: #{config}") | ||
| end | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def format_endpoints(config) | ||
| config.map do |name, url| | ||
| %{#{name}="#{url}"} | ||
| end.join(", ") | ||
| end | ||
|
Comment on lines
+44
to
+48
|
||
| end | ||
| end | ||
| end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The README example uses symbol keys with colons at the end (Ruby 1.9+ syntax) for the
reporting_endpointsconfiguration, but the implementation expects string keys. This inconsistency could lead to configuration errors.In lines 98-101, the example shows:
But based on the
ReportingEndpoints.validate_config!method (line 27 in reporting_endpoints.rb), which checksname.is_a?(String), the implementation expects string keys like:While Ruby symbols and string keys might work interchangeably in some cases, the validation explicitly checks for String instances, and using symbol keys with the colon syntax is misleading since they're actually symbols, not strings.