aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/middleware
diff options
context:
space:
mode:
authorAidan Haran <aidanharan@yahoo.com>2017-12-09 13:41:02 +0000
committerGitHub <noreply@github.com>2017-12-09 13:41:02 +0000
commit66f34a8ea58c8c98d9cc2651d386c9e5a0789d08 (patch)
treed24e9014cf9045abc892ba97ac993e2e26e31c7e /actionpack/lib/action_dispatch/middleware
parent3291fa3630c456450f8c6a9b771f77c293d036cd (diff)
parent55d4cf2a9c1a6e77ed7aedb866e964039bb4a143 (diff)
downloadrails-66f34a8ea58c8c98d9cc2651d386c9e5a0789d08.tar.gz
rails-66f34a8ea58c8c98d9cc2651d386c9e5a0789d08.tar.bz2
rails-66f34a8ea58c8c98d9cc2651d386c9e5a0789d08.zip
Merge branch 'master' into custom-discarded-job-handling
Diffstat (limited to 'actionpack/lib/action_dispatch/middleware')
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb197
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cache_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb69
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb1
9 files changed, 142 insertions, 145 deletions
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 845df500d8..ea4156c972 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -49,6 +49,18 @@ module ActionDispatch
get_header Cookies::AUTHENTICATED_ENCRYPTED_COOKIE_SALT
end
+ def use_authenticated_cookie_encryption
+ get_header Cookies::USE_AUTHENTICATED_COOKIE_ENCRYPTION
+ end
+
+ def encrypted_cookie_cipher
+ get_header Cookies::ENCRYPTED_COOKIE_CIPHER
+ end
+
+ def signed_cookie_digest
+ get_header Cookies::SIGNED_COOKIE_DIGEST
+ end
+
def secret_token
get_header Cookies::SECRET_TOKEN
end
@@ -64,6 +76,11 @@ module ActionDispatch
def cookies_digest
get_header Cookies::COOKIES_DIGEST
end
+
+ def cookies_rotations
+ get_header Cookies::COOKIES_ROTATIONS
+ end
+
# :startdoc:
end
@@ -144,7 +161,7 @@ module ActionDispatch
#
# * <tt>:tld_length</tt> - When using <tt>:domain => :all</tt>, this option can be used to explicitly
# set the TLD length when using a short (<= 3 character) domain that is being interpreted as part of a TLD.
- # For example, to share cookies between user1.lvh.me and user2.lvh.me, set <tt>:tld_length</tt> to 1.
+ # For example, to share cookies between user1.lvh.me and user2.lvh.me, set <tt>:tld_length</tt> to 2.
# * <tt>:expires</tt> - The time at which this cookie expires, as a \Time or ActiveSupport::Duration object.
# * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers.
# Default is +false+.
@@ -157,10 +174,14 @@ module ActionDispatch
ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt".freeze
+ USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption".freeze
+ ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher".freeze
+ SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest".freeze
SECRET_TOKEN = "action_dispatch.secret_token".freeze
SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
COOKIES_DIGEST = "action_dispatch.cookies_digest".freeze
+ COOKIES_ROTATIONS = "action_dispatch.cookies_rotations".freeze
# Cookies can typically store 4096 bytes.
MAX_COOKIE_SIZE = 4096
@@ -201,12 +222,7 @@ module ActionDispatch
#
# cookies.signed[:discount] # => 45
def signed
- @signed ||=
- if upgrade_legacy_signed_cookies?
- UpgradeLegacySignedCookieJar.new(self)
- else
- SignedCookieJar.new(self)
- end
+ @signed ||= SignedKeyRotatingCookieJar.new(self)
end
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
@@ -223,18 +239,11 @@ module ActionDispatch
# Example:
#
# cookies.encrypted[:discount] = 45
- # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
+ # # => Set-Cookie: discount=DIQ7fw==--K3n//8vvnSbGq9dA--7Xh91HfLpwzbj1czhBiwOg==; path=/
#
# cookies.encrypted[:discount] # => 45
def encrypted
- @encrypted ||=
- if upgrade_legacy_signed_cookies?
- UpgradeLegacyEncryptedCookieJar.new(self)
- elsif upgrade_legacy_hmac_aes_cbc_cookies?
- UpgradeLegacyHmacAesCbcCookieJar.new(self)
- else
- EncryptedCookieJar.new(self)
- end
+ @encrypted ||= EncryptedKeyRotatingCookieJar.new(self)
end
# Returns the +signed+ or +encrypted+ jar, preferring +encrypted+ if +secret_key_base+ is set.
@@ -255,34 +264,18 @@ module ActionDispatch
end
def upgrade_legacy_hmac_aes_cbc_cookies?
- request.secret_key_base.present? &&
- request.authenticated_encrypted_cookie_salt.present? &&
- request.encrypted_signed_cookie_salt.present? &&
- request.encrypted_cookie_salt.present?
+ request.secret_key_base.present? &&
+ request.encrypted_signed_cookie_salt.present? &&
+ request.encrypted_cookie_salt.present? &&
+ request.use_authenticated_cookie_encryption
end
- end
- # Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream
- # to the Message{Encryptor,Verifier} allows us to handle the
- # (de)serialization step within the cookie jar, which gives us the
- # opportunity to detect and migrate legacy cookies.
- module VerifyAndUpgradeLegacySignedMessage # :nodoc:
- def initialize(*args)
- super
- @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
- end
-
- def verify_and_upgrade_legacy_signed_message(name, signed_message)
- deserialize(name, @legacy_verifier.verify(signed_message)).tap do |value|
- self[name] = { value: value }
+ def encrypted_cookie_cipher
+ request.encrypted_cookie_cipher || "aes-256-gcm"
end
- rescue ActiveSupport::MessageVerifier::InvalidSignature
- nil
- end
- private
- def parse(name, signed_message)
- super || verify_and_upgrade_legacy_signed_message(name, signed_message)
+ def signed_cookie_digest
+ request.signed_cookie_digest || "SHA1"
end
end
@@ -524,6 +517,7 @@ module ActionDispatch
module SerializedCookieJars # :nodoc:
MARSHAL_SIGNATURE = "\x04\x08".freeze
+ SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer
protected
def needs_migration?(value)
@@ -534,12 +528,16 @@ module ActionDispatch
serializer.dump(value)
end
- def deserialize(name, value)
+ def deserialize(name)
+ rotate = false
+ value = yield -> { rotate = true }
+
if value
- if needs_migration?(value)
- Marshal.load(value).tap do |v|
- self[name] = { value: v }
- end
+ case
+ when needs_migration?(value)
+ self[name] = Marshal.load(value)
+ when rotate
+ self[name] = serializer.load(value)
else
serializer.load(value)
end
@@ -561,24 +559,31 @@ module ActionDispatch
def digest
request.cookies_digest || "SHA1"
end
-
- def key_generator
- request.key_generator
- end
end
- class SignedCookieJar < AbstractCookieJar # :nodoc:
+ class SignedKeyRotatingCookieJar < AbstractCookieJar # :nodoc:
include SerializedCookieJars
def initialize(parent_jar)
super
- secret = key_generator.generate_key(request.signed_cookie_salt)
- @verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
+
+ secret = request.key_generator.generate_key(request.signed_cookie_salt)
+ @verifier = ActiveSupport::MessageVerifier.new(secret, digest: signed_cookie_digest, serializer: SERIALIZER)
+
+ request.cookies_rotations.signed.each do |*secrets, **options|
+ @verifier.rotate(*secrets, serializer: SERIALIZER, **options)
+ end
+
+ if upgrade_legacy_signed_cookies?
+ @verifier.rotate request.secret_token, serializer: SERIALIZER
+ end
end
private
def parse(name, signed_message)
- deserialize name, @verifier.verified(signed_message)
+ deserialize(name) do |rotate|
+ @verifier.verified(signed_message, on_rotation: rotate)
+ end
end
def commit(options)
@@ -588,37 +593,47 @@ module ActionDispatch
end
end
- # UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
- # secrets.secret_token and secret_key_base are both set. It reads
- # legacy cookies signed with the old dummy key generator and signs and
- # re-saves them using the new key generator to provide a smooth upgrade path.
- class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
- include VerifyAndUpgradeLegacySignedMessage
- end
-
- class EncryptedCookieJar < AbstractCookieJar # :nodoc:
+ class EncryptedKeyRotatingCookieJar < AbstractCookieJar # :nodoc:
include SerializedCookieJars
def initialize(parent_jar)
super
- if ActiveSupport::LegacyKeyGenerator === key_generator
- raise "You didn't set secret_key_base, which is required for this cookie jar. " \
- "Read the upgrade documentation to learn more about this new config option."
+ if request.use_authenticated_cookie_encryption
+ key_len = ActiveSupport::MessageEncryptor.key_len(encrypted_cookie_cipher)
+ secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, key_len)
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: encrypted_cookie_cipher, serializer: SERIALIZER)
+ else
+ key_len = ActiveSupport::MessageEncryptor.key_len("aes-256-cbc")
+ secret = request.key_generator.generate_key(request.encrypted_cookie_salt, key_len)
+ sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: SERIALIZER)
+ end
+
+ request.cookies_rotations.encrypted.each do |*secrets, **options|
+ @encryptor.rotate(*secrets, serializer: SERIALIZER, **options)
end
- cipher = "aes-256-gcm"
- key_len = ActiveSupport::MessageEncryptor.key_len(cipher)
- secret = key_generator.generate_key(request.authenticated_encrypted_cookie_salt || "")[0, key_len]
+ if upgrade_legacy_hmac_aes_cbc_cookies?
+ legacy_cipher = "aes-256-cbc"
+ secret = request.key_generator.generate_key(request.encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(legacy_cipher))
+ sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
- @encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
+ @encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
+ end
+
+ if upgrade_legacy_signed_cookies?
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, digest: digest, serializer: SERIALIZER)
+ end
end
private
def parse(name, encrypted_message)
- deserialize name, @encryptor.decrypt_and_verify(encrypted_message)
- rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
- nil
+ deserialize(name) do |rotate|
+ @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate)
+ end
+ rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
+ parse_legacy_signed_message(name, encrypted_message)
end
def commit(options)
@@ -626,39 +641,15 @@ module ActionDispatch
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
end
- end
-
- # UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
- # instead of EncryptedCookieJar if secrets.secret_token and secret_key_base
- # are both set. It reads legacy cookies signed with the old dummy key generator and
- # encrypts and re-saves them using the new key generator to provide a smooth upgrade path.
- class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc:
- include VerifyAndUpgradeLegacySignedMessage
- end
-
- # UpgradeLegacyHmacAesCbcCookieJar is used by ActionDispatch::Session::CookieStore
- # to upgrade cookies encrypted with AES-256-CBC with HMAC to AES-256-GCM
- class UpgradeLegacyHmacAesCbcCookieJar < EncryptedCookieJar
- def initialize(parent_jar)
- super
- secret = key_generator.generate_key(request.encrypted_cookie_salt || "")[0, ActiveSupport::MessageEncryptor.key_len]
- sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || "")
+ def parse_legacy_signed_message(name, legacy_signed_message)
+ if defined?(@legacy_verifier)
+ deserialize(name) do |rotate|
+ rotate.call
- @legacy_encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
- end
-
- def decrypt_and_verify_legacy_encrypted_message(name, signed_message)
- deserialize(name, @legacy_encryptor.decrypt_and_verify(signed_message)).tap do |value|
- self[name] = { value: value }
- end
- rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
- nil
- end
-
- private
- def parse(name, signed_message)
- super || decrypt_and_verify_legacy_encrypted_message(name, signed_message)
+ @legacy_verifier.verified(legacy_signed_message)
+ end
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 3006cd97ce..511306eb0e 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require_relative "../http/request"
-require_relative "exception_wrapper"
-require_relative "../routing/inspector"
+require "action_dispatch/http/request"
+require "action_dispatch/middleware/exception_wrapper"
+require "action_dispatch/routing/inspector"
require "action_view"
require "action_view/base"
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index e054fefc9b..5b0be96223 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -3,8 +3,8 @@
require "rack/utils"
require "rack/request"
require "rack/session/abstract/id"
-require_relative "../cookies"
-require_relative "../../request/session"
+require "action_dispatch/middleware/cookies"
+require "action_dispatch/request/session"
module ActionDispatch
module Session
diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
index c84bc8bfad..a6d965a644 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "abstract_store"
+require "action_dispatch/middleware/session/abstract_store"
module ActionDispatch
module Session
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index b0514a96d8..4ea96196d3 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "active_support/core_ext/hash/keys"
-require_relative "abstract_store"
+require "action_dispatch/middleware/session/abstract_store"
require "rack/session/cookie"
module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
index f0aec39c9c..914df3a2b1 100644
--- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "abstract_store"
+require "action_dispatch/middleware/session/abstract_store"
begin
require "rack/session/dalli"
rescue LoadError => e
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index d2e739d27f..3c88afd4d3 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../http/request"
-require_relative "exception_wrapper"
+require "action_dispatch/http/request"
+require "action_dispatch/middleware/exception_wrapper"
module ActionDispatch
# This middleware rescues any exception returned by the application
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index 45290b6ac3..ef633aadc6 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -1,47 +1,52 @@
# frozen_string_literal: true
module ActionDispatch
- # This middleware is added to the stack when `config.force_ssl = true`, and is passed
- # the options set in `config.ssl_options`. It does three jobs to enforce secure HTTP
+ # This middleware is added to the stack when <tt>config.force_ssl = true</tt>, and is passed
+ # the options set in +config.ssl_options+. It does three jobs to enforce secure HTTP
# requests:
#
- # 1. TLS redirect: Permanently redirects http:// requests to https://
- # with the same URL host, path, etc. Enabled by default. Set `config.ssl_options`
- # to modify the destination URL
- # (e.g. `redirect: { host: "secure.widgets.com", port: 8080 }`), or set
- # `redirect: false` to disable this feature.
+ # 1. <b>TLS redirect</b>: Permanently redirects +http://+ requests to +https://+
+ # with the same URL host, path, etc. Enabled by default. Set +config.ssl_options+
+ # to modify the destination URL
+ # (e.g. <tt>redirect: { host: "secure.widgets.com", port: 8080 }</tt>), or set
+ # <tt>redirect: false</tt> to disable this feature.
#
- # 2. Secure cookies: Sets the `secure` flag on cookies to tell browsers they
- # mustn't be sent along with http:// requests. Enabled by default. Set
- # `config.ssl_options` with `secure_cookies: false` to disable this feature.
+ # Requests can opt-out of redirection with +exclude+:
#
- # 3. HTTP Strict Transport Security (HSTS): Tells the browser to remember
- # this site as TLS-only and automatically redirect non-TLS requests.
- # Enabled by default. Configure `config.ssl_options` with `hsts: false` to disable.
+ # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } }
#
- # Set `config.ssl_options` with `hsts: { … }` to configure HSTS:
- # * `expires`: How long, in seconds, these settings will stick. The minimum
- # required to qualify for browser preload lists is `18.weeks`. Defaults to
- # `180.days` (recommended).
- # * `subdomains`: Set to `true` to tell the browser to apply these settings
- # to all subdomains. This protects your cookies from interception by a
- # vulnerable site on a subdomain. Defaults to `true`.
- # * `preload`: Advertise that this site may be included in browsers'
- # preloaded HSTS lists. HSTS protects your site on every visit *except the
- # first visit* since it hasn't seen your HSTS header yet. To close this
- # gap, browser vendors include a baked-in list of HSTS-enabled sites.
- # Go to https://hstspreload.appspot.com to submit your site for inclusion.
- # Defaults to `false`.
+ # 2. <b>Secure cookies</b>: Sets the +secure+ flag on cookies to tell browsers they
+ # must not be sent along with +http://+ requests. Enabled by default. Set
+ # +config.ssl_options+ with <tt>secure_cookies: false</tt> to disable this feature.
#
- # To turn off HSTS, omitting the header is not enough. Browsers will remember the
- # original HSTS directive until it expires. Instead, use the header to tell browsers to
- # expire HSTS immediately. Setting `hsts: false` is a shortcut for
- # `hsts: { expires: 0 }`.
+ # 3. <b>HTTP Strict Transport Security (HSTS)</b>: Tells the browser to remember
+ # this site as TLS-only and automatically redirect non-TLS requests.
+ # Enabled by default. Configure +config.ssl_options+ with <tt>hsts: false</tt> to disable.
#
- # Requests can opt-out of redirection with `exclude`:
+ # Set +config.ssl_options+ with <tt>hsts: { ... }</tt> to configure HSTS:
#
- # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } }
+ # * +expires+: How long, in seconds, these settings will stick. The minimum
+ # required to qualify for browser preload lists is 18 weeks. Defaults to
+ # 180 days (recommended).
+ #
+ # * +subdomains+: Set to +true+ to tell the browser to apply these settings
+ # to all subdomains. This protects your cookies from interception by a
+ # vulnerable site on a subdomain. Defaults to +true+.
+ #
+ # * +preload+: Advertise that this site may be included in browsers'
+ # preloaded HSTS lists. HSTS protects your site on every visit <i>except the
+ # first visit</i> since it hasn't seen your HSTS header yet. To close this
+ # gap, browser vendors include a baked-in list of HSTS-enabled sites.
+ # Go to https://hstspreload.org to submit your site for inclusion.
+ # Defaults to +false+.
+ #
+ # To turn off HSTS, omitting the header is not enough. Browsers will remember the
+ # original HSTS directive until it expires. Instead, use the header to tell browsers to
+ # expire HSTS immediately. Setting <tt>hsts: false</tt> is a shortcut for
+ # <tt>hsts: { expires: 0 }</tt>.
class SSL
+ # :stopdoc:
+
# Default to 180 days, the low end for https://www.ssllabs.com/ssltest/
# and greater than the 18-week requirement for browser preload lists.
HSTS_EXPIRES_IN = 15552000
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
index e0509f56f4..39ea25bdfc 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
@@ -106,6 +106,7 @@
.line {
padding-left: 10px;
+ white-space: pre;
}
.line:hover {