OSCAL Python Library
A Python library for working with Open Security Controls Assessment Language (OSCAL) content. The library provides classes to load, validate, convert, query 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
- Installation
- Model Classes
- Quick Start
- Air Gapped Environments
- Feedback and Contributions
- Use of AI
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
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
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/
Metadata
Release files for oscal 3.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| oscal-3.2.1.tar.gz | 15.4 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| oscal-3.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 30.8 MB
Release files / oscal-3.2.1.tar.gz
| Download URL | oscal-3.2.1.tar.gz |
|---|---|
| Size | 15.4 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1b476388672d3b066b2d2705ce3d2d77eee819f42f706bc7dc0ce7a45d29f12a
|
|
BLAKE2b-256 checksum How to use checksums |
98d5015c9a37bab5d37b93c2a874d37ade71e935bcf5975c8df530923b774bf1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.
Transparency logRelease files / oscal-3.2.1-py3-none-any.whl
| Download URL | oscal-3.2.1-py3-none-any.whl |
|---|---|
| Size | 15.4 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
859aff4926ba56b6b391880f3ce7481dc14ef553d7cf7db6d4ea17712f729369
|
|
BLAKE2b-256 checksum How to use checksums |
4897d2a361614ab7b38e3db58f0ca6ea5d4213c91d10d3e16c2415874a30846d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.
Transparency log