Skip to main content

Python Open Source License: MIT OSCAL PyPI GitHub last commit

OSCAL Python Library

A Python library for working with Open Security Controls Assessment Language (OSCAL) content. The library provides classes to load, validate, convert, and manipulate OSCAL XML, JSON, and YAML documents for all published OSCAL versions and models.


Features

  • Profile Processing: Handles any combination and depth of profiles and catalogs. (See Profile Processing for details.)
  • All OSCAL models: Catalog, Profile, Mapping, Component Definition, SSP, Assessment Plan, Assessment Results, POA&M
  • All OSCAL formats: XML, JSON, and YAML — load any, save to any
  • All published OSCAL versions: pre-populated support database covers every NIST release; update to learn new versions as they are published
  • Pure-Python format conversion: no external XSLT processor required
  • Metaschema-based validation: structure, data-type, allowed-value, and cardinality checks against the NIST metaschema
  • Import resolution: automatically loads referenced catalogs, profiles, and other documents; surfaces structured failure details when imports cannot be resolved
  • Path-based querying: XPath-inspired syntax for navigating OSCAL content using either XML element names or JSON key names
  • Air-gapped operation: the bundled support database enables full offline use; update from an internet-connected machine and transfer the database file

Contents


Documentation

Document Contents
API Reference (Human Oriented) Reference: Functions, Classes, Methods, Attributes
API Reference (LLM Oriented) Reference: Functions, Classes, Methods, Attributes
Getting Started Installation, loading patterns, saving, and a walkthrough example
OSCAL Class API Complete class reference: factory methods, states, querying, mutation, import handling
Querying Content Full path syntax for query() and json_query()
Import Resolution How imports are resolved, failure codes, and retry API
Profile Processing How imports are resolved, failure codes, and retry API
Format Converters OSCALConverter and markup conversion internals
Support Module Support database configuration, updates, and API
Logging Standard Logging and Other Logging Libraries

Installation

pip install oscal

Latest pre-released development version:

pip install git+https://github.com/brian-ruf/oscal-class.git@develop#egg=oscal

Model Classes

Python Class OSCAL model
Catalog catalog
Profile profile
Mapping mapping-collection
ComponentDefinition component-definition
SSP system-security-plan
AssessmentPlan assessment-plan
AssessmentResults assessment-results
POAM plan-of-action-and-milestones

Use the base OSCAL class when the model is not known in advance. It will return the appropriate model-specific class.


Quick Start

Load and convert existing content

from oscal import OSCAL

# Load any OSCAL version for any model and any supported format
content = OSCAL.load("./catalog.yaml")

if content:
    print(f"{content.title} ({content.oscal_version})")
    # Save to JSON, XML, or YAML
    content.dump("catalog.json", format="json", pretty_print=True)
    content.dump("catalog.xml",  format="xml",  pretty_print=True)
    content.dump("catalog.yaml", format="yaml")
else:
    print(f"Load failed: {content.content_state.name}")

Profile Processing

from oscal import OSCAL
from oscal.oscal_controls import ResolutionStatus

# Load any OSCAL version for any model and any supported format
profile = OSCAL.load("path/to/profile.json")

# Groups/Controls Tree is available immediately after load:
def print_tree(nodes, indent=0):
    for node in nodes:
        kind = "GROUP  " if node["group"] else "control"
        print("  " * indent + f"{kind} {node['id']}  {node['title']}")
        print_tree(node["children"], indent + 1)

print_tree(profile.controls_tree)

# get_control_by_id also works pre-resolve (materializes just-in-time):
print(profile.get_control_by_id("ac-2"))

# resolve() is only needed when you want the full merged catalog:
if profile.resolve() == ResolutionStatus.RESOLVED:
    print(profile.dumps_catalog(format="json", pretty_print=True))
    print(profile.dumps_catalog(format="xml", pretty_print=True))
    print(profile.dumps_catalog(format="yaml"))

Create a new catalog

from oscal import Catalog

catalog = Catalog.new(
    title="My Catalog",
    version="1.0.0",
    published="2026-03-02T00:00:00Z",
)

catalog.create_control_group(
    parent_id="", id="ac", title="Access Control",
    props=[{"name": "label", "value": "AC"},
           {"name": "sort-id", "value": "001"}],
)
catalog.create_control(
    parent_id="ac", id="ac-1",
    title="Access Control Policy and Procedures",
    props=[{"name": "label", "value": "AC-1"},
           {"name": "sort-id", "value": "001-001"}],
    statements=["Develop, document, and disseminate an access control policy."],
)

# Save to XML, JSON, and YAML in one step each
catalog.dump("catalog.json", format="json", pretty_print=True)
catalog.dump("catalog.xml",  format="xml",  pretty_print=True)
catalog.dump("catalog.yaml", format="yaml")

Load in-memory content

from oscal import OSCAL

xml_str = """<?xml version="1.0" encoding="UTF-8"?>
<catalog xmlns="http://csrc.nist.gov/ns/oscal/1.0" uuid="8e38fb28-...">
  <metadata>
    <title>My Catalog</title>
    <version>DRAFT</version>
    <oscal-version>1.1.3</oscal-version>
  </metadata>
</catalog>"""

content = OSCAL.loads(xml_str)
print(content.model, content.title)   # catalog   My Catalog

Acquire from a URI

from oscal import OSCAL

content = OSCAL.acquire("https://raw.githubusercontent.com/.../catalog.json")

# Fallback list — first successful source wins
content = OSCAL.acquire([
    "https://primary.example.com/catalog.json",
    "./local-fallback/catalog.json",
])

Generic Content Queries

Load the content once and query using either the XML syntax names or JSON/YAML syntax names. Note XML group vs. JSON groups and XML control vs. JSON controls

# XML element name syntax
groups  = content.query('//group')    # Returns all groups as a Python list of dict objects
control = content.query_one('//control[@id="ac-2"]') # Returns a single control as a Python dict

# JSON key name syntax
groups  = content.query('//groups')    # Returns all groups as a Python list of dict objects
control = content.json_query_one('//controls[id="ac-2"]') # Returns a single control as a Python dict

Model-Specific Content Queries

Some model-specific classes have specific query methods. More will be added over time. For example, Catalog and Profile classes offer get_group_by_id and get_control_by_id. The optional depth parameter determines if child groups or controls are also returned. The default is 0 - no children returned.

group  = content.get_group_by_id("ac", depth=0)      # Returns a Python Dict with the group's title, props, parts and links
control = content.get_control_by_id("ac-2", depth=0)  # Returns a Python Dict with the control's title, props, parts and links

Air-Gapped Environments

The OSCALSupport class manages a local SQLite database of NIST-published metaschema and support files for every OSCAL version. The database ships pre-populated, enabling full offline operation from the moment you install the library.

To learn a newly published OSCAL version:

from oscal.oscal_support import get_support

support = get_support()
support.update()           # fetch any new NIST releases

Run update() on an internet-connected machine, then copy the updated support/oscal_support.db into the air-gapped environment.


Feedback and Contributions

Please submit bug reports and feature requests as GitHub issues. Bug fixes and backward-compatible contributions are welcome. Please open an issue and consider collaborating before starting work on any breaking changes.


Use of AI in This Library

No portion of this library was "vibe coded."

Early versions were written entirely without AI tools. Claude / Claude Code and GitHub Copilot have since been used in a manner similar to pair programming:

  • Options analysis when planning approaches
  • Alignment with Pythonic best practices
  • Targeted code reviews and linter resolution
  • Debugging and testing support
  • Drafting individual functions and methods (reviewed and tested before merge)
  • Drafting documentation and unit tests

Ruf Risk Logo

Cybersecurity Consulting
https://RufRisk.com
https://www.linkedin.com/company/rufrisk/

Brian J. Ruf, CISSP, CCSP, PMP
OSCAL Co-Creator, Independent Consultant
https://www.linkedin.com/in/brianruf/

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

oscal-3.1.1.tar.gz (16.0 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

oscal-3.1.1-py3-none-any.whl (16.0 MB view details)

Uploaded Python 3

File details

Details for the file oscal-3.1.1.tar.gz.

File metadata

  • Download URL: oscal-3.1.1.tar.gz
  • Upload date:
  • Size: 16.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for oscal-3.1.1.tar.gz
Algorithm Hash digest
SHA256 b51f5b4c937bddeb9ccf880cda868d9a027535f62106d9062ff711af2b4e2964
MD5 c20c2a305e2e5fe42677e5c5dd59b688
BLAKE2b-256 d8214bdfee91ca22e430e04b4420352bc874fb85ee3ea3a6141a1cd621d925c0

See more details on using hashes here.

Provenance

The following attestation bundles were made for oscal-3.1.1.tar.gz:

Publisher: publish.yml on brian-ruf/oscal-class

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file oscal-3.1.1-py3-none-any.whl.

File metadata

  • Download URL: oscal-3.1.1-py3-none-any.whl
  • Upload date:
  • Size: 16.0 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for oscal-3.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 16509e483e46b14a7f3d62d69003aca46bd985f8e4f2219bb5b9c839a8b6ce80
MD5 e68bcd09f0a348c67ffe11353b2eb1d1
BLAKE2b-256 547c7eebc6a64bccb7e95b175d8dfd0ba4330807f5a48f442708d84b4f87f5a7

See more details on using hashes here.

Provenance

The following attestation bundles were made for oscal-3.1.1-py3-none-any.whl:

Publisher: publish.yml on brian-ruf/oscal-class

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

3.1.1 This release

2 files

3.1.0

2 files

3.0.0

2 files

2.0.0

2 files

1.0.3

2 files

1.0.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page