---
title: "Control mapping"
manual: "nr-vault"
version: "1.0"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-vault:auditor-control-mapping@1.0"
source: "Auditor/ControlMapping.rst"
rendered: "2026-09-18T07:37:50+00:00"
---

# Control mapping {#control-mapping}

Implemented controls mapped to BSI IT-Grundschutz modules and OWASP ASVS
chapters, each with an implementation pointer and an evidence source.

> [!NOTE]
> **How to read this mapping.** References are **chapter- and
> module-level**, deliberately. Individual requirement identifiers are not
> cited: they change between editions of the IT-Grundschutz-Kompendium and
> between ASVS versions, and a fabricated requirement number is worse than no
> number at all. Confirm the mapping against the edition in force for your
> engagement.
>
> A row means "this control contributes to that module", **not** "this module
> is satisfied". Most IT-Grundschutz modules extend well beyond what a TYPO3
> extension can implement — see [Target of evaluation](https://docs.typo3.org/permalink/netresearch/nr-vault:auditor-target-of-evaluation@1.0).

## Cryptography {#cryptography}

*BSI IT-Grundschutz: CON.1 (Kryptokonzept). OWASP ASVS v4: chapter 6
(Stored Cryptography), chapter 8 (Data Protection).*

| Control | Implementation | Evidence source |
| --- | --- | --- |
| Secrets encrypted at rest with authenticated encryption | `EncryptionService::encrypt()` — XChaCha20-Poly1305 or AES-256-GCM, identifier as associated data | `tx_nrvault_secret` contains no plaintext column; [Envelope scheme](https://docs.typo3.org/permalink/netresearch/nr-vault:security-cryptography-envelope@1.0) |
| Per-secret key separation (bounded blast radius) | One DEK per secret, wrapped by the master key (`encrypted_dek`, `dek_nonce`) | Schema; [ADR-002: Envelope encryption](https://docs.typo3.org/permalink/netresearch/nr-vault:adr-002-envelope-encryption@1.0) |
| Master key never stored with the ciphertext | `Typo3MasterKeyProvider`, `FileMasterKeyProvider`, `EnvironmentMasterKeyProvider` (and `transit`) | `masterKeyProvider` setting; [Key custody](https://docs.typo3.org/permalink/netresearch/nr-vault:operations-key-custody@1.0) |
| Domain-separated key derivation | `hash_hkdf()` with three distinct `info` strings | [HKDF usages](https://docs.typo3.org/permalink/netresearch/nr-vault:security-cryptography-hkdf@1.0) |
| Recorded, not inferred, algorithm selection | `encryption_version` \+ `encryption_algorithm` markers; unknown marker is a hard error | [Algorithm agility](https://docs.typo3.org/permalink/netresearch/nr-vault:security-cryptography-agility@1.0) |
| Cryptographically secure randomness for keys and nonces | `random_bytes()`; fresh nonce per operation and per re-wrap | [Key and nonce lengths](https://docs.typo3.org/permalink/netresearch/nr-vault:security-cryptography-key-lengths@1.0) |
| Constant-time comparison of secrets and integrity tags | `hash_equals()` in chain verification, anchor comparison and token handling | [Constant-time comparison](https://docs.typo3.org/permalink/netresearch/nr-vault:security-cryptography-constant-time@1.0) |
| Minimised plaintext lifetime in memory | `sodium_memzero()` on success and in `finally`; `#[SensitiveParameter]` | [Memory scrubbing policy](https://docs.typo3.org/permalink/netresearch/nr-vault:security-cryptography-memory@1.0) |
| Key rotation without plaintext exposure | `EncryptionService::reEncryptDek()` — DEK layer only | `vault:rotate-master-key` output; [Key rotation](https://docs.typo3.org/permalink/netresearch/nr-vault:operations-key-rotation@1.0) |

## Access control and authorization {#access-control-and-authorization}

*BSI IT-Grundschutz: ORP.4 (Identitäts- und Berechtigungsmanagement).
OWASP ASVS v4: chapter 2 (Authentication), chapter 4 (Access Control).*

| Control | Implementation | Evidence source |
| --- | --- | --- |
| Authentication required for every vault surface | TYPO3 backend authentication; frontend requests hold no operation permission | `AccessControlService::isGranted()`; [Frontend and page-cache caveats](https://docs.typo3.org/permalink/netresearch/nr-vault:security-trust-boundaries-frontend@1.0) |
| Least privilege by operation | `VaultPermission` — ten distinct permissions as TYPO3 custom permission options | `be_groups.custom_options`; [Operation permissions](https://docs.typo3.org/permalink/netresearch/nr-vault:security-operation-permissions@1.0) |
| Separation of machine consumption from human disclosure | `secret.use` and `secret.reveal`, neither implying the other | Permission table; [Verification procedures](https://docs.typo3.org/permalink/netresearch/nr-vault:auditor-verification-procedures@1.0) |
| Separation of duties for privileged operations | Distinct `master_key.rotate`, `secret.manage_policy`, `vault.configure`, `audit.view` / `audit.export` | Group grants per backend group |
| Object-level authorization independent of operation permission | Per-secret owner / group tiers via `canRead()` / `canWrite()` / `canDelete()` | [ADR-005: Access control](https://docs.typo3.org/permalink/netresearch/nr-vault:adr-005-access-control@1.0) |
| Server-side enforcement at every entry point | Operation permissions are enforced centrally in `VaultService` (`store()` requires `secret.create` / `secret.rotate` and — for access-policy changes — `secret.manage_policy`; `rotate()` requires `secret.rotate`; `delete()` requires `secret.delete`), so DataHandler/FormEngine requests and programmatic callers face the same gate as the module controllers. Controllers re-assert the permissions as defense-in-depth. `SecretTcaHook` does so too, and is the *sole* enforcement point for one case the service cannot see: creating a `tx_nrvault_secret` record without a value never calls `store()`, so the hook asserts `secret.create` in `processDatamap_preProcessFieldArray()` and refuses the record before it is inserted. | `VaultService::assertOperationGranted()`; `SecretTcaHook::isCreationGranted()`; route configuration plus controller code |
| No privileged short-circuit in the grant lookup | Grant evaluation deliberately avoids `BackendUserAuthentication::check()`, which returns `true` unconditionally for admins | `hasCustomPermissionOption()` |
| Administrative override is a single, withdrawable seam | `adminBypassActive()`; `disableAdminOverride` in the hardened profile, pinnable outside the backend | `vault:break-glass --status` reporting `adminOverrideDisabledEffective` |
| Emergency access is time-boxed, justified and evidenced | `BreakGlassService` — mandatory reason, TTL clamped to 1–60 minutes, audit written before the grant | `break_glass_activated` / `break_glass_deactivated` rows; [Break-glass mode](https://docs.typo3.org/permalink/netresearch/nr-vault:security-break-glass@1.0) |
| Disabled accounts cannot act on a stale session | Defence-in-depth `disable` checks in `AccessControlService` and `BreakGlassService` | Code review |
| Attributable impersonation for headless code | `TechnicalActorContext::runAs()` — validates the target user, restores scope on exceptions, never mutates `$GLOBALS['BE_USER']` | `actor_type = 'technical'` audit rows; [ADR-029: Scoped technical-actor identity for headless use](https://docs.typo3.org/permalink/netresearch/nr-vault:adr-029-technical-actor-context@1.0) |
| Unattended CLI access closed by default | `allowCliAccess` defaults to `0` | Configuration; [Configuration](https://docs.typo3.org/permalink/netresearch/nr-vault:configuration@1.0) |
| Frontend placeholder resolution restricted to published identifiers | `FrontendPlaceholderPolicy` — a `%vault(id)%` placeholder resolves only if the identifier was published from an admin-only source (TypoScript setup, site configuration, `frontendResolvableIdentifiers`, or an explicit `allowIdentifier()` grant). `frontend_accessible` alone is no longer sufficient, and the rule is strict on the CLI too unless `frontendPlaceholderLegacyCli` opts out | [ADR-035: Per-request allow-set of frontend-resolvable identifiers](https://docs.typo3.org/permalink/netresearch/nr-vault:adr-035-frontend-placeholder-allow-set@1.0) |
| Record copy and delete fail closed across vault fields | `DataHandlerHook` — a record delete asserts every vault field's delete gate before removing the first secret and is cancelled outright if any fails; a copy that cannot clone every secret deletes the ones it made and blanks every vault field. The control is a preflight plus best-effort compensation, not atomicity: a failure the preflight cannot predict leaves the secrets already deleted unrestorable, a failed rollback delete leaves an orphaned clone, and a failed blanking leaves the copy still referencing the source record's identifiers. The record is preserved either way, the delete and blanking residuals are named to the editor, and every failure — the orphaned clone included — is logged under a correlation reference | [TCA integration](https://docs.typo3.org/permalink/netresearch/nr-vault:tca-integration@1.0); [ADR-018: FlexForm secret lifecycle management](https://docs.typo3.org/permalink/netresearch/nr-vault:adr-018-flexform-secret-lifecycle@1.0) |
| CLI grant narrowed to automation operations | `cliAllowedOperations` defaults to `secret.use,secret.create,secret.rotate`; reveal, delete, policy management, audit viewing, audit export, master-key rotation and vault configuration must be added explicitly | `vault:doctor` finding `cli.allowed_operations` |

## Logging and audit {#logging-and-audit}

*BSI IT-Grundschutz: OPS.1.1.5 (Protokollierung), DER.1 (Detektion von
sicherheitsrelevanten Ereignissen). OWASP ASVS v4: chapter 7 (Error Handling
and Logging).*

| Control | Implementation | Evidence source |
| --- | --- | --- |
| Every access, mutation and denial is logged | `AuditLogService::log()` on read, create, update, rotate, delete, metadata change and `access_denied` | `tx_nrvault_audit_log`; [ADR-006: Audit logging](https://docs.typo3.org/permalink/netresearch/nr-vault:adr-006-audit-logging@1.0) |
| Log entry precedes the effect where order matters | Break-glass audits **before** granting; delete/store compensate a failed audit write by rolling the data change back. The FormEngine/DataHandler paths share this contract: the `tx_nrvault_secret` delete command runs through `VaultService::delete()`, and a metadata change (or value-less record creation) whose audit write fails is reverted by `SecretTcaHook` — no mutation persists without its audit entry. | `BreakGlassService::activate()`, `VaultService::delete()`, `SecretTcaHook` |
| Complete attribution | `actor_uid`, `actor_type`, `actor_username`, `actor_role`, `ip_address`, `user_agent`, `request_id` | Schema; bound into the hash from epoch 3 |
| Only known actions can enter the chain | `AuditAction::tryFrom()` rejects unknown actions loudly | Code review |
| Tamper detection on stored entries | HMAC-SHA256 hash chain; verification with `hash_equals()` | `vault:audit-verify`; [ADR-023: Audit hash chain HMAC consideration](https://docs.typo3.org/permalink/netresearch/nr-vault:adr-023-audit-hash-chain-hmac@1.0) |
| Adversarial resistance against a database writer | Chain key derived from the master key, which the database does not contain | [What the log proves — and against whom](https://docs.typo3.org/permalink/netresearch/nr-vault:security-audit-evidence-claims@1.0) |
| Deletion detection | uid-sequence gap analysis → `UID_GAP` | Verification output |
| Algorithm-downgrade detection | Per-row epoch comparison, chain-level epoch floor, and the epoch bound into the hash from epoch 3 | `EPOCH_DOWNGRADE` findings |
| Reset detection without external evidence | `AuditChainAnchorStore` — MAC-signed tip in `sys_registry` under a key derived from the master key, so truncating the audit table alone leaves an anchor naming a row that is gone. `auditAnchorRequired` promotes a missing anchor from warning to critical from a configuration file | `vault:doctor` finding `audit.db_anchor`; `vault:audit --verify` tip-anchor line; [ADR-034: Audit chain tip anchor](https://docs.typo3.org/permalink/netresearch/nr-vault:adr-034-audit-chain-tip-anchor@1.0) |
| Reset detection through external evidence | `ChainTipAnchorService` — shrinkage, substitution and epoch regression against a published anchor | `TABLE_RESET` findings; anchor file / SIEM records |
| Log copies outside the protected store | Syslog (RFC 5424), append-only NDJSON, webhook — fan-out after commit, contained per sink | [Sinks](https://docs.typo3.org/permalink/netresearch/nr-vault:security-audit-evidence-sinks@1.0) |
| Availability of the audit pipeline is observable | Persisted per-sink delivery state (last success / last failure / consecutive failures, `sys_registry`) surfaced as `audit.sink_state.<sink>` findings; active end-to-end verification via `vault:doctor --active-probes` (`audit.sink_probe.<sink>`); process-local failure counters; `SINK_FAILURE` and `NO_EXTERNAL_SINK` reason codes | [Sink failure counters](https://docs.typo3.org/permalink/netresearch/nr-vault:operations-monitoring-counters@1.0) |
| Machine-readable alerting | `AuditIntegrityAlertEvent` with stable reason codes and `isTamperEvidence()` | [AuditIntegrityAlertEvent](https://docs.typo3.org/permalink/netresearch/nr-vault:operations-monitoring-events@1.0) |
| Scheduled, unattended detection | `AuditAnchorTask`, `AuditVerifyTask` | Scheduler records; [Scheduler tasks](https://docs.typo3.org/permalink/netresearch/nr-vault:operations-monitoring-scheduler@1.0) |
| No secrets in logs or exceptions | `[REDACTED]` placeholders; token redaction in transport errors; response bodies not surfaced | Code review; `SecretRedactor` |

## Data protection and secret handling {#data-protection-and-secret-handling}

*BSI IT-Grundschutz: CON.1 (Kryptokonzept), CON.3 (Datensicherungskonzept).
OWASP ASVS v4: chapter 8 (Data Protection).*

| Control | Implementation | Evidence source |
| --- | --- | --- |
| Disclosure responses are never cached | `Cache-Control: no-store` and `Pragma: no-cache` on every reveal response, success and error alike | `AjaxController::withNoStore()`; HTTP response inspection |
| Client-side exposure is bounded | `startRevealLifecycle()` — 30 s auto-hide, wipe on `visibilitychange` and `pagehide` | `vault-reveal-lifecycle.js`; [Revealed plaintext in the browser cannot be zeroized](https://docs.typo3.org/permalink/netresearch/nr-vault:security-known-limitations-js@1.0) |
| No client-side secret cache | Every reveal re-hits `vault_reveal`, so every reveal is audited | Two reveals produce two audit rows |
| Clipboard exposure removed where it cannot be controlled | `copyAllowed = false` under the hardened profile; no copy button | Reveal response payload |
| Frontend disclosure is a property of the secret, not the visitor | `frontend_accessible`; frontend requests hold no operation permission | [Frontend and page-cache caveats](https://docs.typo3.org/permalink/netresearch/nr-vault:security-trust-boundaries-frontend@1.0) |
| Change detection without a plaintext oracle | `value_checksum` is a keyed MAC over the **ciphertext**, keyed per secret from the DEK | [The change-detection token](https://docs.typo3.org/permalink/netresearch/nr-vault:security-cryptography-checksum@1.0) |
| Recoverability of encrypted data | Documented separation of database and key-material backups, with a probe-decrypt verification step | [Backup and restore](https://docs.typo3.org/permalink/netresearch/nr-vault:operations-backup-and-restore@1.0) |
| Detection of plaintext secrets elsewhere in the installation | `SecretDetectionService`, `vault:scan` | Scan output |

## Configuration, fail-closed behaviour and supply chain {#configuration-fail-closed-behaviour-and-supply-chain}

*BSI IT-Grundschutz: CON.8 (Software-Entwicklung), OPS.1.1.5
(Protokollierung), DER.2.1 (Behandlung von Sicherheitsvorfällen). OWASP ASVS
v4: chapter 10 (Malicious Code), chapter 14 (Configuration).*

| Control | Implementation | Evidence source |
| --- | --- | --- |
| Security policy is enforced in code, not documentation | `SecurityProfile` consulted by provider selection, access control and audit verification | [Exact technical differences](https://docs.typo3.org/permalink/netresearch/nr-vault:security-profiles-differences@1.0) |
| Fail closed on misconfiguration | Unknown profile → `1753900001`; forbidden provider → `1753900002`; no hardened fallback or auto-detection | `MasterKeyProviderFactory`; [Verification procedures](https://docs.typo3.org/permalink/netresearch/nr-vault:auditor-verification-procedures@1.0) |
| Security-critical settings can be placed out of admin reach | `$GLOBALS['TYPO3_CONF_VARS']['SYS']['nrVault']` pins | `config/system/additional.php`; `--status` output |
| Deployment-time policy gate | `vault:doctor --profile=hardened`; exit `0` pass, `1` warnings, `2` critical | Pipeline logs and `--format=json` artefact |
| Outbound requests hardened against SSRF and DNS rebinding | `SecureHttpClientFactory`; scheme restricted to http/https; private targets need `allowed_hosts` | [ADR-026: DNS-rebinding defence via CURLOPT_RESOLVE](https://docs.typo3.org/permalink/netresearch/nr-vault:adr-026-dns-rebinding-defence@1.0) |
| Static analysis, security scanning and code review in CI | Shared `security.yml` (`composer audit` \+ **Opengrep** SAST on registry rules, blocking on WARNING+), `codeql.yml`, `license-check.yml`, `dependency-review.yml`, `fuzz.yml`. The in-repo `semgrep.yml` is **not** referenced by any workflow — see [Development and release evidence](https://docs.typo3.org/permalink/netresearch/nr-vault:auditor-evidence-ci@1.0) | `.github/workflows/checks.yml`; [Evidence collection](https://docs.typo3.org/permalink/netresearch/nr-vault:auditor-evidence-collection@1.0) |
| Secret scanning on every pull request | `gitleaks` job against the in-repo `.gitleaks.toml`, reporting to GitHub code scanning | `.github/workflows/checks.yml` |
| Workflow-definition auditing | `zizmor` job over `.github/workflows/`, configured by `.github/zizmor.yml` | `.github/workflows/checks.yml` |
| Broad compatibility test matrix | PHP 8.2–8.5 × TYPO3 `^13.4` and `^14.3`, unit and functional, coverage uploaded | `.github/workflows/ci.yml` |
| Release provenance | `actions/attest-build-provenance` over the `.zip` and `.tar.gz` — ungated. Requested at the call site with `id-token: write` \+ `attestations: write` | `.github/workflows/release.yml`; verify with `gh attestation verify` |
| Release evidence is itself tamper-evident | `actions/attest-build-provenance` over `release-evidence-<version>.tar.gz`, in the `bundle` job with `id-token: write` \+ `attestations: write`. The attestation outlives the 90-day run-artifact retention | `.github/workflows/release-evidence.yml`; verify with \``gh attestation verify release-evidence-<version>.tar.gz --repo netresearch/t3x-nr-vault`\` |
| Software bill of materials | Two SBOMs per tagged release via `anchore/sbom-action`: `<prefix>-<version>.sbom.spdx.json` (SPDX) and `.sbom.cdx.json` (CycloneDX). Delivered by the shared reusable; `include-sbom` defaults `true` and this repository's `release.yml` does not opt out | Release assets `*.sbom.spdx.json` / `*.sbom.cdx.json` |
| Artefact signing | Keyless Sigstore signing via `sigstore/cosign-installer` and `cosign sign-blob --bundle`, over **every** file in `dist/`, producing `<file>.sigstore.json` (that extension is chosen so OpenSSF Scorecard recognises the artefacts as signed). `sign-artifacts` defaults `true`; not opted out here | Release assets `*.sigstore.json`; verify with `cosign verify-blob --bundle` |
| Artefact integrity | `checksums.txt` — `sha256sum` over the whole `dist/` directory, including the SBOMs | Release asset; verify with `sha256sum -c checksums.txt` |
| Third-party actions pinned by digest | Every third-party `uses:` in the release path is pinned to a full commit SHA (`sbom-action`, `cosign-installer`, `attest-build-provenance`, `upload-artifact`). Netresearch-owned reusables intentionally stay on `@main` so upstream fixes propagate | Reusable workflow source; [Development and release evidence](https://docs.typo3.org/permalink/netresearch/nr-vault:auditor-evidence-ci@1.0) |
| Continuous security posture measurement | OpenSSF Scorecard on schedule and on default-branch pushes | `.github/workflows/checks.yml`; Scorecard results |
| Documented incident and emergency procedures | Runbooks for exposure, tampering and break-glass review | [Incident response](https://docs.typo3.org/permalink/netresearch/nr-vault:operations-incident-response@1.0) |

## Declared gaps {#declared-gaps}

Stated here so an assessment does not have to discover them, and does not
credit them as controls.

| Gap | Status |
| --- | --- |
| No protection against a compromised PHP process | **By design.** Architecturally unfixable in-process; the alternative is recorded in [ADR-016: Sidecar daemon option](https://docs.typo3.org/permalink/netresearch/nr-vault:adr-016-sidecar-option@1.0) and is **not implemented**. |
| No HSM or cloud-KMS integration | Only indirectly, via HashiCorp Vault Transit. No provider exists for AWS KMS, Azure Key Vault or GCP KMS. |
| Audit chain is tamper-evident, not tamper-proof | **By design.** Detection only; prevention would require append-only external storage, which is an operator responsibility. |
| Metadata and the audit trail are unencrypted | Accepted. Identifiers, ownership, timestamps and the access history are readable to anyone with database access; `audit.view` / `audit.export` gate the application path only. |
| `vault:delete` is a soft delete | Accepted. The ciphertext row remains until removed at the database level; crypto-erasure is the disposal mechanism — [Decommissioning](https://docs.typo3.org/permalink/netresearch/nr-vault:operations-decommissioning@1.0). |
| Revealed plaintext in the browser cannot be zeroized | Accepted, mitigated by a bounded exposure window and by disabling clipboard copy under the hardened profile. |
| `runAs()` is not an authentication boundary | **Explicitly documented as such.** Any extension code can act as any enabled backend user; the control provided is attribution, not prevention. |
| Supply-chain controls are delivered by a shared reusable pinned to `@main`, not by in-repo workflow steps | Accepted, by netresearch convention. SBOM generation, Cosign signing and build-provenance attestation live in `netresearch/typo3-ci-workflows` (referenced at `@main` so upstream fixes propagate), not in this repository's `release.yml`. An assessment verifies them against the resolved reusable and the produced release assets, not against this repository's workflow files alone. |
