Skip to content

@mitre/hdf-extension-graph ​

Bidirectional extension graph processing for HDF baseline hierarchies.

Why this exists ​

HDF baseline documents can represent 'overlay' structures that form extension chains of parent baselines and their children. For example, a DISA STIG baseline defines hundreds of requirements; an organizational overlay on that DISA baseline can modify a subset for organization-specific policies; a project overlay can further tighten thresholds for a specific system. When an HDF results file contains multiple baselines linked via parentBaseline, understanding what each layer changed requires walking these chains bidirectionally.

Without this library, answering "did this overlay change the impact of SV-238196, or inherit it unchanged?" requires manually cross-referencing requirements across baselines by ID. The extension graph provides:

  • root — jump from any overlay requirement to the original base definition
  • modifications — which fields (impact, title, severity, effectiveImpact, disposition) an overlay changed relative to its parent
  • isRedundant — whether an overlay re-declares a control without actually changing it
  • fullCode — the complete code from all layers in one string
  • extensionChain — the ordered list of baselines from root to leaf

This is the same graph algorithm that powers Heimdall's control detail panel, extracted as a standalone library.

Installation ​

bash
pnpm add @mitre/hdf-extension-graph

Requires @mitre/hdf-schema as a peer dependency.

bash
go get github.com/mitre/hdf-libs/hdf-extension-graph/go/v3

Usage ​

Build the graph ​

typescript
import { buildExtensionGraph } from '@mitre/hdf-extension-graph';
import type { HDFResults } from '@mitre/hdf-schema';

const hdfResults: HDFResults = JSON.parse(fileContents);
const graph = buildExtensionGraph(hdfResults);
typescript
// Find root baselines (no parent)
const roots = graph.rootBaselines;

// Find a specific baseline
const stig = graph.findBaseline('rhel9-stig-baseline');

// See what extends it
for (const overlay of stig.extendedBy) {
  console.log(`${overlay.data.name} extends ${stig.data.name}`);
}
typescript
// Find all instances of a control across all baselines
const controls = graph.findRequirements('SV-238196');

// Get the root (base) control
const root = controls[1].root; // walks up the chain

// Get the full code with all layers
console.log(controls[1].fullCode);
// # my-overlay
// describe sshd_config do
//   its("ClientAliveInterval") { should cmp <= 300 }
// end
//
// # rhel9-stig-baseline
// describe sshd_config do
//   its("ClientAliveInterval") { should cmp <= 600 }
// end

Detect changes ​

typescript
const overlay = graph.baselines[1].requirements[0];

// Is this overlay just inheriting, or did it change something?
if (!overlay.isRedundant) {
  console.log('This overlay modifies the base control');
}

// What specifically changed?
for (const mod of overlay.modifications) {
  console.log(`${mod.field}: ${mod.originalValue} → ${mod.newValue}`);
}
// impact: 0.5 → 0.9
// title: SSH timeout → SSH timeout (project)

Walk the chain ​

typescript
// Ordered list of baselines from root to current
const chain = overlay.extensionChain;
console.log(chain.map(b => b.data.name));
// ['disa-rhel7-stig', 'cms-rhel7-overlay', 'project-overlay']

API ​

buildExtensionGraph(results: HDFResults): ExtensionGraph ​

Builds a bidirectional extension graph from an HDF Results file. Links baselines via parentBaseline and requirements by matching id across linked baselines.

ExtensionGraph ​

Property / MethodTypeDescription
baselinesContextualizedBaseline[]All baselines in the graph
requirementsContextualizedRequirement[]All requirements across all baselines
rootBaselinesContextualizedBaseline[]Baselines with no parent
findBaseline(name)ContextualizedBaseline | undefinedFind baseline by name
findRequirements(id)ContextualizedRequirement[]Find all requirements with given id

ContextualizedBaseline ​

PropertyTypeDescription
dataEvaluatedBaselineOriginal baseline data
sourcedFromHDFResultsThe results file this came from
extendsFromContextualizedBaseline[]Parent baselines
extendedByContextualizedBaseline[]Child baselines
requirementsContextualizedRequirement[]Wrapped requirements

ContextualizedRequirement ​

PropertyTypeDescription
dataEvaluatedRequirementOriginal requirement data
sourcedFromContextualizedBaselineOwning baseline
extendsFromContextualizedRequirement[]Parent requirements
extendedByContextualizedRequirement[]Child requirements
rootContextualizedRequirementBase requirement at bottom of chain
isRedundantbooleanTrue if code is empty or matches root
fullCodestringConcatenated code from all layers
extensionChainContextualizedBaseline[]Baselines from root to leaf
modificationsModification[]Fields changed vs immediate parent

Modification ​

typescript
interface Modification {
  field: string;        // 'impact', 'title', 'severity', 'effectiveImpact', or 'disposition'
  originalValue: unknown;
  newValue: unknown;
  inBaseline: string;   // Name of the baseline making the change
}

Go ​

The Go module (package hdfextension) mirrors the TypeScript package: the same graph, the same derived properties, and Modification output that serializes identically. The test/cross-language-equivalence.test.ts suite builds the go/cmd/equivalence-dump command and compares its canonical graph dump with the TypeScript dumper's (test/equivalence-dump.ts) on the same fixtures, so the two implementations cannot drift.

go
import hdfextension "github.com/mitre/hdf-libs/hdf-extension-graph/go/v3"

graph := hdfextension.BuildExtensionGraph(results) // results is a *hdf.HDFResults

stig := graph.FindBaseline("rhel9-stig-baseline")
for _, overlay := range stig.ExtendedBy {
    fmt.Printf("%s extends %s\n", overlay.Data.Name, stig.Data.Name)
}

for _, req := range graph.FindRequirements("SV-238196") {
    if !req.IsRedundant() {
        for _, mod := range req.Modifications() {
            fmt.Printf("%s: %v → %v\n", mod.Field, mod.OriginalValue, mod.NewValue)
        }
    }
}

BuildExtensionGraph(results *hdf.HDFResults) *ExtensionGraph ​

Builds the bidirectional graph from an HDF Results document, linking baselines via parentBaseline and requirements by matching id across linked baselines.

TypeScriptGo
graph.baselinesgraph.Baselines ([]*ContextualizedBaseline)
graph.requirementsgraph.Requirements ([]*ContextualizedRequirement)
graph.rootBaselinesgraph.RootBaselines()
graph.findBaseline(name)graph.FindBaseline(name) (nil when absent)
graph.findRequirements(id)graph.FindRequirements(id)
baseline.data / sourcedFrom / extendsFrom / extendedBy / requirementsData / SourcedFrom / ExtendsFrom / ExtendedBy / Requirements fields on ContextualizedBaseline
req.data / sourcedFrom / extendsFrom / extendedByData / SourcedFrom / ExtendsFrom / ExtendedBy fields on ContextualizedRequirement
req.root / isRedundant / fullCode / extensionChain / modificationsRoot() / IsRedundant() / FullCode() / ExtensionChain() / Modifications() methods
ModificationModification{Field, OriginalValue, NewValue, InBaseline}

hdfextension.TrackedFields lists the requirement fields Modifications() compares, in emission order.

Notes ​

License ​

Apache-2.0

Released under the Apache 2.0 License.