Skip to content

Conversation

@rei-moo rei-moo force-pushed the feature-7.2 branch 3 times, most recently from 4f5686e to 930157e Compare December 16, 2025 23:08
Do not join cookies with new like if they weren't before

fix(middleware): ensure headers are wrapped with `Rack::Headers`

Add `Rack::Headers` wrapping to middleware to
prevent header manipulation issues. Added a test
to verify cookies remain as an array when flagged
if already in array format.
obrie and others added 4 commits December 17, 2025 14:00
While this gem now uses lowercase headers, the Rails default configuration still
defines non-lowercase headers.  As a result, our Railtie will not remove those
conflicting headers.

This change ensures that we're accounting for both lowercase and non-lowercase
default headers in Rails.
CSP3 more explicitly calls this out:

> If path A consists of one character that is equal to the U+002F
> SOLIDUS character (/) and path B is empty, return "Matches".

A URL like `example.com/foo` will match a connect-src of `example.com`,
as well as `example.com/`, so having two connect-srcs listed like this
is redundant.

fix: allow URIs with schema to have trailing slashes normalised

Co-authored-by: Dusty Greif <dgreif@users.noreply.github.com>
Fix rake task file count output message
Copilot AI and others added 2 commits December 19, 2025 13:07
Co-authored-by: fletchto99 <718681+fletchto99@users.noreply.github.com>m>
Co-authored-by: fletchto99 <718681+fletchto99@users.noreply.github.com>
fletchto99 and others added 5 commits December 19, 2025 13:13
closes #512

## All PRs:

* [x] Has tests
* [x] Documentation updated

## Adding a new header (Reporting-Endpoints)
**Is the header supported by any user agent?*
 Yes - Chrome 116+, Edge 116+, Opera 102+ (via Reporting API)

**What does it do?**
Defines HTTP reporting endpoints for CSP violations and other
security/performance reports using the HTTP Reporting API

**What are the valid values?**
Comma-separated pairs of [name="url"] where url must be HTTPS (e.g.,
csp-violations="https://example.com/reports")

**Where does the specification live?** 
[MDN
Reporting-Endpoints](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Reporting-Endpoints)
and [MDN report-to
directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/report-to)

## Adding a new CSP directive (report-to)

**Is the directive supported by any user agent?** 
Yes - Chrome 69+, Edge 79+, Firefox 110+, Safari 15.1+

**What does it do?**
Specifies a named reporting endpoint (defined via Reporting-Endpoints
header) where CSP violations should be reported, replacing or
complementing report-uri

**What are the valid values?**
A single string endpoint name (e.g., report-to csp-violations), must
match a name defined in the Reporting-Endpoints header

---------

Co-authored-by: Kylie Stradley <kyfast@users.noreply.github.com>
@rei-moo rei-moo marked this pull request as ready for review January 27, 2026 20:54
Copilot AI review requested due to automatic review settings January 27, 2026 20:54
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Aggregates the 7.2 release changes, including Rack 3 / Rails compatibility fixes, CSP enhancements (reporting + HTTP/HTTPS behavior), and an opt-out mechanism to fully disable secure_headers.

Changes:

  • Add SecureHeaders::Configuration.disable! to fully disable header injection and middleware behavior.
  • Improve CSP features: support report-to + reporting-endpoints, normalize trailing slashes in CSP sources, and omit upgrade-insecure-requests on HTTP requests.
  • Improve framework compatibility: Rack 3 header/cookie handling updates and Railtie removal of Rails default headers regardless of header casing; refactor hash-generation rake task into a helper.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
spec/spec_helper.rb Adds reset helper for new disabled/noop configuration state.
spec/lib/secure_headers_spec.rb Adds/extends specs for disable!, CSP upgrade-insecure-requests behavior, report-to/reporting-endpoints.
spec/lib/secure_headers/task_helper_spec.rb New specs for extracted rake-task helper behavior.
spec/lib/secure_headers/railtie_spec.rb New specs to validate case-insensitive Rails default header removal.
spec/lib/secure_headers/middleware_spec.rb Adds coverage for Rack::Headers conversion, cookie handling, and disabled behavior.
spec/lib/secure_headers/headers/reporting_endpoints_spec.rb New unit tests for Reporting-Endpoints header generation/validation.
spec/lib/secure_headers/headers/policy_management_spec.rb Adds validation coverage for new report_to directive type.
spec/lib/secure_headers/headers/content_security_policy_spec.rb Adds CSP tests for trailing-slash normalization and report-to formatting/order.
spec/lib/secure_headers/configuration_spec.rb Adds coverage for Configuration.disable! lifecycle and interactions.
lib/tasks/tasks.rake Refactors hash generation task to use extracted helper and improves output message.
lib/secure_headers/task_helper.rb Extracts rake-task hashing logic into a reusable helper module.
lib/secure_headers/railtie.rb Removes conflicting Rails default headers case-insensitively.
lib/secure_headers/middleware.rb Improves Rack 3 compatibility (Rack::Headers) and preserves Set-Cookie arrays when present.
lib/secure_headers/headers/reporting_endpoints.rb Introduces Reporting-Endpoints header support + config validation.
lib/secure_headers/headers/policy_management.rb Adds report_to directive support and validation wiring.
lib/secure_headers/headers/content_security_policy.rb Adds report-to directive rendering and CSP source trailing-slash normalization.
lib/secure_headers/configuration.rb Adds disable!/disabled? and new reporting_endpoints configuration attribute + dup support.
lib/secure_headers.rb Wires in reporting-endpoints, HTTP-specific CSP behavior, and documents upgrade-insecure-requests behavior in code comments.
README.md Documents report-to/reporting-endpoints usage and how to fully disable secure_headers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

# formatted header value based on the config.
def make_header(config = nil)
return if config.nil? || config == OPT_OUT
validate_config!(config)
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make_header will emit a reporting-endpoints header with an empty value when config is an empty Hash (since format_endpoints({}) returns "" and generate_headers treats empty string as truthy). Consider treating an empty Hash like nil/OPT_OUT (return no header) or making validate_config! reject empty configs.

Suggested change
validate_config!(config)
validate_config!(config)
return if config.is_a?(Hash) && config.empty?

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,123 @@
# frozen_string_literal: true
require "spec_helper"
require "secure_headers/task_helper"
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This spec uses Tempfile.create but never requires the tempfile stdlib, which will cause NameError: uninitialized constant Tempfile in a clean test run. Add require "tempfile" near the top of the file (or in spec_helper.rb).

Suggested change
require "secure_headers/task_helper"
require "secure_headers/task_helper"
require "tempfile"

Copilot uses AI. Check for mistakes.
end
}.not_to raise_error

# After reset_config, disabled? returns nil (not false) because @disabled is removed
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says disabled? returns nil (not false), but Configuration.disabled? is implemented as defined?(@disabled) && @disabled, which returns false when @disabled is removed. Please update the comment to match the actual behavior to avoid confusion.

Suggested change
# After reset_config, disabled? returns nil (not false) because @disabled is removed
# After reset_config, disabled? returns a falsy value because @disabled is removed

Copilot uses AI. Check for mistakes.
Comment on lines +164 to +173
def normalize_uri_paths(source_list)
source_list.map do |source|
# Normalize domains ending in a single / as without omitting the slash accomplishes the same.
# https://www.w3.org/TR/CSP3/#match-paths § 6.6.2.10 Step 2
begin
uri = URI(source)
if uri.path == "/"
next source.chomp("/")
end
rescue URI::InvalidURIError
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normalize_uri_paths calls URI(source)/rescues URI::InvalidURIError, but this file never requires the stdlib uri. In a clean Ruby process this will raise NameError: uninitialized constant URI. Add require "uri" (or equivalent) before using URI.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment