Ruby Psych YAML

Ruby from Psych: what it is, how it works, and how to use it safely

Psych is the default YAML parser and emitter shipped with modern Ruby, providing a stable interface to the libyaml C library for parsing and generating YAML documents. As the ca...

Mara Ellison
Ruby from Psych: what it is, how it works, and how to use it safely

Psych is the default YAML parser and emitter shipped with modern Ruby, providing a stable interface to the libyaml C library for parsing and generating YAML documents. As the canonical YAML engine in Ruby since Ruby 1.9, Psych underpins serialization for configuration files, rails templates, metadata, and many persistence formats. This evergreen explainer covers what Psych is, how Psych.load and Psych.dump behave, document options, type construction, and safe loading best practices so you can use YAML in Ruby reliably and securely.

What Psych is and why it matters

Psych is a YAML 1.1/1.2 wrapper around the C library libyaml, bundled as standard library psych in Ruby. It provides consistent, cross‑platform behavior for reading and writing YAML, and it serves as the default backend for ActiveSupport::JSON unless configured otherwise. Because Psych produces and consumes plain Ruby objects, understanding its interface is essential for safe deserialization, configuration management, and serialization of structured data in Ruby and Ruby on Rails applications.

Psych.load and Psych.dump fundamentals

Psych.load accepts a YAML string or IO stream and returns Ruby objects, resolving standard YAML tags by default with access to arbitrary Ruby classes when unsafe loading is used. Psych.dump accepts Ruby objects and emits a YAML string. Both methods accept an optional safe fallback value and a symbol ordering for deterministic output, plus options for line width, indentation, and canonical form. Choosing the correct loading mode—safe vs unsafe—is the most important decision for secure usage.

Key Psych.load options

  • Psych.safe_load: allows only basic YAML types (string, integer, float, boolean, nil, array, hash) unless permitted classes are explicitly listed.
  • Psych.load: uses the default unsafe mode and can deserialize arbitrary Ruby classes via !ruby/object tags, which may execute code if the class is loaded and instantiated.
  • Psych.load_doc: loads a specific document from a multi‑document stream by index.

Psych.safe_load vs Psych.load: security and compatibility

Use Psych.safe_load (or Psych.load with permitted classes) whenever you parse untrusted YAML. safe_load restricts types to scalars, arrays, and hashes, preventing remote code execution through malicious payloads. If you need custom types, explicitly permit them with the :permitted_classes or :aliases options rather than using default Psych.load. In contrast, Psych.load without restrictions can reconstruct complex object graphs and execute initialize methods, which is appropriate only for fully trusted input.

Psych loading modes quick comparison

Loading mode Permitted types Typical use case
Psych.safe_load Basic scalar, array, hash, and permitted classes Configuration, user‑provided YAML, CI pipelines
Psych.load with default behavior Any class that Psych can resolve and instantiate Trusted internal documents, fast prototyping with known sources
Psych.load with :permitted_classes User‑whitelisted classes only Controlled extensibility without full unsafe deserialization

YAML aliases and anchors in Psych

Understanding anchors and aliases

YAML supports anchors (&) and aliases (*) to reference nodes within a document, enabling compact structures and avoiding duplication. Psych preserves anchors and aliases when dumping if you use Psych.dump with :canonical set to true or explicitly manage references. When loading, Psych resolves aliases into consistent Ruby objects, while merge keys (<<) are handled according to YAML 1.1 semantics if supported by the parser configuration. This behavior is important for complex configuration where references reduce maintenance burden.

Document metadata and Psych parsing options

YAML documents can include directives for version and tag prefixes, which Psych exposes through Psych.parse and Psych.load_stream. You can inspect version and tag directives and control permitted tags for strict compliance. Psych.dump accepts options such as :line_width, :indent, :canonical, and :object_header to customize emitted YAML. When interoperating with other languages, prefer explicit typing and avoid language-specific tags to maintain portability across implementations.

Common pitfalls and safe patterns with Psych

Deserializing untrusted YAML with Psych.load is a common source of remote code execution. Always default to safe loading or explicitly permit classes. Be cautious with recursive data structures and deeply nested documents, which can cause high memory or CPU use. Use symbols for keys carefully to avoid symbol flooding in long‑running processes; prefer string keys or configure symbolize_names selectively. For deterministic output, consider sorting keys when dumping. Test edge cases such as empty documents, multi‑document streams, and malformed encodings to ensure robust integrations.

Multi‑document streams and Psych.parse

Psych supports loading and emitting multiple YAML documents in a single stream using Psych.load_stream and Psych.parse, which returns a Psych::Nodes::Document tree. This is useful for processing log files, configuration bundles, or versioned metadata. With Psych.parse you can inspect directives, version nodes, and tag declarations before deciding how to handle each document. For simple use cases, Psych.load_doc lets you select a document by index while preserving safety controls.

Psych versioning and compatibility notes

Psych follows the libyaml and Ruby compatibility model, with updates tied to Ruby releases. New Ruby versions may update the bundled libyaml, which can affect parsing behavior for obscure edge cases, but core API remains stable. Major Psych changes are rare and typically tied to Ruby itself. When choosing permitted classes or relying on specific YAML features, check the Psych version in your runtime and consult its documentation for supported options and defaults to avoid surprises across deployments.