Turn scanner findings into the UAE control references an auditor actually asks for.
A scanner tells you CWE-89, SQL injection, high, app.py:31. A UAE auditor asks
which control that falls under, and whether it is one of the mandatory ones.
uae-control-map does that translation. Feed it SARIF from any scanner and get
back the UAE Information Assurance Standard v2.1 controls each finding
implicates — with priority (P1–P4), applicability (Always Applicable / Risk
Based), and a rationale quoting the standard's own wording. For Abu Dhabi
healthcare entities, --adhics additionally resolves every control onto
ADHICS v2.
Every control in the catalogue is extracted from the regulator's published PDF and validated against the figures the standard states about itself. Every mapping is authored by hand and cites primary-source text — nothing is inferred by an LLM or chained through a third-party crosswalk. Here is why that matters.
$ uae-control-map map scan.sarif --format table
CONTROL NAME APPLIC PRI SEV FIND
------------------------------------------------------------------------------------------------
T4.1.1 Network Controls always P1 high 3
T3.4.2 Data at Rest and in Motion always P1 high 1
T7.2.1 Application Security Requirements always P1 high 10
T3.4.3 Key Management always P1 high 1
T3.1.6 Controls Against Malware always P1 high 3
T1.3.3 Handling of Information Assets always P2 high 2
T7.3.1 Secure Coding risk-based P2 high 16
T4.1.3 Segregation in Networks risk-based P2 high 3
T7.4.3 Security testing in development and accep... risk-based P2 high 5
T4.1.5 Web Filtering risk-based P3 high 2
T3.1.1 Configuration Management always P1 medium 2
T3.3.2 Management of Technical Vulnerabilities always P1 medium 1
T5.3.1 Secure Authentication always P1 medium 2
T3.4.1 Cryptography Governance always P2 medium 2
T3.1.4 Separation of Development, Test and Opera... risk-based P2 medium 1
15 control(s) implicated by 19 of 19 finding(s); 0 unmapped CWE, 0 without CWE.Real output. Reproduce it from this repo with the quickstart below.
- Quickstart
- What ships in the box
- Installation
- Command reference
- Output formats
- Python API
- Abu Dhabi healthcare: ADHICS v2
- How the catalogue is built
- Why the mappings are authored, not derived
- Scope and limitations
- Reproducing every artefact
- Contributing
- Licence and attribution
git clone https://github.com/Edneam/uae-control-map
cd uae-control-map
uv sync --extra dev # or: pip install -e ".[dev]"
# map the committed fixture — a deliberately vulnerable Flask app
uv run uae-control-map map tests/fixtures/semgrep.sarif --format tableTo regenerate that SARIF from source with Semgrep:
uvx semgrep --config=p/security-audit --config=p/python --sarif \
-o scan.sarif tests/fixtures/vulnerable_app/app.py
uv run uae-control-map map scan.sarif --format tableNote
Point Semgrep at the file, not the directory. Semgrep's default ignore
list skips tests/, so scanning the directory silently yields zero findings.
| Artefact | Contents | Provenance |
|---|---|---|
data/uae_ia_v21.json |
134 controls · 15 families · 47 sub-families · 449 sub-controls | Extracted from UAE IA v2.1 (CSC, Nov 2025) |
| ↳ crosswalks | ISO 27001/27002:2022, NIST SP 800-53 r5, Dubai DESC ISR v3 | Annex D of the same document |
| ↳ lineage | 123 controls mapped to v1.1 + 11 new in v2.1 | Annex G of the same document |
data/adhics_v2.json |
131 controls · 577 sub-controls · 11 domains | Extracted from ADHICS v2 (DoH Abu Dhabi, May 2024) |
| ↳ crosswalk | 106 controls carrying a UAE IA v1.1 reference | UAE IAR Reference column, ADHICS Appendix 2 |
data/mappings/cwe_to_uae_ia_v21.yaml |
48 CWEs → 114 control mappings, covering 32 controls | Authored here · 42 quotes machine-verified |
data/mappings/uae_ia_v21_to_adhics_v2.yaml |
8 controls → 17 ADHICS mappings | Authored here · 16 quotes machine-verified |
Neither source PDF is vendored — they belong to their respective regulators.
SOURCES.md records both URLs and sha256 hashes so any copy can
be verified, and docs/METHOD.md documents the extraction
method in full.
uv pip install -e . # from a clone
pip install -e . # equivalentPython 3.10 – 3.13. One runtime dependency: PyYAML. The package is typed
(py.typed ships in the wheel) and bundles its own data, so the CLI works
identically from a clone or an installed wheel.
Not yet on PyPI.
| Command | Purpose |
|---|---|
map SARIF... |
Map findings to controls. Accepts multiple SARIF files, so one report can merge Semgrep (code) with Trivy (dependencies). |
cwe ID... |
Look up which controls one or more CWEs implicate — no scan needed. |
show ID |
Show a single control in full: statement, sub-controls, crosswalks, mapped CWEs. Alias: control. |
controls |
Browse and filter the catalogue. Alias: catalog. |
coverage |
Report mapping coverage per family, and state the gaps. |
adhics |
Browse the ADHICS v2 catalogue and inspect bridge coverage. |
Triaging a single finding is the most common question, so it does not require a scan:
$ uae-control-map cwe 89
CWE-89 Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
OWASP Top 10:2025 A05:2025 Injection
T7.3.1 P2 risk-based intersects-with 9/10 Secure Coding
T7.2.1 P1 always intersects-with 7/10 Application Security Requirements
T7.4.3 P2 risk-based intersects-with 6/10 Security testing in development and acceptance
T3.3.2 P1 always intersects-with 5/10 Management of Technical VulnerabilitiesAdd --rationale to print the full written justification for each mapping.
Filtering the catalogue:
uae-control-map controls --family T7 # one family
uae-control-map controls --priority P1 # by priority
uae-control-map controls --always-applicable # the mandatory 70
uae-control-map controls --grep "secure coding" # search names + statementsuae-control-map map scan.sarif --fail-on-always-applicableExits 1 when any finding implicates an Always Applicable control — the subset
every in-scope entity must satisfy regardless of its risk assessment.
--min-severity is a filter, not a gate: on its own it always exits 0. It
composes with the gate, so this fails the build only on high-or-worse findings
that touch a mandatory control:
uae-control-map map scan.sarif --min-severity high --fail-on-always-applicableOn the committed fixture, --min-severity narrows 19 findings to 9 at high
and 0 at critical.
--format markdown (default) is written for a human reviewer or an auditor: a
summary table, the implicated controls ordered by severity, then per-control
detail carrying the control statement, its Annex D cross-references, every
CWE that implicated it with the written rationale, and the findings themselves.
--format json is the same model for pipelines and dashboards:
{
"generated_utc": "...",
"target": { "standard": "UAE Information Assurance Standard", "version": "2.1", "...": "..." },
"summary": {
"findings_ingested": 19,
"findings_mapped": 19,
"controls_implicated": 15,
"controls_total": 134,
"always_applicable_implicated": 10,
"p1_implicated": 8
},
"controls": [ { "id": "T7.3.1", "cross_references": {}, "adhics_v2": [], "mappings": [], "findings": [] } ],
"unmapped_with_cwe": [],
"without_cwe": [],
"limitations": "..."
}--format table is the terse terminal summary shown at the top.
Findings that carry a CWE with no mapping, and findings carrying no CWE at all, are reported in their own sections rather than dropped. An unmapped CWE is stated as unmapped, never guessed at.
from uae_control_map import (
Catalog, MappingSet, parse_sarif, build_report, render_markdown,
)
from uae_control_map.adhics import AdhicsBridge
catalog = Catalog.load()
mappings = MappingSet.load(catalog=catalog)
findings = parse_sarif("scan.sarif")
report = build_report(
catalog, mappings, findings,
adhics=AdhicsBridge.load(catalog=catalog), # optional
)
print(f"{len(report.hits)} controls implicated by {report.mapped_findings} findings")
open("compliance.md", "w").write(render_markdown(report))
# Or query the ledger directly, without a scan
for cm in mappings.entry(89).controls:
print(cm.control.id, cm.relationship, cm.strength, cm.control.name)An Abu Dhabi healthcare entity is audited against ADHICS v2 (Department of
Health, May 2024), not UAE IA directly. Pass --adhics and every implicated
control also carries its ADHICS equivalents:
$ uae-control-map map scan.sarif --adhics --format table
...
15 control(s) implicated by 19 of 19 finding(s); 0 unmapped CWE, 0 without CWE.
ADHICS v2: 19 of 131 control(s) implicated.Every ADHICS row states how it was derived, because the two derivations carry very different weight:
$ uae-control-map show T5.3.1 --adhics
ADHICS v2 equivalents (1)
AC 6.2 Basic Annex G via v1.1 T5.5.2 User Identification and Authentication
$ uae-control-map show T7.3.1 --adhics
ADHICS v2 equivalents (3)
SA 2.3 Transitional authored intersects-with 8/10 Correct Processing in Applications
SA 1.1 Basic authored intersects-with 6/10 Information Systems Acquisition, Development and Maintenance Policy
CO 7.1 Advanced authored intersects-with 5/10 Technical Vulnerability Assessment and Penetration TestingAnnex G via … means the mapping is derived by composing two
regulator-published ID tables, with no judgement of mine in the chain:
ADHICS Appendix 2 each ADHICS control's "UAE IAR Reference" → UAE IA v1.1 id
UAE IA Annex G each v2.1 control's v1.1 predecessor(s) → UAE IA v2.1 id
authored … means Annex G leaves that control without a successor, so the
mapping is written by hand under the same STRM method as the CWE ledger, with a
quoted rationale. 24 of the 32 ledger-cited controls resolve via Annex G;
the remaining 8 are supplemented by hand.
ADHICS cites UAE IA v1.1 control IDs. They have the same shape as v2.1 IDs, which makes reading them directly very tempting — and wrong for 34 of the 114 IDs ADHICS cites. 102 IDs exist in both versions, and 56 mean substantively different things:
| ID | In v1.1 | In v2.1 |
|---|---|---|
M4.2.1 |
Screening | Notification and Communication of Security Policies |
M4.1.1 |
Human Resources Security Policy | Screening |
M1.3.3 |
Contact with Authorities | Documentation |
ADHICS HR 1.1 cites M4.2.1 meaning Screening. A tool that skipped Annex G
would confidently report Notification and Communication of Security Policies.
Routing through Annex G is what makes the reference correct, and
scripts/validate_adhics.py asserts that figure so it cannot quietly drift.
uae-control-map adhics --domain SA # the appsec domain
uae-control-map adhics --criteria Basic # what every entity must satisfy
uae-control-map adhics --grep validation
uae-control-map adhics --coverage # bridge coverage + source caveatsCriteria tiers are cumulative: --criteria Advanced returns all 131 controls,
because an Advanced entity must also satisfy Basic and Transitional.
Both are recorded in the data rather than smoothed over, and both are asserted by the extractor so a future revision that fixes them fails loudly instead of passing silently:
- The criteria split contradicts itself by one control. Appendix 1 says
Basic 58 / Transitional 40; the per-control table in Appendix 2 says 59 / 39.
The totals agree either way (131 controls, 577 sub-controls), and the
sub-control columns pin the delta exactly. The control is AC 2.2 Privilege
Management, printed
Basicin Appendix 2 butTransitionalin its own body text (p51). This catalogue follows Appendix 2 — the per-control table an auditor reads, and the one internally consistent with its own counts. - Two controls have no body text.
CO 9.1 Information Exchange ProceduresandSA 2.4 Off-line Processing Capabilitiesappear in Appendix 2 but are absent from Section B. Verified by comparing anchors: the body defines 129 controls, all present in Appendix 2, so this is a gap in the source rather than a parse miss. Both are kept with an empty statement.
ADHICS coverage is asymmetric, and worth stating plainly: 84 of 131 ADHICS controls are reachable from some UAE IA control, but only 39 from a CWE in the ledger. The other 47 are healthcare-specific (medical devices, telehealth, consent, DPIA) or governance requirements no scanner can observe.
The catalogue is parsed from the published PDF, not transcribed by hand. Two decisions make it trustworthy.
Geometry, not character columns. scripts/extract_uae_ia.py reads
pdftotext -bbox-layout output and assigns each word to a column by its true
x-coordinate. The first version of this extractor sliced fixed character columns
and produced a catalogue that passed every count check while being corrupt:
"name": "Configuration Management 8" ← ISO ref 8.9 bleeding in
"name": "Identity and Access Management N Policy" ← the "N" of "None"
Column label offsets differ from column data offsets, and the drift varies per page — the NIST header starts at character 73, 68, 66 and 79 on different pages of the same table. No single global slice is correct everywhere. Word coordinates are stable.
Validation against the document's own arithmetic. Extraction asserts every figure the standard publishes about itself, and fails rather than emitting a suspect catalogue:
families 15 · sub-families 47 · controls 134 · sub-controls 449 (Annex A, Tables 4 & 5)
always-applicable 70 · risk-based 64 (Annex A, Table 5)
P1 39 · P2 60 · P3 24 · P4 11 (Annex C, Table 7)
Reconciling to 449 sub-controls exactly is what surfaced three page-break bugs
that had silently dropped data — the PDF repeats a control-ID header on the
continuation page, so a naive parser commits the truncated first half and
discards the rest. The ADHICS extractor asserts its own published figures the
same way (131 controls / 577 sub-controls, Appendix 1), which is how a stray
backtick in the source — `12.1 for CO 12.1 — was caught costing an
entire control.
Both extractions are deterministic: regenerating either catalogue produces a byte-identical file.
Deriving CWE → UAE IA automatically was the first thing I tried. It does not work, and the failure is documented here so nobody repeats it.
The standard never mentions CWE — the string does not appear once in 242 pages.
But Annex D maps every control to NIST SP 800-53 r5 and ISO 27001:2022, and
OWASP's OpenCRE links CWEs to both, which suggests an
obvious two-hop bridge: CWE → OpenCRE → NIST/ISO → Annex D → UAE IA.
Measured against the real data by scripts/measure_derivation.py:
| Bridge | CWEs reaching any UAE control | UAE controls reached |
|---|---|---|
| via NIST SP 800-53 r5 | 1 of 582 | 2 of 134 |
| via ISO 27001:2022 | 265 of 582 | 7 of 134 |
| both combined | 266 of 582 | 8 of 134 |
That is 6% of the catalogue, and no path at all for most of what a scanner actually emits — path traversal, improper authentication, broken crypto, unsafe deserialization, hard-coded credentials and SSRF all reach nothing.
The NIST bridge is empty because OpenCRE hangs its CWE links and its NIST links off largely disjoint sets of Common Requirements — only 5 CREs carry both.
The ISO bridge looks better and is worse, because its output is wrong:
CWE-89 (SQL injection)
→ CRE "Clear policy compliant I/O requirements"
→ ISO 5.31 "Legal, statutory, regulatory and contractual requirements"
→ M5.1.1 "Identification of Applicable Legislation"
The phrase "policy compliant" in an intermediate node pulled SQL injection into a legal-compliance clause. Chaining two independently reasonable mappings composes their semantic error. OpenCRE's own contributing guide warns against exactly this: "We do not recommend to use an existing mapping from the standard to another standard that is already in OpenCRE (e.g. CWE). Typically, details get lost that way."
So mappings follow NIST IR 8477 Set Theory Relationship Mapping — the method NIST and the Secure Controls Framework use for crosswalks. A human author, an explicit relationship type, a strength score, and a written rationale:
- cwe: 89
name: "Improper Neutralization of Special Elements used in an SQL Command"
owasp_2025: "A05:2025 Injection"
controls:
- id: T7.3.1
relationship: intersects-with
strength: 9
rationale: >
T7.3.1 requires the entity to "apply secure coding practices for
software development". The control's implementation guidance names
injection explicitly as a class secure coding must prevent, and
parameterised queries are the canonical secure-coding remedy for
CWE-89. Intersects rather than subset: T7.3.1 spans every coding
weakness class, not only injection.relationship is one of the five STRM types (equal-to, subset-of,
superset-of, intersects-with, no-relationship); strength is 1–10.
The quote check is the trust anchor. scripts/validate_mappings.py asserts
that every cited control exists in the extracted catalogue and that every
quoted phrase appears verbatim in the standard's control text. It has already
caught two paraphrases I had written as quotes, and one quote that silently
normalised a typographic quirk in the source. A reviewer can therefore audit any
row without taking my word for it.
What this does. Translate CWE-tagged findings into UAE IA v2.1 (and optionally ADHICS v2) control references, with priority and applicability, so a report can be organised the way an auditor reads it.
What this does not do.
- It is not a compliance assessment. A control is implicated by a finding, not failed by it. A SQL injection defect is evidence relevant to T7.3.1 Secure Coding, but T7.3.1 also requires documented practices and developer training, which no SARIF file can speak to.
- It covers the technical slice only. 32 of 134 controls are reachable from
code-level findings. The management families (M1–M6: strategy, risk,
awareness, HR, compliance, performance) are almost entirely out of reach by
construction. That is a property of the standard, not a gap in the data —
uae-control-map coverageprints the breakdown per family. - The ledger is one author's judgement. STRM prescribes SME authoring; it does not make the result objective. Rationales are prose and quotes are verifiable precisely so a specialist can disagree with a specific row.
- Applicability is entity-specific. The 70/64 always-applicable/risk-based split comes from the standard, but which risk-based controls apply to a given entity comes from its own risk assessment and its sector regulator.
- 48 CWEs are mapped, chosen for prevalence in SAST/SCA/IaC output. Anything else is reported as unmapped.
- Do not average the two ADHICS derivations. Annex-G-derived mappings are as trustworthy as the regulator's own tables; supplement mappings are judgement. Every rendered row says which it is.
Nothing here requires trust. Every number in this README is regenerable:
# 1. fetch both standards (SOURCES.md has URLs + expected sha256)
sha256sum sources/uae-ia-standard-v2.pdf sources/adhics-v2-standard.pdf
# 2. extract with word geometry (character columns drift; coordinates do not)
pdftotext -bbox-layout sources/uae-ia-standard-v2.pdf sources/text/uae-ia-v2.bbox.xhtml
pdftotext -bbox-layout sources/adhics-v2-standard.pdf sources/text/adhics-v2.bbox.xhtml
# 3. rebuild both catalogues (each asserts its own published counts)
python scripts/extract_uae_ia.py sources/text/uae-ia-v2.bbox.xhtml data/uae_ia_v21.json
python scripts/extract_adhics.py sources/text/adhics-v2.bbox.xhtml data/adhics_v2.json
# 4. validate the ledgers against them
python scripts/validate_mappings.py # CWE -> UAE IA, incl. quote fidelity
python scripts/validate_adhics.py # UAE IA -> ADHICS lineage + supplement
# 5. reproduce the rejected-derivation measurement
python scripts/measure_derivation.py
# 6. tests
pytest # 113 testsSteps 4–6 need no PDFs and run on a fresh clone. Steps 1–3 require the source documents, which are not vendored — they belong to the Cyber Security Council and DoH Abu Dhabi respectively.
Mappings are the most useful thing to contribute, especially from anyone who has sat on the assessor side of a UAE IA or ADHICS audit. See CONTRIBUTING.md for the full guide.
Disagreement with an existing mapping is genuinely welcome — open an issue arguing the relationship type, since that is the part most worth debating.
python scripts/validate_mappings.py && python scripts/validate_adhics.py && pytestCode and mapping data: Apache-2.0.
The UAE Information Assurance Standard is the property of the UAE Cyber Security Council; ADHICS is the property of the Department of Health – Abu Dhabi. This project quotes control titles and short excerpts for identification and interpretation, and does not reproduce either document.
This is an independent project with no affiliation to, or endorsement from, the UAE Cyber Security Council, TDRA, or DoH Abu Dhabi. It does not produce a compliance determination.