Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 2024-05-24 - [Missing Default Security Headers]
**Vulnerability:** Newly generated Amber framework apps omit foundational HTTP security headers (e.g., XSS Protection, Content-Type Options).
**Learning:** The pipeline configuration in `routes.cr.ecr` lacked a dedicated middleware pipe to enforce baseline security headers for web routes.
**Prevention:** Always include a SecureHeaders middleware in the default web pipeline to ensure a defense-in-depth posture out of the box.
1 change: 1 addition & 0 deletions spec/amber/cli/commands/pipelines/pipelines_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ module Amber::CLI
Amber::Pipe::Session
Amber::Pipe::Flash
Amber::Pipe::CSRF
Amber::Pipe::SecureHeaders
)

static_default_plugs = %w(
Expand Down
35 changes: 35 additions & 0 deletions spec/amber/pipes/secure_headers_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require "../../spec_helper"

module Amber::Pipe
describe SecureHeaders do
it "adds default security headers to the response (HSTS disabled by default)" do
request = HTTP::Request.new("GET", "/")
response = HTTP::Server::Response.new(IO::Memory.new)
context = HTTP::Server::Context.new(request, response)

pipe = SecureHeaders.new
pipe.next = HTTP::Handler::HandlerProc.new { |ctx| }

pipe.call(context)

context.response.headers["X-Content-Type-Options"].should eq "nosniff"
context.response.headers["X-XSS-Protection"].should eq "1; mode=block"
context.response.headers["X-Frame-Options"].should eq "SAMEORIGIN"
context.response.headers["Referrer-Policy"].should eq "strict-origin-when-cross-origin"
context.response.headers.has_key?("Strict-Transport-Security").should be_false
end

it "adds HSTS when enabled" do
request = HTTP::Request.new("GET", "/")
response = HTTP::Server::Response.new(IO::Memory.new)
context = HTTP::Server::Context.new(request, response)

pipe = SecureHeaders.new(hsts: true)
pipe.next = HTTP::Handler::HandlerProc.new { |ctx| }

pipe.call(context)

context.response.headers["Strict-Transport-Security"].should eq "max-age=31536000; includeSubDomains"
end
end
end
1 change: 1 addition & 0 deletions src/amber/cli/templates/app/config/routes.cr.ecr
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Amber::Server.configure do
plug Amber::Pipe::Session.new
plug Amber::Pipe::Flash.new
plug Amber::Pipe::CSRF.new
plug Amber::Pipe::SecureHeaders.new
end

pipeline :api do
Expand Down
35 changes: 35 additions & 0 deletions src/amber/pipes/secure_headers.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module Amber
module Pipe
# The SecureHeaders pipe sets security-related HTTP headers to help
# protect against common vulnerabilities like XSS, clickjacking, and MIME sniffing.
class SecureHeaders < Base
property? hsts : Bool

def initialize(@hsts = false)
end

def call(context : HTTP::Server::Context)
headers = context.response.headers

# Prevent browser from guessing the MIME type
headers["X-Content-Type-Options"] ||= "nosniff"

# Prevent clickjacking by not allowing the page to be framed
headers["X-Frame-Options"] ||= "SAMEORIGIN"

# Enable XSS filtering in browsers
headers["X-XSS-Protection"] ||= "1; mode=block"

# Control how much referrer information is sent
headers["Referrer-Policy"] ||= "strict-origin-when-cross-origin"

# Enforce HTTPS connections (if enabled)
if @hsts
headers["Strict-Transport-Security"] ||= "max-age=31536000; includeSubDomains"
end

call_next(context)
end
end
end
end