A Ruby library for OSCAL (Open Security Controls Assessment Language) models. All 226 model classes for OSCAL 1.2.1 are generated from the official NIST Metaschema definitions, supporting XML, JSON, and YAML serialization with round-trip fidelity.
This gem is used by the Metanorma plugin for OSCAL.
require "oscal"
# Parse any OSCAL document -- auto-detects format and version
doc = Oscal.parse(File.read("catalog.xml"))
doc = Oscal.parse(File.read("catalog.json"))
doc = Oscal.parse(File.read("catalog.yaml"))All 8 OSCAL root document types are supported:
| Class | XML root element | Description |
|---|---|---|
|
|
Control catalogs |
|
|
Control profiles / baselines |
|
|
Component definitions |
|
|
System security plans |
|
|
Assessment plans |
|
|
Assessment results |
|
|
POA&Ms |
|
|
Mapping collections |
# XML
catalog = Oscal::Catalog.from_xml(File.read("catalog.xml"))
# JSON
profile = Oscal::Profile.from_json(File.read("profile.json"))
# YAML
ssp = Oscal::SystemSecurityPlan.from_yaml(File.read("ssp.yaml"))Oscal.parse detects the document format (XML, JSON, or YAML), the OSCAL
version from <oscal-version>, and the root model type automatically:
# From XML -- detects <catalog> root, version from metadata
doc = Oscal.parse(File.read("catalog.xml"))
# From JSON -- detects root key, version from metadata
doc = Oscal.parse(File.read("component-definition.json"))
# Force a specific model type (useful for incomplete documents)
doc = Oscal.parse(xml_string, model: :profile)# To XML
xml = Oscal::Catalog.to_xml(catalog)
File.write("output.xml", xml)
# To JSON
json = Oscal::Catalog.to_json(catalog)
File.write("output.json", json)
# To YAML
yaml = Oscal::Catalog.to_yaml(catalog)
File.write("output.yaml", yaml)Model classes are standard Lutaml::Model::Serializable objects with typed
attributes:
catalog = Oscal::Catalog.from_xml(File.read("catalog.xml"))
# Top-level attributes
catalog.uuid # => "74c8ba1e-5cd4-4ad1-bbfd-d888e2f6c724"
# Metadata
catalog.metadata.title.content # => ["Sample Security Catalog"]
catalog.metadata.oscal_version.content # => ["1.0.0"]
catalog.metadata.last_modified.content # => ["2024-01-01T00:00:00Z"]
# Navigate groups and controls
groups = catalog.group # => Array of Group objects
groups.first.title.content # => ["Organization of Information Security"]
groups.first.control # => Array of Control objects
# Back matter
catalog.back_matter.resource # => Array of Resource objects|
Note
|
Markup content fields (like title, description, prose) use mixed
content and return arrays. Use .content.join to get a plain string, or
access individual elements for structured markup.
|
The gem uses a version registry to support multiple OSCAL schema versions. Currently OSCAL 1.2.1 is included; adding new versions follows the same pattern (see ADDING_VERSIONS.adoc).
# Default (latest registered version)
Oscal.default_version_module # => Oscal::V1_2_1
# Explicit version
v121 = Oscal.version("1.2.1")
v121::Catalog.from_xml(xml)
# Check available versions
Oscal::VersionRegistry.available_versions # => ["1.2.1"]
# Detect version from a document
Oscal::VersionRegistry.detect_version(xml_string) # => "1.2.1"
Oscal::VersionRegistry.detect_version(json_string) # => "1.2.1"
# Detect model type from a document
Oscal::VersionRegistry.detect_model_type(xml_string) # => :catalog
Oscal::VersionRegistry.detect_model_type(json_string) # => :profileModel classes are generated from the NIST OSCAL Metaschema XML definitions
using the metaschema gem. This ensures
the Ruby models stay faithful to the official OSCAL schema specification.
# Generate OSCAL 1.2.1 (default)
bundle exec rake oscal:generate
# Generate a specific version
bundle exec rake oscal:generate[1.3.0]This creates lib/oscal/v1_2_1/all_models.rb containing all 226 model classes
with XML and key-value (JSON/YAML) mappings.
-
The
metaschemagem parses the OSCAL Metaschema XML (oscal_complete_metaschema.xml) -
Metaschema::ModelGeneratorcreates in-memory Ruby classes from the metaschema definitions -
Metaschema::RubySourceEmitterserializes them to Ruby source code -
Each class uses
Lutaml::Model::Serializablefor serialization
Each OSCAL version has its own Lutaml::Model::Register for type resolution:
-
Register ID:
:oscal_1_2_1 -
Fallback chain:
:oscal_common→:default -
Generated code uses symbol type references (
:metadata,:control) that resolve through the version-specific register, enabling version swappability
lib/oscal.rb # Facade: parse(), version(), const_missing lib/oscal/version.rb # Gem version lib/oscal/versioned.rb # Base module for version modules lib/oscal/version_registry.rb # Version detection and registration lib/oscal/v1_2_1.rb # OSCAL 1.2.1 version module lib/oscal/v1_2_1/all_models.rb # 226 generated model classes
After checking out the repo, run bin/setup to install dependencies.
Then run rake spec to run the tests. You can also run bin/console for an
interactive prompt.
# Run default tests (excludes large NIST fixtures)
bundle exec rake spec
# Run with NIST SP 800-53 tests (requires oscal-content submodule)
bundle exec rspec --tag nist
# Run everything including slow cross-format tests
bundle exec rspec --tag slow
# Regenerate model classes
bundle exec rake oscal:generateBug reports and pull requests are welcome on GitHub at https://github.com/lutaml/oscal-ruby.