Configuration 

Extension configuration 

Configure nr-vault in Admin Tools > Settings > Extension Configuration.

storageAdapter

storageAdapter
Type
string
Default
local
Options
local

Where secrets are stored.

local
Store secrets in the TYPO3 database (default). Secrets are encrypted with envelope encryption before storage.

securityProfile

securityProfile
Type
string
Default
standard
Options
standard, hardened

The vault's operating profile — a single, internally consistent policy rather than a bag of independent toggles. Enforcement happens in code (provider selection, access control, audit anchoring), never only in documentation.

standard
Secure defaults with zero-configuration TYPO3 integration.
hardened
Fail-closed and audit-ready. Requires an explicit external master-key provider (file or env), disables provider auto-detection and any fallback to the TYPO3 encryption key, and makes vault operations refuse to run on a misconfigured or unavailable provider. It is also the prerequisite for disableAdminOverride.

An unrecognised value throws rather than degrading to standard — a typo in a hardened deployment must never weaken the effective policy.

masterKeyProvider

masterKeyProvider
Type
string
Default
typo3
Options
typo3, file, env, transit

How to retrieve the master encryption key.

typo3
Derive from TYPO3's encryption key. This is the recommended default as it requires no additional configuration and works out of the box.
file
Read from a file on the filesystem.
env
Read from an environment variable.
transit
Unwrap through HashiCorp Vault's transit secrets engine. Only the Vault-encrypted ciphertext is stored locally — see HashiCorp Vault Transit provider.

masterKeySource

masterKeySource
Type
string
Default
NR_VAULT_MASTER_KEY

Source location for the master key. Interpretation depends on the provider:

  • file: Path to the key file (e.g., /secure/path/vault.key).
  • env: Environment variable name (e.g., NR_VAULT_MASTER_KEY).
  • typo3: Not used (key derived from TYPO3's encryption key).
  • transit: Not used (configured via the hashicorp.* settings below).

hashicorp.address

hashicorp.address
Type
string
Default
(empty)

Vault server base address, e.g. https://vault.example.com:8200. Required by the transit master key provider.

hashicorp.authMethod

hashicorp.authMethod
Type
string
Default
token
Options
token, kubernetes, approle

Vault authentication method. The transit master key provider implements token only and refuses to start on the other values rather than silently downgrading. See HashiCorp Vault Transit provider.

hashicorp.tokenEnvVar

hashicorp.tokenEnvVar
Type
string
Default
VAULT_TOKEN

Name of the environment variable holding the Vault token. Read in preference to hashicorp.token.

hashicorp.token

hashicorp.token
Type
string
Default
(empty)

Vault token stored in the extension configuration. Development fallback only — a token stored here is readable in the Install Tool and ends up in configuration exports. Prefer the environment variable.

hashicorp.transitMount

hashicorp.transitMount
Type
string
Default
transit

Mount path of the transit secrets engine, without the /v1/ prefix. Nested mounts such as platform/transit are supported.

hashicorp.transitKeyName

hashicorp.transitKeyName
Type
string
Default
nr-vault-master

Name of the transit key that wraps the vault master key.

hashicorp.transitWrappedKeyPath

hashicorp.transitWrappedKeyPath
Type
string
Default
(empty)

File holding the Vault-wrapped master key. Empty resolves to <var-path>/secrets/vault-master.key.transit. The file contains ciphertext only, never key material.

hashicorp.path

hashicorp.path
Type
string
Default
secret/data/typo3

Path prefix for secrets in Vault. Reserved — it belongs to the not-yet-implemented HashiCorp storage adapter and has no effect on the transit master-key provider, which uses the transit* settings above.

AWS Secrets Manager 

aws.region

aws.region
Type
string
Default
(empty)

AWS region for Secrets Manager, for example eu-central-1.

aws.secretPrefix

aws.secretPrefix
Type
string
Default
typo3/

Prefix for secret names in AWS Secrets Manager.

CLI access 

allowCliAccess

allowCliAccess
Type
boolean
Default
false

Allow CLI commands to access secrets without a backend user session.

cliAccessGroups

cliAccessGroups
Type
string
Default
empty

Comma-separated list of backend user group UIDs that CLI can access. Empty means all secrets are accessible when CLI access is enabled.

cliAllowedOperations

cliAllowedOperations
Type
string
Default
secret.use,secret.create,secret.rotate

Operation permissions the unattributed CLI actor may hold while allowCliAccess is on. The default covers deployment automation (store, rotate, consume). High-risk operations — secret.reveal ( vault:retrieve printing plaintext), secret.delete, audit.export, master_key.rotate ( vault:rotate-master-key ), vault.configure — are excluded by default and must be added explicitly where a workflow genuinely needs them. Prefer a named technical actor (TechnicalActorContext::runAs()) over widening this list: the audit trail then names the responsible identity. Note that the scheduled orphan cleanup deletes secrets and therefore needs secret.delete when it runs as the bare CLI actor.

Three of those five change what a CLI command can do today: secret.reveal, secret.delete and master_key.rotate. audit.export and vault.configure gate the corresponding backend actions — the audit module's export and the migration wizard — and vault:audit --export asserts no operation permission of its own. Withholding them here still matters: the list is the record of what the unattributed CLI actor has been granted, and a CLI surface for either would inherit it.

frontendPlaceholderLegacyCli

frontendPlaceholderLegacyCli
Type
boolean
Default
false

Restore the pre-hardening command-line behaviour, in which every frontend-accessible %vault(id)% placeholder resolves on the CLI, whoever authored the string it appears in.

Off by default: the CLI enforces the same allow-set as a frontend request, so an identifier has to be published through an admin-only source — TypoScript setup, site configuration, plugin.tx_nrvault.frontendResolvableIdentifiers , or FrontendPlaceholderPolicyInterface::allowIdentifier() . See ADR-035: Per-request allow-set of frontend-resolvable identifiers.

Enable it only for a deployment whose internal render jobs genuinely need the old behaviour and cannot publish their identifiers. The narrower remedy is one frontendResolvableIdentifiers line, or one allowIdentifier() call in the job itself.

The flag is CLI-only: it never weakens a web request, and it never changes which secrets exist — a secret without frontend_accessible stays unreadable either way.

auditLogRetention

auditLogRetention
Type
integer
Default
365

Number of days to retain audit log entries. Set to 0 for unlimited retention.

auditReads

auditReads
Type
boolean
Default
true

Log every secret read to the audit log. Disable only for high-throughput scenarios where read logging is not required.

disableAdminOverride

disableAdminOverride
Type
boolean
Default
false

Remove the unconditional "administrators and system maintainers may do anything" bypass — both the operation permissions and the per-secret read/write/delete tiers. Administrators then hold exactly what their groups were granted and reach only the secrets they own or share a group with.

Only effective when securityProfile is hardened. In the standard profile the flag is inert — a lockout guard, since setting it without the rest of the hardened policy is more likely a misunderstanding than a decision. vault:break-glass --status reports adminOverrideDisabledEffective so the mismatch is visible.

See Disabling the admin override for what exactly is removed.

auditHmacEpoch

auditHmacEpoch
Type
integer
Default
3

Hash-algorithm version marker for the audit log hash chain:

0
Legacy SHA-256 without HMAC.
1
HMAC-SHA256 over identity fields only.
2
HMAC-SHA256 over identity and forensic fields (success, error_message, reason, ip_address, user_agent, context).
3
Additionally binds the epoch selector hmac_key_epoch itself plus the attribution fields actor_type, actor_username, actor_role and request_id.

Epoch 2 binds the forensic surface into the chain, so a DB-write attacker cannot flip success or rewrite error_message without breaking it. The shipped default (3) additionally closes the algorithm-downgrade forgery — with the epoch selector inside the hash, an attacker can no longer relabel a row to epoch 0, re-sign it with keyless SHA-256 and keep the chain consistent — and makes actor attribution tamper-evident.

After raising this value, run the Install Tool wizard Migrate audit hash chain (or the vault:audit-migrate-hmac command) to re-hash existing rows. See ADR-023: Audit hash chain HMAC consideration.

vault:doctor grades the three states apart under audit.hmac_epoch: pass at 3 and above, warning at 1 and 2 naming the columns that epoch leaves outside the MAC, and critical at 0. A stalled or partially applied migration is the usual way an installation ends up at 1 or 2, so treat that warning as "the migration did not finish", not as a pending nice-to-have.

auditAnchorRequired

auditAnchorRequired
Type
boolean
Default
false

Treat a missing audit chain tip anchor as an error instead of a warning.

The anchor pins "audit row uid = A still exists with entry_hash = H" in sys_registry, signed with a key that is not in the database. It is what makes removal of the end of the audit log detectable — a truncation leaves no UID gap and no broken link, so the chain walk alone reports it as valid. See ADR-034: Audit chain tip anchor.

The anchor arms itself on the next audit log write. Leave this setting off until that has happened: before the first write after the upgrade, an installation that never had an anchor is indistinguishable from one whose anchor an attacker deleted, and every chain would report invalid.

Once enabled, an attacker with database write access can no longer silence the control by deleting the anchor row, because this setting lives in a configuration file rather than the database. It does two things, not one: verification reports a missing anchor as an error, and ordinary audit writes stop arming an anchor that is not there. Without the second half the error would clear itself within seconds — one audited read is enough to mint a fresh anchor on a truncated log.

The second half applies whatever the log contains, including an empty one. "The log still holds an earlier entry" cannot stand in for "this anchor was never armed": the audit write that would arm the anchor has just inserted the only row the chain has, so a log emptied outright is indistinguishable from a new installation.

Arming is therefore always explicit while this is on: vault:audit --reset-anchor arms the anchor and writes the reset into the chain. That includes the first arming — one more reason to enable the setting only after the anchor exists.

Requires auditHmacEpoch >= 1; at epoch 0 the chain is keyless and the anchor is disabled.

encryptionAlgorithm

encryptionAlgorithm
Type
string
Default
(empty)

AEAD algorithm recorded per secret at encrypt time. Empty selects XChaCha20-Poly1305, which is the recommended value: it is available in every libsodium build, and its 24-byte nonce makes random-nonce collisions a non-concern, so vault contents stay portable across hosts with differing CPU capabilities.

Set it to aes256gcm only on hosts with hardware AES support. An unknown or host-unavailable value makes encryption fail loudly at the crypto boundary rather than silently falling back.

preferXChaCha20

preferXChaCha20
Type
boolean
Default
false

Prefer XChaCha20-Poly1305 over AES-256-GCM for legacy secrets only — those stored under encryption version 1, before the per-secret algorithm marker existed. New secrets record their algorithm explicitly and take it from encryptionAlgorithm; this setting has no effect on them.

External audit sinks 

The database table tx_nrvault_audit_log is the chain-authoritative audit sink. External sinks are additional, best-effort copies whose purpose is to put audit evidence somewhere a database-write attacker cannot reach.

Two properties are worth stating up front:

  • A sink failure never fails the audited vault operation. Fan-out happens after the chain row has committed and after the audit lock has been released, so a slow or broken destination cannot roll back a secret operation or serialise every other vault call behind itself. Failures are logged, counted, and reported by vault:audit-verify.
  • Only an external sink makes a full audit table reset detectable. See vault:audit-anchor for why the in-database hash chain structurally cannot catch a truncate-and-rebuild.

Under the hardened security profile (securityProfile = hardened), having no usable external sink is reported as a NO_EXTERNAL_SINK finding.

auditSinkSyslogEnabled

auditSinkSyslogEnabled
Type
boolean
Default
false

Mirror every audit entry, chain-tip anchor and integrity alert to the local syslog as an RFC 5424 structured-data message on facility local0. The cheapest useful sink: on any host with a log shipper the audit trail leaves the TYPO3 database with no extra infrastructure.

auditSinkSyslogIdent

auditSinkSyslogIdent
Type
string
Default
'nr-vault'

openlog() ident, which becomes RFC 5424's APP-NAME. Vary it when several TYPO3 instances share a host. An empty value falls back to the default — an unattributable syslog line would defeat the purpose of the sink.

auditSinkFileEnabled

auditSinkFileEnabled
Type
boolean
Default
false

Append audit evidence to newline-delimited JSON files (one JSON object per line). This is also the sink that writes the chain-tip anchors vault:audit-verify reads back, so enabling it is the minimum for table-reset detection.

Files are created with mode 0600 and only ever appended to.

auditSinkFilePath

auditSinkFilePath
Type
string
Default
'' (resolves to var/log/nr-vault-audit.ndjson)

Absolute path of the append-only audit entry stream.

auditSinkAnchorPath

auditSinkAnchorPath
Type
string
Default
'' (resolves to var/log/nr-vault-audit-anchor.ndjson)

Absolute path of the append-only chain-tip anchor stream, written by vault:audit-anchor and read back by vault:audit-verify. Integrity alerts are appended here too, so this one file tells the whole chain-health story.

Deliberately separate from auditSinkFilePath: this is the evidence that survives a table reset, so point it at append-only or off-host storage. Verification always takes the anchor with the highest sequence, never the last line — appending a low-sequence anchor therefore cannot weaken the baseline.

The public-web-root refusal described above applies to this path as well.

auditSinkWebhookEnabled

auditSinkWebhookEnabled
Type
boolean
Default
false

POST every audit entry, anchor and integrity alert as JSON to an HTTP endpoint — typically a SIEM collector. Each payload carries a type discriminator (entry, anchor, alert) so one endpoint can route all three.

When enabled, the webhook also receives integrity alerts by default through the built-in nr-vault/audit-integrity-alert-sinks event listener.

auditSinkWebhookUrl

auditSinkWebhookUrl
Type
string
Default
''

https:// (or http://) endpoint receiving the payloads. Only http/https are accepted, so a file:// or php:// value cannot turn audit fan-out into a local write.

auditSinkStaleDeliveryHours

auditSinkStaleDeliveryHours
Type
integer
Default
24

Hours after which the last successful external delivery of an enabled sink counts as stale for vault:doctor (finding audit.sink_state.<sink>: warning under the standard profile, critical under hardened). The per-sink delivery state — last success, last failure, consecutive failures — is persisted in sys_registry by the sink registry, so a freshly started process still knows a collector has been unreachable for days. Use vault:doctor --active-probes to verify delivery end-to-end.

Scheduling 

Two scheduler tasks accompany the CLI commands:

Vault Audit Chain Anchoring
Publishes the current chain tip (vault:audit-anchor). The interval is the blind window — entries written since the last anchor are what an attacker who resets the table can still hide. Hourly is a reasonable starting point.
Vault Audit Integrity Verification
Verifies the chain against the anchor (vault:audit-verify) and dispatches an integrity alert event per finding. Set Fail on tamper evidence only while sinks are still being rolled out, so a pending integration does not keep the task permanently red and train operators to ignore it.

Master key providers 

TYPO3 provider (default) 

Uses TYPO3's built-in encryption key to derive the master key. This is the recommended default because:

  • Zero configuration: Works immediately after installation.
  • No server access required: Ideal for users without shell access.
  • Unique per installation: Each TYPO3 instance has its own key.
  • Already secured: TYPO3's encryption key is already protected.

The master key is derived from the encryption key using HKDF-SHA256 with a nr-vault-specific context, ensuring it cannot be used to compromise other TYPO3 functionality.

Master key derivation (internal)
// How it works internally
$masterKey = hash_hkdf(
    'sha256',
    $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'],
    32,
    'nr-vault-master-key'
);
Copied!

File provider 

Store the master key in a file with restrictive permissions:

Create master key file
# Generate a new key
openssl rand -base64 32 > /secure/path/vault-master.key
chmod 0400 /secure/path/vault-master.key
Copied!

Configure in extension settings:

Environment provider 

Store the master key in an environment variable:

Set master key via environment
export NR_VAULT_MASTER_KEY="base64-encoded-key"
Copied!

Configure in extension settings:

This is ideal for containerized deployments where secrets are injected via environment variables.

HashiCorp Vault Transit provider 

The master key is generated once, wrapped by Vault's transit secrets engine, and only the resulting ciphertext (vault:v1:…) is stored on the local filesystem. Every start-up unwraps it through a Vault API call, so no key material sits at rest next to the database.

Set up the transit engine and a key:

Enable transit and create the wrapping key
vault secrets enable transit
vault write -f transit/keys/nr-vault-master
Copied!

Grant the TYPO3 instance encrypt and decrypt on that one key — nothing else:

Vault policy nr-vault-transit.hcl (HCL)
path "transit/encrypt/nr-vault-master" {
  capabilities = ["update"]
}

path "transit/decrypt/nr-vault-master" {
  capabilities = ["update"]
}
Copied!
Apply the policy and issue a token
vault policy write nr-vault-transit nr-vault-transit.hcl
vault token create -policy=nr-vault-transit -period=768h
Copied!

Provide the token through the environment, never in the extension configuration:

Vault token via environment
export VAULT_TOKEN="hvs...."
Copied!

Configure in extension settings:

Then create and wrap the master key:

Initialize the vault with a Vault-wrapped master key
vendor/bin/typo3 vault:init
Copied!

The wrapped key is written to hashicorp.transitWrappedKeyPath with 0600 permissions. Back that file up together with the Vault key: the blob is worthless without Vault, and Vault is worthless without the blob.

Rotating the transit key (vault write -f transit/keys/nr-vault-master/rotate) re-wraps future ciphertexts without touching any secret in the TYPO3 database. Rotating the vault master key itself is unchanged — the new key is wrapped through Vault and the local blob replaced:

Rotate the vault master key
vendor/bin/typo3 vault:rotate-master-key
Copied!

Access control 

Access to secrets is controlled by:

  1. Ownership: The user who created the secret has full access.
  2. Group membership: Secrets can be shared with backend user groups, in two tiers — allowed_groups grants read, write_groups grants read and write. Neither grants delete.
  3. Admin access: Backend administrators have access to all secrets — unless the hardened profile withdrew that bypass via disableAdminOverride, after which an administrator holds only what their own groups grant.
  4. CLI access: Configurable via allowCliAccess, narrowed by cliAllowedOperations.
  5. Operation permissions: independently of all of the above, every privileged operation (secret.create, secret.rotate, secret.delete, secret.manage_policy, …) is granted per backend user group and asserted centrally. Passing the per-secret tiers never implies holding the operation. See Operation permissions.

Analytics thresholds 

These decide when the usage-analytics dashboard and the secrets.dead / secrets.never_rotated readiness controls flag a secret. They are reporting thresholds only — nothing expires or is deleted because of them.

staleNeverReadDays

staleNeverReadDays
Type
integer
Default
30

A secret that has never been read and is older than this many days is flagged dead — a strong candidate for redaction, since nothing has ever consumed it.

staleNotReadDays

staleNotReadDays
Type
integer
Default
90

A secret not read for this many days is flagged dead.

staleNeverRotatedDays

staleNeverRotatedDays
Type
integer
Default
180

A secret not rotated — or, if it never was, not created — within this many days is flagged never rotated.

Context-based scoping 

Organize secrets by context for easier management:

  • payment - Payment gateway credentials.
  • email - Email service API keys.
  • api - Third-party API tokens.
  • database - External database credentials.

Contexts are user-defined strings that help organize and filter secrets.

Site configuration integration 

Use the %vault(identifier)% syntax in site configuration files:

config/sites/main/config.yaml
settings:
  payment:
    stripeSecretKey: '%vault(stripe_api_key)%'
  email:
    mailchimpKey: '%vault(mailchimp_key)%'
Copied!

References are resolved on demand, in the reading context, via SiteConfigurationVaultProcessor — not automatically when the site configuration is loaded:

Resolve at read time
use Netresearch\NrVault\Configuration\SiteConfigurationVaultProcessor;
use TYPO3\CMS\Core\Utility\GeneralUtility;

$site = $request->getAttribute('site');
$processor = GeneralUtility::makeInstance(SiteConfigurationVaultProcessor::class);
$config = $processor->processConfiguration($site->getConfiguration(), $site);
$stripeKey = $config['settings']['payment']['stripeSecretKey'];
Copied!

This keeps sensitive values out of version control while allowing configuration through the standard TYPO3 site settings.

Frontend-accessible secrets 

By default, secrets cannot be resolved in frontend context (TypoScript). To allow a secret to be used in TypoScript:

  1. Create the secret with frontend_accessible metadata.
  2. Use the %vault(identifier)% syntax in TypoScript.

frontend_accessible says the secret may appear in a page; it does not say which placeholders get expanded. In a frontend request — and on the command line, unless frontendPlaceholderLegacyCli is on — the identifier must also be published through an admin-only source — the TypoScript setup array, the site configuration, plugin.tx_nrvault.frontendResolvableIdentifiers , or FrontendPlaceholderPolicyInterface::allowIdentifier() . Step 2 satisfies that on its own; an identifier used only in a Fluid template file or an eID handler needs one of the last two. See Which placeholders resolve in the frontend.

Store frontend-accessible secret
$this->vaultService->store(
    'google_maps_key',
    $apiKey,
    [
        'metadata' => [
            'frontend_accessible' => true,
        ],
    ],
);
Copied!