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:

  1. Each ATT&CK technique id is looked up in the framework's ATT&CK table.
  2. Each WSTG id is looked up in the framework's WSTG table.
  3. 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 reported not_tested on 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