Skip to content

Repository files navigation

uae-control-map

Turn scanner findings into the UAE control references an auditor actually asks for.

Licence: Apache-2.0 tests: 113 Python 3.10+ UAE IA v2.1 ADHICS v2


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.

Contents

Quickstart

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 table

To 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 table

Note

Point Semgrep at the file, not the directory. Semgrep's default ignore list skips tests/, so scanning the directory silently yields zero findings.

What ships in the box

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.

Installation

uv pip install -e .          # from a clone
pip install -e .            # equivalent

Python 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 reference

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 Vulnerabilities

Add --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 + statements

CI gating

uae-control-map map scan.sarif --fail-on-always-applicable

Exits 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-applicable

On the committed fixture, --min-severity narrows 19 findings to 9 at high and 0 at critical.

Output formats

--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.

Python API

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)

Abu Dhabi healthcare: ADHICS v2

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 Testing

Annex 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.

Why the v1.1 hop is not optional

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.

Browsing ADHICS

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 caveats

Criteria tiers are cumulative: --criteria Advanced returns all 131 controls, because an Advanced entity must also satisfy Basic and Transitional.

Two inconsistencies in the ADHICS document

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:

  1. 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 Basic in Appendix 2 but Transitional in 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.
  2. Two controls have no body text. CO 9.1 Information Exchange Procedures and SA 2.4 Off-line Processing Capabilities appear 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.

How the catalogue is built

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.

Why the mappings are authored, not derived

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.

Scope and limitations

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 coverage prints 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.

Reproducing every artefact

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 tests

Steps 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.

Contributing

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 && pytest

Licence and attribution

Code 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.

About

Map SAST/DAST/SCA findings to UAE Information Assurance Standard v2.1 control references (+ ISO 27001:2022, NIST SP 800-53 r5, Dubai DESC ISR v3 crosswalks from Annex D)

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages