---
title: "Decommissioning"
manual: "nr-vault"
version: "1.0"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-vault:operations-decommissioning@1.0"
source: "Operations/Decommissioning.rst"
rendered: "2026-09-18T07:37:50+00:00"
---

# Decommissioning {#decommissioning}

Retiring a vault, or an installation that contains one. The order matters, and
one step is irreversible by design.

## Secret disposal {#secret-disposal}

> [!WARNING]
> **\`\`vault:delete\`\` is a soft delete.** `SecretRepository::delete()`
> sets `deleted = 1` and updates `tstamp`; the row — with its ciphertext,
> its wrapped DEK and its nonces — stays in `tx_nrvault_secret`. The same
> is true of `vault:cleanup-orphans`, which routes through the same
> `VaultService::delete()`.
>
> There is no hard-delete path in the extension. Do not read "deleted" as
> "gone".

That is the right default for a live system: it keeps the operation auditable,
and the row is unreadable to anyone without the master key anyway. It does
**not** make the delete reversible. The vault has no restore operation, and
the backend refuses TYPO3's `undelete` command on `tx_nrvault_secret` — a
restore would hand back the ciphertext, the wrapped DEK, `frontend_accessible`
and both ACL tiers with no vault check at all, and nothing in the audit chain
would say it happened. Recovering a deleted secret means acting on the database
directly, deliberately and visibly. See
[What the backend will not do to a secret record](https://docs.typo3.org/permalink/netresearch/nr-vault:usage-record-operations-refused@1.0).

The soft delete is the wrong assumption when decommissioning. For actual
disposal you have two options, and they compose:

**Crypto-erasure (recommended).** Destroy the master key and every secret in
the vault becomes permanently unreadable in one step — including soft-deleted
rows, including rows in every backup already taken. This is the only measure
that reaches copies you no longer control. See
[Key destruction](https://docs.typo3.org/permalink/netresearch/nr-vault:operations-decommissioning-keys@1.0).
The same holds for rows deleted before a master-key rotation:
`vault:rotate-master-key` re-wraps live and disabled secrets only, so a
soft-deleted row stays wrapped under the retired key and becomes unreadable
once that key is destroyed.

**Row removal (for completeness).** Drop or truncate the tables at the
database level once the audit-retention obligations below are satisfied:

**Only after the evidence export is complete and verified**

```sql
-- Secrets, including soft-deleted rows, and their two ACL relation
-- tables. Dropping only the secret table leaves the MM rows behind.
DROP TABLE tx_nrvault_secret;
DROP TABLE tx_nrvault_secret_begroups_mm;
DROP TABLE tx_nrvault_secret_writegroups_mm;
-- Audit chain. Check your retention obligations FIRST.
DROP TABLE tx_nrvault_audit_log;

-- Vault state also lives in the core registry: the chain tip anchor,
-- and the break-glass session plus per-sink delivery state.
DELETE FROM sys_registry WHERE entry_namespace = 'tx_nrvault_audit_anchor';
DELETE FROM sys_registry WHERE entry_namespace = 'tx_nrvault';
```

> [!NOTE]
> Row removal on its own is weaker than it looks: database backups,
> replicas, binlogs, filesystem snapshots and storage-level copies keep the
> ciphertext for as long as their own retention allows. Crypto-erasure is
> what makes those copies worthless. Do both if the policy demands it; do
> crypto-erasure if you can only do one.

## Rotate credentials out, do not just delete them {#rotate-credentials-out-do-not-just-delete-them}

A secret in the vault is a *copy* of a credential that also exists in the
system it authenticates against. Deleting the vault copy does not revoke
anything.

Before disposal, for every secret still in use: rotate the credential at its
origin — the API provider, the SMTP relay, the OAuth client, the deploy key —
so that the value the vault held stops working. Then dispose of the vault
copy. In that order: a rotation performed after the vault is gone has to be
done without the ability to read what is being replaced.

## Final evidence export {#final-evidence-export}

Do this **before** anything destructive. Once the master key is gone, the
audit chain can no longer be verified — the HMAC key was derived from it — so
a verification run after key destruction proves nothing.

1.  **Verify the chain while the key still exists**, and keep the output:

    ```bash
    vendor/bin/typo3 vault:audit-verify > decommission-verify.txt
    ```
1.  **Publish a final anchor**, so the tip is witnessed externally at the
    moment of decommissioning:

    ```bash
    vendor/bin/typo3 vault:audit-anchor
    ```

    > [!NOTE]
    > Both commands assert an operation permission (breaking change; they
    > previously asserted none): `vault:audit-verify` needs `audit.view`,
    > `vault:audit-anchor` needs `vault.configure`, and the export in the
    > next step needs `audit.export`. All three are excluded from the
    > [cliAllowedOperations](https://docs.typo3.org/permalink/netresearch/nr-vault:confval-ext-nrvault-cliallowedoperations@1.0) default, so from a shell
    > they need `allowCliAccess = 1` **and** the operation in that
    > allowlist — or, better for an evidence run, a named technical actor, so
    > the record names who collected the evidence. Sort the grant out before
    > the decommissioning window: after the master key is destroyed there is
    > no second chance at this output.
1.  **Export the audit log** in full, over the entire retained period, with
    the hash columns included — `uid`, `previous_hash`, `entry_hash`,
    `hmac_key_epoch`. Without them the export is a log, not evidence.
1.  **Collect the external evidence** from where it actually lives: the anchor
    file, the syslog archive, the SIEM's retained records. This is the half
    the installation cannot fabricate, and after decommissioning it is the
    only half left.
1.  **Record the decommissioning itself** — date, operator, which artefacts
    were exported, which tables were dropped, when and how the key was
    destroyed. A `TABLE_RESET` finding against the final anchor is
    indistinguishable from a malicious reset without this record.

[Evidence collection](https://docs.typo3.org/permalink/netresearch/nr-vault:auditor-evidence-collection@1.0) lists the artefacts in the form an assessor
expects.

## Retention obligations versus deletion {#retention-obligations-versus-deletion}

These pull in opposite directions and nr-vault cannot resolve the conflict for
you — it can only make sure you notice it.

-   **The secrets are the deletion obligation.** Credentials have no reason to
    outlive the system that used them, and a retained ciphertext is a retained
    risk for as long as any copy of the key might exist.
-   **The audit log is the retention obligation.** It is the record of who
    accessed which credential and when — frequently the artefact an audit
    regime requires to be kept, and often for years. It contains no secret
    values.

**They are separable, and separating them is the answer.** Destroy the key and
drop `tx_nrvault_secret`; keep the exported audit evidence for as long as
policy requires. The exported chain remains internally verifiable — each row's
`previous_hash` still links to its predecessor — but note honestly what is
lost: **without the master key, HMAC recomputation is no longer possible**, so
from epoch 1 upwards the export becomes a *sequence* whose links can be
inspected rather than a chain whose authenticity can be re-proved. That is why
the verification output from step 1 above matters: it is the last point at
which authenticity was demonstrable, and it must be captured then, not later.

> [!NOTE]
> `tx_nrvault_audit_log` also holds personal data — `actor_username`,
> `ip_address`, `user_agent`. A deletion request under data-protection
> law collides with the tamper-evident design: editing or removing a row
> breaks the chain by construction, which is the entire point.
> [ADR-017: Audit metadata retention](https://docs.typo3.org/permalink/netresearch/nr-vault:adr-017-audit-metadata-retention@1.0) is where that trade-off is
> recorded; resolve it as a policy decision before decommissioning, not
> during.

## Key destruction {#key-destruction}

Irreversible. There is no escrow and no vendor-side reset — see
[Master key loss is data loss](https://docs.typo3.org/permalink/netresearch/nr-vault:security-known-limitations-key-loss@1.0).

| Provider | How to destroy the key |
| --- | --- |
| `typo3` | Rotate or remove `SYS/encryptionKey` in `config/system/settings.php`, and destroy every backup of that file. **Harder than it sounds** — the config directory is usually in more backups than anyone expects, and `encryptionKey` is also used by TYPO3 core for unrelated purposes, so removing it has effects beyond the vault. |
| `file` | Delete the key file and every copy, including the separate key backup. On a copy-on-write filesystem or a storage layer with snapshots, deletion is not erasure — destroy the snapshots too. |
| `env` | Remove the variable from the injection mechanism and restart. Then check where it was *also* recorded: CI secret stores, deployment manifests, container inspection output, shell history. |
| `transit` | **The cleanest case.** Delete the transit key in HashiCorp Vault (or revoke every policy granting `decrypt` on it) and the locally stored wrapped blob becomes undecryptable immediately — including in every backup that contains it. Then delete the wrapped file as well. |

1.  Confirm the evidence export is complete and stored elsewhere.
1.  Confirm no other installation shares the key material. A key file copied
    to a staging system is still a live key.
1.  Destroy the key.
1.  Verify destruction by attempting a read: a probe decrypt must now fail
    with `Master key not found at: …` or
    `Authentication failed - data may have been tampered with`. A successful
    read means a copy survived somewhere — find it.
1.  Drop the tables, if row removal is also required.

## Installation cleanup {#installation-cleanup}

-   **Remove the sinks** last, not first: they are what records the
    decommissioning steps. Turn off `auditSinkWebhookEnabled` and friends
    only after the final anchor has been published.
-   **Unschedule the tasks** — the audit anchor and audit verify scheduler
    tasks, and orphan cleanup — or they will start failing loudly against a
    vault that no longer exists.
-   **Revoke the grants.** Remove the `tx_nrvault:*` custom options from
    `be_groups`, so a later reinstall does not silently inherit a permission
    model nobody reviewed.
-   **Unpin the settings.** Remove the
    `$GLOBALS['TYPO3_CONF_VARS']['SYS']['nrVault']` entries from
    `config/system/additional.php`, and the extension configuration
    block itself from `config/system/settings.php` — the pins are only
    the part that lives in `additional.php`.
-   **Remove the extension**, and remember that removing it does not remove
    its tables.
-   **Clean up the KMS side** if `transit` was used: revoke the token, remove
    the policy, delete the transit key.
