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.
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 |
Encryption —
XChaCha20-Poly1305 or AES-256-GCM, identifier as associated data | tx_nrvault_secret contains no plaintext column;
Envelope scheme |
| 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 |
| Master key never stored with the ciphertext |
Typo3Master,
File,
Environment
(and transit) | masterKeyProvider setting;
Key custody |
| Domain-separated key derivation |
hash_ with three distinct info strings | HKDF usages |
| Recorded, not inferred, algorithm selection | encryption_version + encryption_algorithm markers;
unknown marker is a hard error | Algorithm agility |
| Cryptographically secure randomness for keys and nonces |
random_; fresh nonce per operation and per re-wrap | Key and nonce lengths |
| Constant-time comparison of secrets and integrity tags |
hash_ in chain verification, anchor comparison and
token handling | Constant-time comparison |
| Minimised plaintext lifetime in memory |
sodium_ on success and in finally;
# | Memory scrubbing policy |
| Key rotation without plaintext exposure |
Encryption — DEK layer only | vault:rotate-master-key output;
Key rotation |
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 |
Access;
Frontend and page-cache caveats |
| Least privilege by operation |
Vault — ten distinct permissions as TYPO3 custom
permission options | be_groups.custom_options;
Operation permissions |
| Separation of machine consumption from human disclosure | secret.use and secret.reveal, neither implying the other | Permission table; Verification procedures |
| 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
can / canWrite() / canDelete() | ADR-005: Access control |
| Server-side enforcement at every entry point | Operation permissions are enforced centrally in
Vault (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.
Secret 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. |
Vault;
Secret; route
configuration plus controller code |
| No privileged short-circuit in the grant lookup | Grant evaluation deliberately avoids
Backend, which returns true
unconditionally for admins |
has |
| Administrative override is a single, withdrawable seam |
admin; disableAdminOverride in the
hardened profile, pinnable outside the backend | vault:break-glass --status reporting
adminOverrideDisabledEffective |
| Emergency access is time-boxed, justified and evidenced |
Break — mandatory reason, TTL clamped to 1–60
minutes, audit written before the grant | break_glass_activated /
break_glass_deactivated rows;
Break-glass mode |
| Disabled accounts cannot act on a stale session | Defence-in-depth disable checks in
Access and
Break | Code review |
| Attributable impersonation for headless code |
Technical — validates the target user,
restores scope on exceptions, never mutates
$GLOBALS | actor_type = 'technical' audit rows;
ADR-029: Scoped technical-actor identity for headless use |
| Unattended CLI access closed by default | allowCliAccess defaults to 0 | Configuration; Configuration |
| Frontend placeholder resolution restricted to published identifiers |
Frontend — 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 |
| Record copy and delete fail closed across vault fields |
Data — 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; ADR-018: FlexForm secret lifecycle management |
| CLI grant narrowed to low-risk operations | cliAllowedOperations defaults to
secret.use,secret.create,secret.rotate; reveal, delete,
audit export, master-key rotation and vault configuration must
be added explicitly | vault:doctor finding cli.allowed_operations |
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 |
Audit on read, create, update, rotate,
delete, metadata change and access_denied | tx_nrvault_audit_log;
ADR-006: Audit logging |
| 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
Vault, and a metadata change (or
value-less record creation) whose audit write fails is reverted
by
Secret — no mutation persists without its audit
entry. |
Break,
Vault,
Secret |
| 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 |
Audit rejects unknown actions loudly | Code review |
| Tamper detection on stored entries | HMAC-SHA256 hash chain; verification with
hash_ | vault:audit-verify;
ADR-023: Audit hash chain HMAC consideration |
| 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 |
| 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 |
Audit — 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 |
| Reset detection through external evidence |
Chain — 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 |
| 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 |
| Machine-readable alerting |
Audit with stable reason codes and
is | AuditIntegrityAlertEvent |
| Scheduled, unattended detection |
Audit,
Audit | Scheduler records; Scheduler tasks |
| No secrets in logs or exceptions | [REDACTED] placeholders; token redaction in transport errors;
response bodies not surfaced | Code review;
Secret |
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 |
Ajax; HTTP response inspection |
| Client-side exposure is bounded | startRevealLifecycle() — 30 s auto-hide, wipe on
visibilitychange and pagehide | vault-;
Revealed plaintext in the browser cannot be zeroized |
| 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 |
| 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 |
| Recoverability of encrypted data | Documented separation of database and key-material backups, with a probe-decrypt verification step | Backup and restore |
| Detection of plaintext secrets elsewhere in the installation |
Secret, vault:scan | Scan output |
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 |
Security consulted by provider selection, access
control and audit verification | Exact technical differences |
| Fail closed on misconfiguration | Unknown profile → 1753900001; forbidden provider →
1753900002; no hardened fallback or auto-detection |
Master;
Verification procedures |
| Security-critical settings can be placed out of admin reach |
$GLOBALS pins | config/;
--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 |
Secure; scheme restricted to http/https;
private targets need allowed_hosts | ADR-026: DNS-rebinding defence via CURLOPT_RESOLVE |
| 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. is not referenced by any workflow — see
Development and release evidence | .github/;
Evidence collection |
| Secret scanning on every pull request | gitleaks job against the in-repo .gitleaks.,
reporting to GitHub code scanning | .github/ |
| Workflow-definition auditing | zizmor job over .github/, configured by
.github/ | .github/ |
| Broad compatibility test matrix | PHP 8.2–8.5 × TYPO3 ^13.4 and ^14.3, unit and functional,
coverage uploaded | .github/ |
| 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/; verify with
gh attestation verify |
| Release evidence is itself tamper-evident | actions/attest-build-provenance over
release-, in the bundle job
with id-token: write + attestations: write. The attestation
outlives the 90-day run-artifact retention | .github/; verify with
`gh attestation verify release-` |
| 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. 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 |
| Continuous security posture measurement | OpenSSF Scorecard on schedule and on default-branch pushes | .github/; Scorecard results |
| Documented incident and emergency procedures | Runbooks for exposure, tampering and break-glass review | Incident response |
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 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. |
| 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.. An assessment verifies them against the
resolved reusable and the produced release assets, not against
this repository's workflow files alone. |