Skip to content

Harden safe load#799

Open
hsbt wants to merge 3 commits into
masterfrom
harden-safe-load
Open

Harden safe load#799
hsbt wants to merge 3 commits into
masterfrom
harden-safe-load

Conversation

@hsbt

@hsbt hsbt commented Jun 8, 2026

Copy link
Copy Markdown
Member
  • !ruby/encoding now resolves through the class loader, so permitted_classes is honored just like !ruby/object:Encoding.
  • !ruby/hash-with-ivars, !ruby/hash, !map now require a Hash subclass, preventing init_with bypass via direct ivar injection.
  • !ruby/array, !seq, !ruby/string now require an Array/String subclass for the same reason.

hsbt and others added 3 commits June 8, 2026 17:52
safe_load resolved !ruby/encoding directly via ::Encoding.find,
bypassing the permitted_classes check that !ruby/object:Encoding
already honors. Load it through the class loader so Encoding is only
deserialized when permitted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
!ruby/hash-with-ivars, !ruby/hash and !map are only emitted for Hash
subclasses, but the loader allocated whatever class the tag named and
populated its ivars directly. That let a permitted non-Hash class be
instantiated with attacker-chosen ivars, bypassing its init_with
validation. Verify the resolved class is a Hash subclass before use.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
!ruby/array, !seq and !ruby/string carry the same exposure just fixed
for the hash tags: the loader allocated the named class and replaced its
contents without checking the class was actually an Array or String
subclass. Apply the same subclass check so a permitted unrelated class
can no longer be allocated and populated through these tags.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 8, 2026 09:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens Psych’s YAML deserialization around several Ruby-specific tags to prevent safe/unsafe load bypasses (notably via choosing unrelated permitted classes and injecting ivars directly).

Changes:

  • Route !ruby/encoding through the class loader so permitted_classes governs Encoding resolution.
  • Enforce that !ruby/string, !ruby/array/!seq, and !ruby/hash*/!map tags only accept subclasses of their expected core types.
  • Add regression tests ensuring invalid (non-subclass) class names are rejected for these tags.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
lib/psych/visitors/to_ruby.rb Adds resolve_subclass and applies it to string/array/hash tag handling; routes !ruby/encoding through class loader.
lib/psych/class_loader.rb Adds ENCODING constant so restricted loaders can gate Encoding via permitted_classes.
test/psych/test_safe_load.rb Adds safe-load tests verifying !ruby/encoding is disallowed unless Encoding is permitted.
test/psych/test_string.rb Adds tests ensuring !ruby/string:* rejects non-String classes.
test/psych/test_array.rb Adds tests ensuring !seq:* / !ruby/array:* reject non-Array classes.
test/psych/test_hash.rb Adds tests ensuring !ruby/hash*:* / !map:* reject non-Hash classes, covering ivar-injection bypass.

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

Comment on lines +478 to +484
def resolve_subclass klassname, parent
klass = resolve_class(klassname)
if klass && !(klass <= parent)
raise ArgumentError, "Invalid tag: expected a subclass of #{parent}, got #{klass}"
end
klass
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants