Frameworks
How findings are mapped to framework controls, and what a mapping does and does not mean.
Pentr ships six compliance frameworks:
Each one is a YAML file in the application that declares a list of controls, two tables mapping finding identifiers to those controls, and one mapping keyword categories to them. An engagement names one framework, and its report, exports and seal are mapped against that one. Choosing a framework covers the choice. Custom frameworks covers a framework not in the list.
How a finding reaches a control¶
The mapper reads two kinds of structured identifier from a finding: MITRE
ATT&CK technique ids in attack_technique_ids, and OWASP WSTG test ids in
wstg_ids. It resolves a finding's controls in this order:
- Each ATT&CK technique id is looked up in the framework's ATT&CK table.
- Each WSTG id is looked up in the framework's WSTG table.
- If steps 1 and 2 together produced no control, the finding's title, description and evidence text are matched against sixteen keyword categories, and each category found is looked up in the framework's keyword table.
The finding's controls are the union of what those steps produced. Step 3 runs only when steps 1 and 2 produced nothing, so a finding whose identifiers reach at least one control is not matched by keyword at all.
Lookups are exact string matches. A sub-technique such as T1110.001 does not
match a row for T1110. Technique ids are checked against the ATT&CK Enterprise
catalog (v17.0) when the agent records a finding, so a finding can carry a
valid technique that its framework's table does not list; that id contributes no
control.
The keyword step is the path the application source marks as deprecated, kept
for findings without structured identifiers. Findings take it as a matter of
course. The agent's tool for recording a finding lists attack_technique_ids
as a required field, and an empty list satisfies it. Each framework's direct
tables are also small: ten ATT&CK techniques and twelve WSTG ids. The match is a
regular-expression search over the lower-cased text of the finding; a category
is found when any one of its patterns occurs anywhere in that text.
A finding can end with no control. That happens when its identifiers match no table row and its text matches no category the framework's keyword table lists.
What the result means¶
For each control the framework declares, the report records one of two statuses:
tested— at least one finding mapped to the control.not_tested— no finding mapped to it.
There is no third status. The PDF's Control Mappings table prints them as Tested and Not Tested, with the titles of the findings that mapped. A control of the standard that the YAML does not declare has no row. See The PDF report for where the table sits.
A control id is a pointer, not a verdict
A control id beside a finding does not mean the control was assessed, met
or failed. tested means at least one finding mapped to the control.
not_tested means none did, and says nothing about whether the control is
in place.
What a control id tells you depends on the step that produced it. An id from
the ATT&CK or WSTG table means the finding carried an identifier that the table
lists against that control. An id from the keyword step means the finding's
text matched one of a category's patterns. That match is a substring search
with no word boundaries, and it can attach a control the finding does not bear
on. A finding titled "SSH password brute force succeeded" matches the
command_injection pattern rce, because "force" contains it. Under SOC 2 it
lands on CC6.8, Malicious Software Prevention, and CC7.1 as well as on CC6.2,
the credential control its text also matches. The patterns ssl, eol and
sudo also match inside unrelated words, such as classloader, geolocation
and sudoku.
A finding counts toward a control whatever its status, including
false_positive and not_exploitable: the mapper does not read the status.
The PDF does not print a finding's status; the CSV and SARIF exports carry it
in their status field. Anatomy of a finding
describes the statuses.
The Reached by column¶
Each framework page lists its controls in a table generated from the framework's YAML. Its third column, Reached by, names the tables that list the control:
ATT&CK— a row of the ATT&CK table lists the control.WSTG— a row of the WSTG table lists it.keywords— a row of the keyword table lists it. Every key in the six keyword tables is a category the mapper can produce, so each of those rows can fire.none— no table lists it. No finding can map to the control, so it is reportednot_testedon every run, whatever the run finds.
A control whose column reads keywords alone is reached only by a finding
whose ATT&CK and WSTG ids matched nothing. The column describes the tables, not
the testing. ATT&CK means a finding tagged with a listed technique maps to
the control; it does not mean the agent will produce such a finding.
The per-control categories list¶
Each control in a framework's YAML also carries a categories list. The
application loads it and never reads it again: the mapper, the report and the
console do not use it. Its vocabulary differs from the mapper's — SOC 2's CC6.1
lists port_exposure, where the mapper's category is open_port. The keyword
step goes through the file's top-level category_mappings table. Anyone
reading the application repository should take the mapping from the three
tables, not from this list.
When mapping runs¶
Mapping runs at three points, and each one reads the framework's YAML as it is at that moment.
- At seal, once, at the end of the run. Each finding's control ids are resolved from the ATT&CK and WSTG tables; the keyword step is not used. Each artifact takes the union of the control ids of the findings linked to it, and those ids go into the signed manifest. See Sealing.
- The PDF report is built the first time it is requested, and the file is stored. Later requests return the stored file.
- The SARIF and CSV exports are rebuilt from the stored findings on every download. See SARIF and CSV exports.
The report and the exports use all three steps. The results can disagree for one run in two ways.
The first holds whenever a finding is mapped by keyword. The seal does not use
the keyword step, so that finding's controls appear in the report and add
nothing to the control ids in the manifest. A control whose Reached by column
reads keywords alone never appears in a sealed manifest.
The second follows a change to a framework's YAML. The seal and the PDF keep the mapping they were made with, and the SARIF and CSV exports use the file as it is at download. A run sealed and reported before the change and exported after it can carry different control ids in its manifest, its PDF and its exports.
None of the outputs records which state of the file it was mapped against. The
manifest and the SARIF export carry the framework's file name. The PDF prints
that name in capitals in its Engagement Details table, and prefixes each
finding's control ids in the Compliance Crosswalk with the framework's display
name. The CSV carries no framework field. None of them carries the framework's
version.
Last updated 2026-09-22