Skip to content

lutaml/oscal

OSCAL in Ruby

Purpose

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.

Installation

Install the gem:

[sudo] gem install oscal

or add it to your Gemfile:

gem 'oscal'

Quick start

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"))

Supported document types

All 8 OSCAL root document types are supported:

Class XML root element Description

Catalog

<catalog>

Control catalogs

Profile

<profile>

Control profiles / baselines

ComponentDefinition

<component-definition>

Component definitions

SystemSecurityPlan

<system-security-plan>

System security plans

AssessmentPlan

<assessment-plan>

Assessment plans

AssessmentResults

<assessment-results>

Assessment results

PlanOfActionAndMilestones

<plan-of-action-and-milestones>

POA&Ms

MappingCollection

<mapping-collection>

Mapping collections

Parsing

Direct parsing by format

# 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"))

Auto-detection with Oscal.parse

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)

Serialization

# 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)

Accessing model data

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.

Version support

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)  # => :profile

Code generation

Model 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.

Regenerating models

# 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.

How it works

  1. The metaschema gem parses the OSCAL Metaschema XML (oscal_complete_metaschema.xml)

  2. Metaschema::ModelGenerator creates in-memory Ruby classes from the metaschema definitions

  3. Metaschema::RubySourceEmitter serializes them to Ruby source code

  4. Each class uses Lutaml::Model::Serializable for serialization

Architecture

Version register system

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

File layout

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

Development

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:generate

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/lutaml/oscal-ruby.

License

Copyright Ribose Inc. The OSCAL schema is published by NIST.

Published under the 2-clause BSD license.

About

Gem for accessing/writing OSCAL content

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages