Skip to content

Python API

The engines are plain functions over a typed Catalog and an OrgPosture.

from vantage.catalog import load_catalog
from vantage.coverage import compute_coverage
from vantage.io import load_org
from vantage.selectors import expand_org

cat = load_catalog("real")          # or "seed", "nist", or a catalog JSON path
org = expand_org(cat, load_org("vantage/postures/acme-real.yaml"))
cov = compute_coverage(cat, org)
print(cov.summary())

vantage.catalog

Catalog loading: the offline toy seed catalog, or the real-data catalog built from ATT&CK STIX + CIS v8 official mapping + SigmaHQ (see vantage.ingest.build).

catalog_from_dict(d)

Build and validate a Catalog from its JSON form.

load_catalog(which=None)

which: 'seed' (default, offline toy), 'real' (built catalog, CIS v8 controls), 'nist' (built catalog with NIST SP 800-53 rev5 controls), or a path to catalog JSON.

vantage.io

YAML (de)serialisation of org posture files.

Field types are checked here, so a malformed posture file gives one ValidationError naming the file and the field instead of a TypeError deep inside an engine.

org_from_dict(d)

Build an OrgPosture from parsed YAML, checking every field's type.

org_to_dict(org)

Inverse of org_from_dict (sorted, so the YAML is stable).

load_org(path)

Read a posture YAML file. Syntax and type errors raise ValidationError naming the file.

save_org(org, path)

Write a posture YAML file.

vantage.models

Typed contracts. Everything the engines consume is validated here.

TECHNIQUE_RE = re.compile('^T\\d{4}(\\.\\d{3})?$') module-attribute

ValidationError

Bases: ValueError

Invalid catalog, posture file or selector (the CLI prints it and exits 2).

CoverageStatus

Bases: str, Enum

Status of one technique for one org.

DEFENDED = 'defended' class-attribute instance-attribute

PAPER_ONLY = 'paper_only' class-attribute instance-attribute

DETECTED_ONLY = 'detected_only' class-attribute instance-attribute

BLIND = 'blind' class-attribute instance-attribute

Technique dataclass

An ATT&CK (sub-)technique.

id instance-attribute

name instance-attribute

tactic instance-attribute

description = '' class-attribute instance-attribute

tactics = () class-attribute instance-attribute

all_tactics property

Every tactic the technique belongs to.

__init__(id, name, tactic, description='', tactics=())

__post_init__()

Control dataclass

A framework control and the techniques its official mapping assigns to it.

id instance-attribute

framework instance-attribute

title instance-attribute

mitigates = frozenset() class-attribute instance-attribute

text = '' class-attribute instance-attribute

ig = None class-attribute instance-attribute

function = '' class-attribute instance-attribute

baselines = frozenset() class-attribute instance-attribute

__init__(id, framework, title, mitigates=frozenset(), text='', ig=None, function='', baselines=frozenset())

LogSource dataclass

A telemetry source that Sigma rules depend on.

id instance-attribute

name instance-attribute

cost = 1.0 class-attribute instance-attribute

__init__(id, name, cost=1.0)

__post_init__()

Detection dataclass

A detection rule, its techniques and the log sources it needs.

id instance-attribute

title instance-attribute

techniques instance-attribute

requires instance-attribute

cost = 1.0 class-attribute instance-attribute

level = '' class-attribute instance-attribute

status = '' class-attribute instance-attribute

__init__(id, title, techniques, requires, cost=1.0, level='', status='')

__post_init__()

Segment dataclass

A network segment and its criticality (1-3).

name instance-attribute

criticality = 1 class-attribute instance-attribute

__init__(name, criticality=1)

ZeroTrustFacts dataclass

Declared segmentation and identity facts for the Zero-Trust score.

segments = field(default_factory=list) class-attribute instance-attribute

open_flows = field(default_factory=set) class-attribute instance-attribute

mfa_coverage = 0.0 class-attribute instance-attribute

privileged_accounts = 0 class-attribute instance-attribute

pam_vaulted = 0 class-attribute instance-attribute

device_posture_checks = False class-attribute instance-attribute

stale_accounts = 0 class-attribute instance-attribute

total_accounts = 1 class-attribute instance-attribute

__init__(segments=list(), open_flows=set(), mfa_coverage=0.0, privileged_accounts=0, pam_vaulted=0, device_posture_checks=False, stale_accounts=0, total_accounts=1)

validate()

Raise ValidationError for unknown segments or out-of-range values.

OrgPosture dataclass

What the org claims and what is actually live.

name instance-attribute

claimed_controls instance-attribute

ingested_log_sources instance-attribute

deployed_detections instance-attribute

zero_trust = field(default_factory=ZeroTrustFacts) class-attribute instance-attribute

__init__(name, claimed_controls, ingested_log_sources, deployed_detections, zero_trust=ZeroTrustFacts())

Mitigation dataclass

ATT&CK course-of-action (Mxxxx) and the techniques it mitigates (from STIX relationships).

id instance-attribute

name instance-attribute

description instance-attribute

techniques instance-attribute

__init__(id, name, description, techniques)

Catalog dataclass

Techniques, controls, log sources, detections and mitigations, with provenance.

techniques instance-attribute

controls instance-attribute

log_sources instance-attribute

detections instance-attribute

mitigations = field(default_factory=dict) class-attribute instance-attribute

meta = field(default_factory=dict) class-attribute instance-attribute

__init__(techniques, controls, log_sources, detections, mitigations=dict(), meta=dict())

validate()

Raise ValidationError if any edge points at an unknown id.

validate_org(org)

Raise ValidationError if the posture names ids the catalog does not have.

vantage.coverage

Coverage engine: defended / paper_only / detected_only / blind per technique.

LEVEL_WEIGHT = {'critical': 1.0, 'high': 0.9, 'medium': 0.7, 'low': 0.5, 'informational': 0.3} module-attribute

STATUS_WEIGHT = {'stable': 1.0, 'test': 0.8, 'experimental': 0.6} module-attribute

DEFAULT_LEVEL_WEIGHT = 0.7 module-attribute

DEFAULT_STATUS_WEIGHT = 0.8 module-attribute

CoverageResult dataclass

Per-technique status plus the claims and live rules behind it.

status instance-attribute

claimed instance-attribute

detected_by instance-attribute

dead_detections = field(default_factory=set) class-attribute instance-attribute

catalog = field(default=None, repr=False, compare=False) class-attribute instance-attribute

confidence property

technique -> 1 - prod(1 - quality(rule)) over its live rules (computed lazily).

total property

Number of techniques scored.

claimed_pct property

Coverage the org would report on paper (any control claims the technique).

true_pct property

Defended (claimed AND detectable) share, in percent.

weighted_true_pct property

Defended coverage where each claimed technique counts by its detection confidence (noisy-OR of live rule qualities) instead of 0/1.

__init__(status, claimed, detected_by, dead_detections=set(), catalog=None, _confidence=None)

by_status(s)

Technique ids with status s, sorted.

pct(s)

Share of techniques with status s, in percent.

detected_set()

Techniques with at least one live rule, whatever is claimed.

summary()

Rounded headline numbers, status counts and dead rules (CLI and API output).

rule_quality(d)

Confidence in (0, 1] that a live rule really detects its tagged techniques.

live_detections(cat, deployed, ingested)

A detection fires only if deployed AND every required log source is ingested.

compute_coverage(cat, org, *, ingested=None, deployed=None, controls=None)

Score every technique; keyword overrides replace the org's ingested, deployed or claimed sets.

vantage.failure

Failure propagation: knock out one node, recompute, rank single points of failure.

Three node kinds:

  • log_source / detection - the technique loses detection (defended -> paper-only, detected-only -> blind). techniques_gone_dark lists those techniques.
  • control - the claimed control fails (lapses, is mis-configured, or the claim is false): the technique loses its claimed mitigation (defended -> detected-only, paper-only -> blind). techniques_gone_dark then lists the techniques left with no claimed control.

FailureImpact dataclass

Techniques that lose their defence (or claim) when one node fails.

kind instance-attribute

node instance-attribute

techniques_gone_dark instance-attribute

pct_of_matrix instance-attribute

__init__(kind, node, techniques_gone_dark, pct_of_matrix)

simulate_failure(cat, org, kind, node)

Recompute coverage with one node removed (reference implementation for the rankers).

rank_spofs(cat, org, top=None)

All single-node failures in one pass (O(live rules x techniques/rule)).

A technique goes dark when node X fails iff every live detection covering it depends on X. Equivalent to calling simulate_failure for each node (tested), but fast on the full SigmaHQ catalog where there are thousands of deployed rules.

rank_control_spofs(cat, org, top=None)

Claimed controls whose failure leaves techniques with no claimed mitigation, in one pass.

A technique loses its claim when control X fails iff X is the only claimed control mapped to it. Equivalent to simulate_failure(kind="control") for every claimed control (tested).

vantage.recommend

Greedy weighted set-cover: cheapest actions for maximum new technique coverage.

Actions
  • deploy_detection - an undeployed rule whose log sources are already ingested (or will be)
  • onboard_log_source - ingest a new source AND deploy every catalog rule it unlocks

Greedy ratio (new techniques / cost) gives the classic ln(n)-approximation for set cover.

ACTIONS = ('deploy_detection', 'onboard_log_source') module-attribute

Recommendation dataclass

One recommended action, its cost and the techniques it makes detectable.

action instance-attribute

target instance-attribute

cost instance-attribute

new_techniques instance-attribute

enables instance-attribute

ratio property

New techniques per unit cost (the greedy criterion).

__init__(action, target, cost, new_techniques, enables)

recommend(cat, org, *, budget=None, max_steps=10, actions=ACTIONS)

Greedy plan. actions restricts the action types (e.g. only log-source onboarding).

vantage.zerotrust

Rule-based Zero-Trust posture score (0-100) from segmentation + IAM facts.

WEIGHTS = {'segmentation': 0.35, 'mfa': 0.25, 'privileged_access': 0.2, 'device_posture': 0.1, 'account_hygiene': 0.1} module-attribute

LATERAL_TACTICS = {'lateral-movement', 'discovery'} module-attribute

ZTScore dataclass

Zero-Trust score (0-100), its components and the exposed lateral techniques.

score instance-attribute

components instance-attribute

exposed_lateral_techniques instance-attribute

__init__(score, components, exposed_lateral_techniques)

segmentation_score(zt)

1 minus the criticality-weighted share of segment pairs with open flows.

score_zero_trust(zt, cat=None)

Score segmentation, MFA, PAM, device posture and account hygiene.

vantage.selectors

Posture-file selectors so real-catalog org files stay short.

"@ig1" / "@ig2" / "@ig3" (all CIS safeguards up to that Implementation

Group), "@family:AC|IA" (NIST 800-53 families), "@baseline:LOW|MODERATE|HIGH" (NIST SP 800-53B baselines), "@all", or globs such as "CIS-8." / "NIST-AC-".

ingested_log_sources: "@all" or globs such as "windows/*", "aws/cloudtrail". deployed_detections: "@all", "@status:stable", "@min-level:high", or globs over rule ids. Several "@..." terms in one entry are AND-ed with "&": "@status:stable&@min-level:medium". Plain ids pass through unchanged (and are validated by Catalog.validate_org).

A selector or glob that matches nothing in the catalog is an error (a typo such as "@status:stabel" would otherwise silently give 0% coverage), and so is an unknown Sigma level, Sigma status or SP 800-53B baseline name.

LEVELS = ['informational', 'low', 'medium', 'high', 'critical'] module-attribute

STATUSES = {'stable', 'test', 'experimental', 'deprecated', 'unsupported'} module-attribute

BASELINES = {'LOW', 'MODERATE', 'HIGH', 'PRIVACY'} module-attribute

expand_org(cat, org)

Resolve selectors in-place against the catalog and return the org.

vantage.automap

Auto-map free-text controls to ATT&CK techniques.

Mappers (all expose rank(text, k) -> [(technique_id, score), ...]):

  • TfidfMapper - dependency-free TF-IDF + cosine against technique name/description.
  • EmbeddingMapper - sentence-transformers embeddings + cosine (optional dependency).
  • MitigationBridgeMapper - match the control text to ATT&CK mitigations (Mxxxx), then rank the techniques those mitigations address (STIX mitigates relationships). Uses only public ATT&CK data - never the CIS labels - but exploits the fact that controls are preventive/organisational text, which is much closer to mitigation prose than to adversary-behaviour prose.
  • PopularityBaseline / RandomBaseline - sanity baselines for the benchmark.

evaluate (micro P/R@k, used by the offline seed demo) and evaluate_mapper (P@k, R@k, MAP, per-control macro averages, used by benchmarks/bench_automap.py against the official CIS v8 -> ATT&CK mapping).

TfidfMapper

TF-IDF cosine between the control text and each technique's name and description.

name = 'tfidf-direct' class-attribute instance-attribute

ids = list(cat.techniques) instance-attribute

model = _Tfidf([technique_doc(t) for t in cat.techniques.values()]) instance-attribute

__init__(cat)

scores(text)

Similarity to every technique, aligned with self.ids.

rank(text, k=5)

Top-k techniques for free text.

EmbeddingMapper

Sentence-embedding cosine between the control text and each technique (ml extra).

name = f"embed-direct[{model.split('/')[-1]}]" instance-attribute

enc = _Encoder(model) instance-attribute

ids = list(cat.techniques) instance-attribute

mat = self.enc.encode([technique_doc(t) for t in cat.techniques.values()]) instance-attribute

__init__(cat, model=DEFAULT_EMBED_MODEL)

scores(text)

Similarity to every technique, aligned with self.ids.

rank(text, k=5)

Top-k techniques for free text.

MitigationBridgeMapper

text -> ATT&CK mitigations -> techniques they mitigate.

score(t) = max_{m mitigates t} sim(text, m) + alpha * sim(text, t) The direct term only breaks ties inside a mitigation's technique group.

name = f"mitigation-bridge[{encoder if encoder == 'tfidf' else model.split('/')[-1]}]" instance-attribute

ids = list(cat.techniques) instance-attribute

idx = {t: i for i, t in enumerate(self.ids)} instance-attribute

mits = list(cat.mitigations.values()) instance-attribute

alpha = alpha instance-attribute

direct = TfidfMapper(cat) instance-attribute

__init__(cat, encoder='tfidf', alpha=0.1, model=DEFAULT_EMBED_MODEL)

scores(text)

Bridge score per technique (aligned with self.ids); memoised per text.

rank(text, k=5)

Top-k techniques for free text.

RandomBaseline

Uniformly random ranking (seeded per text): the floor for the benchmark.

name = 'baseline-random' class-attribute instance-attribute

ids = sorted(cat.techniques) instance-attribute

seed = seed instance-attribute

__init__(cat, seed=0)

rank(text, k=5)

Top-k techniques for free text.

PopularityBaseline

Always predicts the techniques most often mapped by other controls (leave-one-out).

name = 'baseline-popularity' class-attribute instance-attribute

cat = cat instance-attribute

counts = Counter(t for c in cat.controls.values() for t in c.mitigates) instance-attribute

__init__(cat)

rank_for(control, k)

Top-k techniques for a control from the other controls' labels (leave-one-out).

rank(text, k=5)

Top-k techniques for free text.

evaluate_mapper(mapper, cat, ks=(5, 10, 20, 50), depth=200, ci=False)

Macro-averaged P@k, R@k and MAP@depth. With ci=True also adds <metric>_ci = 95% bootstrap interval over controls.

per_control_scores(mapper, cat, ks=(5, 10, 20, 50), depth=200)

Per-control P@k, R@k and AP@depth against each control's mitigates set (controls with no mapped techniques are skipped). Lists are aligned by control.

bootstrap_ci(values, n=1000, seed=0, alpha=0.05)

Percentile bootstrap CI of the mean (resampling controls with replacement).

paired_bootstrap_ci(a, b, n=2000, seed=0, alpha=0.05)

Mean of a-b and its percentile bootstrap CI, resampling the same controls for both.

vantage.ingest.nist

Parser for the CTID Mappings Explorer NIST SP 800-53 rev5 -> ATT&CK Enterprise mapping (JSON).

Source: https://github.com/center-for-threat-informed-defense/mappings-explorer (mappings/nist_800_53/attack-16.1/nist_800_53-rev5/enterprise/...) - Apache-2.0. Each mapping object is one (control, technique) pair with mapping_type "mitigates"; objects with status "non_mappable" (technique has no mitigation in ATT&CK) carry no control and are skipped.

NistControl dataclass

One NIST SP 800-53 rev5 control with its CTID-mapped techniques.

id instance-attribute

control instance-attribute

family instance-attribute

family_name instance-attribute

title instance-attribute

techniques = field(default_factory=set) class-attribute instance-attribute

__init__(id, control, family, family_name, title, techniques=set())

parse_mapping(doc)

Controls and metadata from the CTID NIST 800-53 -> ATT&CK mapping.

load_nist(path)

Read the CTID NIST 800-53 -> ATT&CK mapping file.

vantage.frameworks

All control frameworks with a public ATT&CK mapping, as Control objects in one ATT&CK release.

  • CIS Controls v8 (official CIS mapping, ATT&CK v8.2) - from the real catalog
  • NIST SP 800-53 rev5 (CTID, ATT&CK v16.1) - control statement prose from the NIST OSCAL catalog, and SP 800-53B LOW / MODERATE / HIGH / PRIVACY baseline membership
  • CRI Profile v2.1, CSA CCM 4.1, AWS, Azure, GCP, M365 (CTID Mappings Explorer)

Every technique id is carried forward to the catalog's ATT&CK release through revoked-by links (AttackData.resolve); deprecated/unknown ids are dropped and counted.

TEXT_RICH = ('CIS v8', 'NIST 800-53', 'CSA-CCM-4.1', 'CRI-2.1') module-attribute

Framework dataclass

One control framework's controls, carried forward to the current ATT&CK release.

name instance-attribute

controls instance-attribute

stats = field(default_factory=dict) class-attribute instance-attribute

source_attack = '' class-attribute instance-attribute

mapped property

Controls with at least one mapped technique.

techniques property

Every technique some control of the framework maps to.

__init__(name, controls, stats=dict(), source_attack='')

load_oscal()

NIST SP 800-53 rev5 OSCAL catalog with SP 800-53B baseline membership attached.

nist_framework(attack, oscal=None, text='statement')

text: 'statement' (OSCAL control statement), 'title' (name only, as in the CTID file).

ctid_framework(short, attack)

One CTID Mappings Explorer framework (short is a key of paths.CTID_FRAMEWORKS).

cis_framework(catalog_controls, stats=None)

CIS v8 safeguards from the built catalog (already carried forward at build time; pass the catalog's meta['cis'] as stats to report those build-time counts).

all_frameworks(attack, catalog_controls, include_title_only_nist=False, cis_stats=None)

Every framework keyed by display name (CIS first, then NIST, then the CTID files).

baseline_techniques(nist, oscal)

Techniques mitigated by the CTID-mapped controls in each SP 800-53B baseline.

source_universe(release_file, attack)

Current-release technique ids that existed (directly or via revoked-by) in an older ATT&CK release: the fair candidate set for scoring a mapping authored against that release.

vantage.transfer

Supervised control -> ATT&CK mapping that is trained on one framework and applied to another.

TransferMapper combines three signals, all computed over the ATT&CK technique universe:

  • bridge - the zero-shot mitigation-bridge score (control text -> ATT&CK mitigations -> techniques; see vantage.automap.MitigationBridgeMapper). Uses no framework labels.
  • knn - label transfer: the query control's k most similar training controls vote for their mapped techniques, weighted by text similarity (normalised to max 1).
  • prior - how often each technique is mapped in the training framework (normalised to max 1).

    score(t) = bridge(t) + beta * knn(t) + gamma * prior(t)

beta and gamma are chosen on the training framework only, by leave-one-control-out MAP@200 over a small grid; the test framework's labels are never seen. When the test control is itself part of the training set (the in-framework diagonal), rank_for excludes it from the neighbours and from the prior. Because (beta, gamma) were tuned on those same leave-one-out folds, that diagonal is optimistic; nested_loo_aps gives the honest in-framework estimate, choosing (beta, gamma) without the held-out control. (The TF-IDF vocabulary/IDF of the training texts still includes the held-out control: a small transductive effect on the diagonal only.)

BETAS = (0.0, 0.25, 0.5, 1.0, 2.0, 4.0) module-attribute

GAMMAS = (0.0, 0.1, 0.25, 0.5, 1.0) module-attribute

TransferMapper

Mitigation bridge + kNN label transfer + technique prior, trained on one framework's labels.

bridge = bridge or MitigationBridgeMapper(cat, encoder, model=model) instance-attribute

ids = [self.bridge.ids[i] for i in self._keep] instance-attribute

idx = {t: i for i, t in enumerate(self.ids)} instance-attribute

train = [c for c in train if c.mitigates & set(self.idx)] instance-attribute

k_nn = k_nn instance-attribute

counts = Counter(t for c in self.train for t in c.mitigates if t in self.idx) instance-attribute

name = f"transfer[{self.bridge.name.split('[')[1].rstrip(']')}, train={train_name}]" instance-attribute

__init__(cat, train, encoder='tfidf', model=DEFAULT_EMBED_MODEL, k_nn=10, beta=None, gamma=None, train_name='train', bridge=None, universe=None)

universe restricts the candidate techniques (e.g. those that existed in the ATT&CK release a framework was mapped against); bridge lets callers share one encoder.

fit()

Grid-search (beta, gamma) by leave-one-out MAP@200 on the training framework.

nested_loo_aps()

Honest in-framework estimate: for each training control, (beta, gamma) are re-chosen on the other n-1 controls' leave-one-out APs, then that control's AP is reported.

Returns {control id: AP@200}. Requires that fit ran (beta/gamma not passed in).

scores(text, exclude=None)

Combined score per technique (aligned with self.ids); exclude = leave-one-out.

rank(text, k=5)

Top-k techniques for free text.

rank_for(control, k)

Top-k techniques for a control, leaving it out of the training labels.

PriorTransferBaseline

Labels-only transfer: rank techniques by how often the training framework maps them.

train = list(train) instance-attribute

name = f'prior-only[train={train_name}]' instance-attribute

__init__(train, train_name='train')

rank_for(control, k)

Most frequent training techniques, excluding the query control's own labels.

rank(text, k=5)

Most frequent training techniques (the text is ignored).

average_precision(ranked, gold, depth=200)

AP@depth of one ranking against a gold set (normalised by min(|gold|, depth)).

vantage.ingest.ctid

Generic parser for CTID Mappings Explorer files (any framework -> ATT&CK Enterprise).

Source: https://github.com/center-for-threat-informed-defense/mappings-explorer (Apache-2.0). Handles both "mitigates" mappings (NIST 800-53, CRI Profile, CSA CCM) and the "technique_scores" security-stack mappings (AWS, Azure, GCP, M365), where every capability->technique pair carries a protect/detect/respond category and a minimal/partial/significant score.

Only the capability id, description (its name) and group are kept as control text. The per-pair comments field is deliberately dropped: it is written about the technique and would leak the label into any auto-mapper that reads it.

POSITIVE = {'mitigates', 'technique_scores', 'technique_score'} module-attribute

CtidCapability dataclass

One CTID Mappings Explorer capability (control) and its techniques.

id instance-attribute

name instance-attribute

group instance-attribute

group_name instance-attribute

techniques = field(default_factory=set) class-attribute instance-attribute

scores = field(default_factory=dict) class-attribute instance-attribute

categories = field(default_factory=set) class-attribute instance-attribute

__init__(id, name, group, group_name, techniques=set(), scores=dict(), categories=set())

parse_ctid(doc)

Capabilities and metadata from a Mappings Explorer JSON document (comments dropped).

load_ctid(path)

Read a Mappings Explorer JSON file.

vantage.ingest.oscal

NIST SP 800-53 rev5 OSCAL catalog + SP 800-53B baseline profiles.

Source: https://github.com/usnistgov/oscal-content (nist.gov/SP800-53/rev5/json) - public domain (US Government work). Gives the full control statement prose (which the CTID mapping file does not carry) and the LOW / MODERATE / HIGH / PRIVACY baseline memberships.

Control ids are normalised to the CTID zero-padded form: ac-2 -> AC-02, ac-2.1 -> AC-02(01).

OscalControl dataclass

One OSCAL rev5 control: title, statement text and SP 800-53B baselines.

id instance-attribute

title instance-attribute

family instance-attribute

family_name instance-attribute

statement instance-attribute

guidance = '' class-attribute instance-attribute

withdrawn = False class-attribute instance-attribute

baselines = field(default_factory=set) class-attribute instance-attribute

__init__(id, title, family, family_name, statement, guidance='', withdrawn=False, baselines=set())

norm_id(oscal_id)

OSCAL control id (ac-2.1) in CTID form (AC-02(01)).

parse_catalog(doc)

Controls and enhancements from the OSCAL rev5 catalog, parameters substituted.

parse_profile(doc)

Control ids included by an OSCAL baseline profile.

load_catalog_with_baselines(catalog, profiles)

OSCAL catalog with each control's SP 800-53B baseline membership.