flawopen.com/Insecure Deserialization/Ruby YAML

Is Ruby's YAML.load safe on untrusted input?

CWE-502: Deserialization of Untrusted DataReference page
Short answer

It depends entirely on your Ruby version, which is why this question causes so much confusion. From Ruby 3.1 (Psych 4), YAML.load is safe by default — it permits only a small set of basic types. On earlier versions it deserialized arbitrary Ruby objects and was a direct route to remote code execution. YAML.unsafe_load is never safe on untrusted input.

VULNERABLE
# Ruby < 3.1 — arbitrary object
# instantiation from YAML tags
YAML.load(params[:config])

# Any version — explicitly unsafe
YAML.unsafe_load(user_input)
Psych.unsafe_load(user_input)

# Marshal is always unsafe on
# untrusted data, no exceptions
Marshal.load(cookie_value)

# The payload shape:
#   --- !ruby/object:SomeClass
#   attr: value
FIXED
# Explicit and version-independent
YAML.safe_load(
  input,
  permitted_classes: [Symbol, Date],
  aliases: false
)

# Prefer JSON for untrusted data —
# it has no object-construction syntax
data = JSON.parse(input)

# Never round-trip session or cookie
# state through Marshal or YAML.
# Keep state server-side.

Why YAML is dangerous at all

Unlike JSON, YAML has a type-tag syntax that maps directly onto language objects. A document containing !ruby/object:SomeClass instructs the parser to construct an instance of that class with attacker-chosen instance variables. From there the attack is a gadget chain assembled from classes already loaded in the process — the same technique used against Python's pickle and PHP's unserialize.

In a Rails application the loaded class set is very large, which historically made these chains straightforward to build.

Why the version change caused confusion

Psych 4, bundled from Ruby 3.1, changed YAML.load to behave like safe_load and introduced unsafe_load for the old behaviour. This was a deliberate breaking change that improved the default, but it means the same line of code has different security properties depending on the interpreter running it — and it broke legitimate uses that relied on loading richer types, prompting some teams to "fix" the breakage by switching to unsafe_load.

If you see unsafe_load introduced during a Ruby upgrade, treat it as a finding: the correct migration is usually safe_load with an explicit permitted_classes list.

FAQ

Is YAML.safe_load completely safe?

It is safe against object instantiation with a conservative permitted-classes list. Be careful what you add to that list — permitting a class with side effects in its initialisation reopens the door. Also set aliases: false unless you need them, since alias expansion enables a denial-of-service variant.

What about loading my own config files?

A YAML file you control and ship with the application is trusted input, so unsafe_load is defensible there. The distinction is provenance, not file extension — a config file uploaded by a user is untrusted.

Is Marshal ever acceptable?

Only for data that never leaves your trust boundary. It has no safe mode and no allowlist mechanism, so any attacker-influenced Marshal payload is a compromise.

References