Default serializer will use YAML.safe_load
Breaking change. Going forward, PT's default serializer (PaperTrail::Serializers::YAML) will use `safe_load` unless `ActiveRecord.use_yaml_unsafe_load`. PT users are required to configure `ActiveRecord.yaml_column_permitted_classes` correctly for their own application. Users may want to start with the following safe-list: ```ruby ::ActiveRecord.use_yaml_unsafe_load = false ::ActiveRecord.yaml_column_permitted_classes = [ ::ActiveRecord::Type::Time::Value, ::ActiveSupport::TimeWithZone, ::ActiveSupport::TimeZone, ::BigDecimal, ::Date, ::Symbol, ::Time ] ```
This commit is contained in:
parent
f7462ea7ea
commit
d17fdabd29
|
@ -26,6 +26,6 @@ appraise "rails-6.1" do
|
||||||
end
|
end
|
||||||
|
|
||||||
appraise "rails-7.0" do
|
appraise "rails-7.0" do
|
||||||
gem "rails", "~> 7.0.0"
|
gem "rails", "~> 7.0.3.1"
|
||||||
gem "rails-controller-testing", "~> 1.0.5"
|
gem "rails-controller-testing", "~> 1.0.5"
|
||||||
end
|
end
|
||||||
|
|
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -17,6 +17,25 @@ recommendations of [keepachangelog.com](http://keepachangelog.com/).
|
||||||
|
|
||||||
- None
|
- None
|
||||||
|
|
||||||
|
## 13.0.0 (2022-08-15)
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
- The default serializer will now use `YAML.safe_load` unless
|
||||||
|
`ActiveRecord.use_yaml_unsafe_load`. This change only affects users whose
|
||||||
|
`versions` table has `object` or `object_changes` columns of type `text`, and
|
||||||
|
who use the YAML serializer. People who use the JSON serializer, or those with
|
||||||
|
`json(b)` columns, are unaffected. Please see [doc/pt_13_yaml_safe_load.md] for
|
||||||
|
details.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- None
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- None
|
||||||
|
|
||||||
## 12.3.0 (2022-03-13)
|
## 12.3.0 (2022-03-13)
|
||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
# PT 13 uses YAML.safe_load
|
||||||
|
|
||||||
|
Starting with 13.0.0, PT's default serializer (`PaperTrail::Serializers::YAML`)
|
||||||
|
will use `safe_load` unless `ActiveRecord.use_yaml_unsafe_load`. Earlier
|
||||||
|
versions of PT use `unsafe_load`.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
> A few days ago Rails released versions 7.0.3.1, 6.1.6.1, 6.0.5.1, and 5.2.8.1.
|
||||||
|
> These are security updates that impact applications that use serialised
|
||||||
|
> attributes on Active Record models. These updates, identified by CVE-2022-32224
|
||||||
|
> cover a possible escalation to RCE when using YAML serialised columns in Active
|
||||||
|
> Record.
|
||||||
|
> https://rubyonrails.org/2022/7/15/this-week-in-rails-rails-security-releases-improved-generator-option-handling-and-more-24774592
|
||||||
|
|
||||||
|
## Who is affected by this change?
|
||||||
|
|
||||||
|
This change only affects users whose `versions` table has `object` or
|
||||||
|
`object_changes` columns of type `text`, and who use the YAML serializer. People
|
||||||
|
who use the JSON serializer, or those with `json(b)` columns, are unaffected.
|
||||||
|
|
||||||
|
## To continue using the YAML serializer
|
||||||
|
|
||||||
|
We recommend switching to `json(b)` columns, or at least JSON in a `text` column
|
||||||
|
(see "Other serializers" below). If you must continue using the YAML serializer,
|
||||||
|
PT users are required to configure `ActiveRecord.yaml_column_permitted_classes`
|
||||||
|
correctly for their own application. Users may want to start with the following
|
||||||
|
safe-list:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
::ActiveRecord.use_yaml_unsafe_load = false
|
||||||
|
::ActiveRecord.yaml_column_permitted_classes = [
|
||||||
|
::ActiveRecord::Type::Time::Value,
|
||||||
|
::ActiveSupport::TimeWithZone,
|
||||||
|
::ActiveSupport::TimeZone,
|
||||||
|
::BigDecimal,
|
||||||
|
::Date,
|
||||||
|
::Symbol,
|
||||||
|
::Time
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Other serializers
|
||||||
|
|
||||||
|
While YAML remains the default serializer in PT for historical compatibility,
|
||||||
|
we have recommended JSON instead, for years. See:
|
||||||
|
|
||||||
|
- [PostgreSQL JSON column type support](https://github.com/paper-trail-gem/paper_trail/blob/v12.3.0/README.md#postgresql-json-column-type-support)
|
||||||
|
- [Convert existing YAML data to JSON](https://github.com/paper-trail-gem/paper_trail/blob/v12.3.0/README.md#convert-existing-yaml-data-to-json)
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
source "https://rubygems.org"
|
source "https://rubygems.org"
|
||||||
|
|
||||||
gem "rails", "~> 7.0.0"
|
gem "rails", "~> 7.0.3.1"
|
||||||
gem "rails-controller-testing", "~> 1.0.5"
|
gem "rails-controller-testing", "~> 1.0.5"
|
||||||
|
|
||||||
gemspec path: "../"
|
gemspec path: "../"
|
||||||
|
|
|
@ -9,7 +9,17 @@ module PaperTrail
|
||||||
extend self # makes all instance methods become module methods as well
|
extend self # makes all instance methods become module methods as well
|
||||||
|
|
||||||
def load(string)
|
def load(string)
|
||||||
::YAML.respond_to?(:unsafe_load) ? ::YAML.unsafe_load(string) : ::YAML.load(string)
|
if use_safe_load?
|
||||||
|
::YAML.safe_load(
|
||||||
|
string,
|
||||||
|
permitted_classes: ::ActiveRecord.yaml_column_permitted_classes,
|
||||||
|
aliases: true
|
||||||
|
)
|
||||||
|
elsif ::YAML.respond_to?(:unsafe_load)
|
||||||
|
::YAML.unsafe_load(string)
|
||||||
|
else
|
||||||
|
::YAML.load(string)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param object (Hash | HashWithIndifferentAccess) - Coming from
|
# @param object (Hash | HashWithIndifferentAccess) - Coming from
|
||||||
|
@ -26,6 +36,14 @@ module PaperTrail
|
||||||
def where_object_condition(arel_field, field, value)
|
def where_object_condition(arel_field, field, value)
|
||||||
arel_field.matches("%\n#{field}: #{value}\n%")
|
arel_field.matches("%\n#{field}: #{value}\n%")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# `use_yaml_unsafe_load` was added in 7.0.3.1, will be removed in 7.1.0?
|
||||||
|
def use_safe_load?
|
||||||
|
defined?(ActiveRecord.use_yaml_unsafe_load) &&
|
||||||
|
!ActiveRecord.use_yaml_unsafe_load
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,6 +15,12 @@ module PaperTrail
|
||||||
module VersionConcern
|
module VersionConcern
|
||||||
extend ::ActiveSupport::Concern
|
extend ::ActiveSupport::Concern
|
||||||
|
|
||||||
|
E_YAML_PERMITTED_CLASSES = <<-EOS.squish.freeze
|
||||||
|
PaperTrail encountered a Psych::DisallowedClass error during
|
||||||
|
deserialization of YAML column, indicating that
|
||||||
|
yaml_column_permitted_classes has not been configured correctly. %s
|
||||||
|
EOS
|
||||||
|
|
||||||
included do
|
included do
|
||||||
belongs_to :item, polymorphic: true, optional: true, inverse_of: false
|
belongs_to :item, polymorphic: true, optional: true, inverse_of: false
|
||||||
validates_presence_of :event
|
validates_presence_of :event
|
||||||
|
@ -348,7 +354,10 @@ module PaperTrail
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
PaperTrail.serializer.load(object_changes)
|
PaperTrail.serializer.load(object_changes)
|
||||||
rescue StandardError # TODO: Rescue something more specific
|
rescue StandardError => e
|
||||||
|
if defined?(::Psych::Exception) && e.instance_of?(::Psych::Exception)
|
||||||
|
::Kernel.warn format(E_YAML_PERMITTED_CLASSES, e)
|
||||||
|
end
|
||||||
{}
|
{}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,5 +27,19 @@ module Dummy
|
||||||
::Gem::Requirement.new("~> 5.2").satisfied_by?(::Rails.gem_version)
|
::Gem::Requirement.new("~> 5.2").satisfied_by?(::Rails.gem_version)
|
||||||
config.active_record.sqlite3.represent_boolean_as_integer = true
|
config.active_record.sqlite3.represent_boolean_as_integer = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# `use_yaml_unsafe_load` was added in 7.0.3.1, will be removed in 7.1.0?
|
||||||
|
if ::ActiveRecord.respond_to?(:use_yaml_unsafe_load)
|
||||||
|
::ActiveRecord.use_yaml_unsafe_load = false
|
||||||
|
::ActiveRecord.yaml_column_permitted_classes = [
|
||||||
|
::ActiveRecord::Type::Time::Value,
|
||||||
|
::ActiveSupport::TimeWithZone,
|
||||||
|
::ActiveSupport::TimeZone,
|
||||||
|
::BigDecimal,
|
||||||
|
::Date,
|
||||||
|
::Symbol,
|
||||||
|
::Time
|
||||||
|
]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -46,6 +46,21 @@ RSpec.describe Widget, type: :model, versioning: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#object_changes_deserialized" do
|
||||||
|
context "when the serializer raises a Psych::DisallowedClass error" do
|
||||||
|
it "prints a warning to stderr" do
|
||||||
|
allow(PaperTrail.serializer).to(
|
||||||
|
receive(:load).and_raise(::Psych::Exception, "kaboom")
|
||||||
|
)
|
||||||
|
widget = described_class.create(name: "Henry")
|
||||||
|
ver = widget.versions.last
|
||||||
|
expect { ver.send(:object_changes_deserialized) }.to(
|
||||||
|
output(/kaboom/).to_stderr
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "with a new record" do
|
context "with a new record" do
|
||||||
it "not have any previous versions" do
|
it "not have any previous versions" do
|
||||||
expect(described_class.new.versions).to(eq([]))
|
expect(described_class.new.versions).to(eq([]))
|
||||||
|
|
|
@ -24,8 +24,13 @@ module PaperTrail
|
||||||
end
|
end
|
||||||
|
|
||||||
it "calls the expected load method based on Psych version" do
|
it "calls the expected load method based on Psych version" do
|
||||||
|
# `use_yaml_unsafe_load` was added in 7.0.3.1, will be removed in 7.1.0?
|
||||||
|
if defined?(ActiveRecord.use_yaml_unsafe_load) && !ActiveRecord.use_yaml_unsafe_load
|
||||||
|
allow(::YAML).to receive(:safe_load)
|
||||||
|
described_class.load("string")
|
||||||
|
expect(::YAML).to have_received(:safe_load)
|
||||||
# Psych 4+ implements .unsafe_load
|
# Psych 4+ implements .unsafe_load
|
||||||
if ::YAML.respond_to?(:unsafe_load)
|
elsif ::YAML.respond_to?(:unsafe_load)
|
||||||
allow(::YAML).to receive(:unsafe_load)
|
allow(::YAML).to receive(:unsafe_load)
|
||||||
described_class.load("string")
|
described_class.load("string")
|
||||||
expect(::YAML).to have_received(:unsafe_load)
|
expect(::YAML).to have_received(:unsafe_load)
|
||||||
|
|
|
@ -5,7 +5,7 @@ ENV["DB"] ||= "sqlite"
|
||||||
|
|
||||||
require "simplecov"
|
require "simplecov"
|
||||||
SimpleCov.start do
|
SimpleCov.start do
|
||||||
add_filter "spec"
|
add_filter %w[Appraisals Gemfile Rakefile doc gemfiles spec]
|
||||||
end
|
end
|
||||||
SimpleCov.minimum_coverage(ENV["DB"] == "postgres" ? 97.3 : 92.4)
|
SimpleCov.minimum_coverage(ENV["DB"] == "postgres" ? 97.3 : 92.4)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue