API 

This chapter documents the public API of the nr-vault extension.

VaultService 

The main service for interacting with the vault.

interface VaultServiceInterface
Fully qualified name
\Netresearch\NrVault\Service\VaultServiceInterface

Main interface for vault operations.

store ( string $identifier, string $secret, array $options = []) : void

Store a secret in the vault. $secret is a #[\SensitiveParameter].

param string $identifier

Unique identifier for the secret.

param string $secret

The secret value to store (#[\SensitiveParameter]).

param array $options

Optional configuration: owner (int BE-user UID), groups (int[] BE-group UIDs), context (string), expiresAt (int|DateTimeInterface|null), metadata (array), description (string), scopePid (int).

throws ValidationException

If the identifier is invalid.

throws EncryptionException

If encryption fails.

retrieve ( string $identifier)

Retrieve a secret from the vault.

param string $identifier

The secret identifier.

returntype

string|null

throws AccessDeniedException

If user lacks read permission.

throws SecretExpiredException

If the secret has expired.

Returns

The decrypted secret value or null if not found.

exists ( string $identifier) : bool

Check if a secret exists.

param string $identifier

The secret identifier.

Returns

True if the secret exists.

delete ( string $identifier, string $reason = '') : void

Delete a secret from the vault.

param string $identifier

The secret identifier.

param string $reason

Optional reason for deletion (logged).

throws SecretNotFoundException

If secret doesn't exist.

throws AccessDeniedException

If user lacks delete permission.

rotate ( string $identifier, string $newSecret, string $reason = '') : void

Rotate a secret with a new value. $newSecret is a #[\SensitiveParameter].

param string $identifier

The secret identifier.

param string $newSecret

The new secret value (#[\SensitiveParameter]).

param string $reason

Optional reason for rotation (logged).

list ( ?string $pattern = null) : array

List accessible secrets.

param string|null $pattern

Optional pattern to filter identifiers (supports the * wildcard).

Returns

A list<SecretMetadata> of secret metadata DTOs (Netresearch\NrVault\Domain\Dto\SecretMetadata).

getMetadata ( string $identifier) : SecretDetails

Get metadata for a secret without retrieving its value.

param string $identifier

The secret identifier.

throws SecretNotFoundException

If secret doesn't exist.

throws AccessDeniedException

If user lacks permission.

Returns

A SecretDetails DTO (Netresearch\NrVault\Domain\Dto\SecretDetails) with identifier, description, owner, groups, version, etc.

clearCache ( ) : void

Clear the request-scoped cache of decrypted secrets, securely wiping cached plaintext from memory.

http ( ) : VaultHttpClientInterface

Get an HTTP client that can inject secrets into requests.

Returns

A PSR-18 compatible vault-aware HTTP client.

EncryptionService 

The crypto boundary: libsodium envelope encryption (per-secret DEK wrapped by the master key).

interface EncryptionServiceInterface
Fully qualified name
\Netresearch\NrVault\Crypto\EncryptionServiceInterface

Low-level encryption operations. Most callers use VaultServiceInterface instead.

encrypt ( string $plaintext, string $identifier) : EncryptedData

Encrypt a plaintext value with a unique DEK. $plaintext is a #[\SensitiveParameter].

param string $plaintext

The value to encrypt (#[\SensitiveParameter]).

param string $identifier

Secret identifier (used as AAD).

throws EncryptionException

If encryption fails.

Returns

An EncryptedData value object (Netresearch\NrVault\Crypto\EncryptedData) holding the ciphertext, encrypted DEK, and nonces.

decrypt ( string $encryptedValue, string $encryptedDek, string $dekNonce, string $valueNonce, string $identifier, int $encryptionVersion = 1, string $encryptionAlgorithm = '') : string

Decrypt a previously encrypted value. $encryptedValue and $encryptedDek are #[\SensitiveParameter].

param string $encryptedValue

Base64-encoded ciphertext (#[\SensitiveParameter]).

param string $encryptedDek

Base64-encoded encrypted DEK (#[\SensitiveParameter]).

param string $dekNonce

Base64-encoded DEK nonce.

param string $valueNonce

Base64-encoded value nonce.

param string $identifier

Secret identifier (used as AAD).

param int $encryptionVersion

Stored per-secret encryption version. Defaults to ENCRYPTION_VERSION_LEGACY (1), where the algorithm is derived from host capabilities.

param string $encryptionAlgorithm

Stored per-secret algorithm marker. Required for version 2+, must be '' for version 1.

throws EncryptionException

If decryption fails or the marker is unknown on this host.

Returns

The decrypted plaintext.

generateDek ( ) : string

Generate a new Data Encryption Key.

Returns

A 32-byte random key.

calculateChecksum ( string $plaintext) : string

Calculate a value checksum for change detection. $plaintext is a #[\SensitiveParameter].

param string $plaintext

The secret value (#[\SensitiveParameter]).

Returns

SHA-256 hash (64 hex characters).

reEncryptDek ( string $encryptedDek, string $dekNonce, string $identifier, string $oldMasterKey, string $newMasterKey, int $encryptionVersion = 1, string $encryptionAlgorithm = '') : ReEncryptedDek

Re-encrypt a DEK with a new master key (used during master-key rotation). $encryptedDek, $oldMasterKey and $newMasterKey are #[\SensitiveParameter].

param string $encryptedDek

Current encrypted DEK (#[\SensitiveParameter]).

param string $dekNonce

Current DEK nonce.

param string $identifier

Secret identifier.

param string $oldMasterKey

Previous master key (#[\SensitiveParameter]).

param string $newMasterKey

New master key (#[\SensitiveParameter]).

param int $encryptionVersion

Stored per-secret encryption version.

param string $encryptionAlgorithm

Stored per-secret algorithm marker (required for version 2+).

The DEK is re-wrapped with the SAME algorithm the secret was encrypted with; the version and algorithm markers are unchanged by the operation.

Returns

A ReEncryptedDek value object (Netresearch\NrVault\Crypto\ReEncryptedDek).

EnvelopeCodec 

Envelope encryption for a payload you keep in ONE column of your own table (ADR-032). Use this instead of EncryptionServiceInterface when you have a blob rather than the vault's seven-column layout.

interface EnvelopeCodecInterface
Fully qualified name
\Netresearch\NrVault\Crypto\EnvelopeCodecInterface
const MARKER

'nrv1:' — the version marker of the envelopes seal() produces. A stored value is self-identifying, so a column can hold sealed and unsealed values during a migration.

seal ( string $plaintext, string $identifier) : string

Encrypt a payload into a single string. $plaintext is a #[\SensitiveParameter].

param string $plaintext

The payload to protect (#[\SensitiveParameter]).

param string $identifier

Context label bound to the ciphertext as additional authenticated data. Use a stable, per-purpose value (a column or use-case name), never a per-row one.

throws EncryptionException

If encryption fails or the master key is unavailable.

Returns

MARKER + base64-encoded JSON envelope.

open ( string $sealed, string $identifier) : string

Decrypt a sealed string. The stored change-detection checksum is not verified — integrity comes from the AEAD tag, which is always checked.

param string $sealed

A string produced by seal() .

param string $identifier

The SAME identifier the payload was sealed with.

throws EnvelopeFormatException

If the string is not a well-formed envelope.

throws EncryptionException

If authentication fails, the algorithm marker is unknown on this host, or the master key is unavailable.

Returns

The decrypted payload.

isSealed ( string $value) : bool

Whether a stored value is an envelope, as opposed to a plain value written before sealing was introduced.

rewrap ( string $sealed, string $identifier, string $oldMasterKey, string $newMasterKey) : string

Re-wrap the envelope's DEK from one master key to another, leaving the payload ciphertext untouched — nothing is decrypted. This is the primitive behind ForeignEnvelopeRotatorInterface ; you normally reach it through EnvelopeRotationContext::rewrap() rather than calling it directly.

ForeignEnvelopeRotator 

How a consuming extension joins master-key rotation (ADR-033). Tag your implementation:

Configuration/Services.yaml (in YOUR extension)
Vendor\Extension\Crypto\MyEnvelopeRotator:
  tags: ['nrvault.foreign_envelope_rotator']
Copied!
interface ForeignEnvelopeRotatorInterface
Fully qualified name
\Netresearch\NrVault\Crypto\ForeignEnvelopeRotatorInterface
getIdentifier ( ) : string

Short label naming your extension and the data it owns, for the operator-facing rotation report (e.g. nr-llm: agent run state).

getTables ( ) : array

Every table rewrapAll() writes to. The command refuses to rotate when one of them is mapped to a different database connection than tx_nrvault_secret, because atomicity across two connections is a fiction.

countEnvelopes ( ) : int

How many sealed envelopes you hold. Called outside the transaction, for the dry-run report and the operator summary. Throwing aborts the rotation before anything is touched.

rewrapAll ( EnvelopeRotationContext $context) : int

Re-wrap every envelope you own; return how many. Runs INSIDE the vault's rotation transaction, after the vault's own secrets and before the commit.

Do not open, commit or roll back a transaction, and do not swallow failures: throwing rolls the ENTIRE rotation back, which is deliberate — a partial rotation leaves data wrapped under a key the operator has been told to destroy. Work in batches; the whole pass is one transaction.

class EnvelopeRotationContext
Fully qualified name
\Netresearch\NrVault\Crypto\EnvelopeRotationContext

Handed to rewrapAll() . It closes over the old and new master keys and exposes only the operation, so you move envelopes between keys without ever holding key material.

rewrap ( string $sealed, string $identifier) : string

Re-wrap one envelope's DEK. The payload is not decrypted.

isSealed ( string $value) : bool

For skipping rows written before you started sealing.

SecretRedactor 

The shared catalogue of recognisable secret shapes (ADR-031), used by this extension's plaintext scanner and available to any consumer that needs to mask secrets in log lines, error messages or outbound payloads.

This is a best-effort net for secrets that have already escaped their proper home. It recognises the catalogued shapes and nothing else, and is not a substitute for keeping secrets in the vault.

interface SecretRedactorInterface
Fully qualified name
\Netresearch\NrVault\Secret\SecretRedactorInterface
redact ( string $text, bool $includeEmails = false) : string

Replace every recognised secret occurrence in free text with a mask.

param bool $includeEmails

Also mask e-mail addresses. Off by default: an address is personal data rather than a secret, and masking one inside, say, a model prompt changes what the text says.

Returns

The masked text. If the regex engine gives up on a pathological input the text is returned as-is rather than emptied.

isSecretIdentifier ( string $identifier, SecretIdentifierKind $kind) : bool

Whether a name reads as secret-bearing within its namespace. The kind matters: database columns and configuration keys are suffix-anchored, while environment variables use a broad substring rule. A name check alone is not enough — GITHUB_PAT says nothing about being secret — so pair it with identifyValue() or redact() on the value.

identifyValue ( string $value)

The shape name when the WHOLE value is a known secret format, else null. Leading and trailing whitespace is ignored.

Returns

string|null — the matched shape name, or null.

enum SecretIdentifierKind
Fully qualified name
\Netresearch\NrVault\Secret\SecretIdentifierKind

DatabaseColumn, ConfigurationKey, EnvironmentVariable — the three identifier namespaces, deliberately not merged into one rule set.

Usage examples 

Storing a secret 

Store a secret with VaultService
use Netresearch\NrVault\Service\VaultServiceInterface;

class MyService
{
    public function __construct(
        private readonly VaultServiceInterface $vault,
    ) {}

    public function storeApiKey(string $apiKey): void
    {
        $this->vault->store(
            'my_extension_api_key',
            $apiKey,
            [
                'description' => 'API key for external service',
                'groups' => [1, 2], // Admin, Editor groups
                'context' => 'payment',
                'expiresAt' => time() + 86400 * 90, // 90 days
            ]
        );
    }
}
Copied!

Retrieving a secret 

Retrieve a secret value
public function getApiKey(): ?string
{
    return $this->vault->retrieve('my_extension_api_key');
}
Copied!

Vault HTTP client 

The vault provides a PSR-18 compatible HTTP client that can inject secrets into requests without exposing them to your code. Configure authentication with withAuthentication() , then use standard sendRequest() .

Via VaultService 

HTTP client via VaultService
use GuzzleHttp\Psr7\Request;
use Netresearch\NrVault\Http\SecretPlacement;

$client = $this->vaultService->http()
    ->withAuthentication('stripe_api_key', SecretPlacement::Bearer);

$request = new Request(
    'POST',
    'https://api.stripe.com/v1/charges',
    ['Content-Type' => 'application/json'],
    json_encode($payload),
);

$response = $client->sendRequest($request);
Copied!
interface VaultHttpClientInterface
Fully qualified name
\Netresearch\NrVault\Http\VaultHttpClientInterface

PSR-18 compatible HTTP client with vault-based authentication. Extends \Psr\Http\Client\ClientInterface .

withAuthentication ( string $secretIdentifier, SecretPlacement $placement = SecretPlacement::Bearer, array $options = []) : static

Create a new client instance configured with authentication. Returns an immutable instance - the original is unchanged.

param string $secretIdentifier

Vault identifier for the secret.

param SecretPlacement $placement

How to inject the secret.

param array $options

Additional options (headerName, prefix, queryParam, bodyField, usernameSecret, reason).

Returns

New client instance with authentication configured.

withOAuth ( OAuthConfig $config, string $reason = 'OAuth2 API call') : static

Create a new client instance configured with OAuth 2.0 authentication.

param OAuthConfig $config

OAuth configuration.

param string $reason

Audit log reason.

Returns

New client instance with OAuth configured.

withReason ( string $reason) : static

Create a new client instance with a custom audit reason.

param string $reason

Audit log reason for requests.

Returns

New client instance with reason configured.

withTimeout ( int $seconds) : static

Create a new client instance with a request timeout override. Applies Guzzle's timeout option (total request duration) to every request sent through the returned instance, authenticated or not — use it for long-running API calls that exceed the instance-wide $GLOBALS['TYPO3_CONF_VARS']['HTTP']['timeout'] . Connection establishment (connect_timeout) stays platform-managed.

param int $seconds

Timeout in seconds; non-positive values mean "no override" and fall back to the platform default.

Returns

New client instance with the timeout configured.

sendRequest ( RequestInterface $request) : ResponseInterface

Send an HTTP request (PSR-18 method).

param RequestInterface $request

PSR-7 request.

throws ClientExceptionInterface

If request fails.

Returns

PSR-7 response.

Authentication options 

The withAuthentication() method accepts these options:

headerName
Custom header name (for SecretPlacement::Header , default: X-API-Key).
prefix
Auth scheme/prefix prepended to the secret (for SecretPlacement::Header ). Use for non-Bearer Authorization: <scheme> <secret> schemes — e.g. 'Key ' for the TYPO3 FAL providers or 'DeepL-Auth-Key ' for DeepL.
queryParam
Query parameter name (for SecretPlacement::QueryParam , default: api_key).
bodyField
Body field name (for SecretPlacement::BodyField , default: api_key).
usernameSecret
Separate username secret identifier (for SecretPlacement::BasicAuth ).
reason
Reason for access (logged in audit).

SecretPlacement enum 

placement

Authentication placement using SecretPlacement enum:

  • SecretPlacement::Bearer - Bearer token in Authorization header.
  • SecretPlacement::BasicAuth - HTTP Basic Authentication.
  • SecretPlacement::Header - Custom header value.
  • SecretPlacement::QueryParam - Query parameter.
  • SecretPlacement::BodyField - Field in request body.
  • SecretPlacement::OAuth2 - OAuth 2.0 with automatic token refresh.
  • SecretPlacement::ApiKey - X-API-Key header (shorthand).
Authentication examples
use GuzzleHttp\Psr7\Request;
use Netresearch\NrVault\Http\SecretPlacement;

// Bearer authentication
$client = $this->vault->http()
    ->withAuthentication('stripe_api_key', SecretPlacement::Bearer);
$response = $client->sendRequest(
    new Request('POST', 'https://api.stripe.com/v1/charges', [], $body)
);

// Custom header
$client = $this->vault->http()
    ->withAuthentication('api_token', SecretPlacement::Header, [
        'headerName' => 'X-API-Key',
    ]);
$response = $client->sendRequest(
    new Request('GET', 'https://api.example.com/data')
);

// Custom Authorization scheme (e.g. DeepL "Authorization: DeepL-Auth-Key <key>")
$client = $this->vault->http()
    ->withAuthentication('deepl_api_key', SecretPlacement::Header, [
        'headerName' => 'Authorization',
        'prefix' => 'DeepL-Auth-Key ',
    ]);
$response = $client->sendRequest(
    new Request('POST', 'https://api-free.deepl.com/v2/translate', [], $body)
);

// Basic authentication with separate credentials
$client = $this->vault->http()
    ->withAuthentication('service_password', SecretPlacement::BasicAuth, [
        'usernameSecret' => 'service_username',
        'reason' => 'Fetching secure data',
    ]);
$response = $client->sendRequest(
    new Request('GET', 'https://api.example.com/secure')
);

// Query parameter
$client = $this->vault->http()
    ->withAuthentication('api_key', SecretPlacement::QueryParam, [
        'queryParam' => 'key',
    ]);
$response = $client->sendRequest(
    new Request('GET', 'https://maps.example.com/geocode')
);
Copied!

PSR-14 events 

The vault dispatches events during secret operations.

class SecretCreatedEvent
Fully qualified name
\Netresearch\NrVault\Event\SecretCreatedEvent

Dispatched when a new secret is created.

  • getIdentifier() : The secret identifier.
  • getSecret() : The Secret entity.
  • getActorUid() : User ID who created it.
class SecretAccessedEvent
Fully qualified name
\Netresearch\NrVault\Event\SecretAccessedEvent

Dispatched when a secret is read.

  • getIdentifier() : The secret identifier.
  • getActorUid() : User ID who accessed it.
  • getContext() : The secret's context.
class SecretRotatedEvent
Fully qualified name
\Netresearch\NrVault\Event\SecretRotatedEvent

Dispatched when a secret is rotated.

  • getIdentifier() : The secret identifier.
  • getNewVersion() : The new version number.
  • getActorUid() : User ID who rotated it.
  • getReason() : The rotation reason.
class SecretDeletedEvent
Fully qualified name
\Netresearch\NrVault\Event\SecretDeletedEvent

Dispatched when a secret is deleted.

  • getIdentifier() : The secret identifier.
  • getActorUid() : User ID who deleted it.
  • getReason() : The deletion reason.
class SecretUpdatedEvent
Fully qualified name
\Netresearch\NrVault\Event\SecretUpdatedEvent

Dispatched when a secret value is updated (without rotation).

  • getIdentifier() : The secret identifier.
  • getNewVersion() : The new version number.
  • getActorUid() : User ID who updated it.
class MasterKeyRotatedEvent
Fully qualified name
\Netresearch\NrVault\Event\MasterKeyRotatedEvent

Dispatched by vault:rotate-master-key after the rotation transaction has COMMITTED, so a listener never observes a rotation that was rolled back.

  • getSecretsReEncrypted() : Number of this extension's secrets re-encrypted.
  • getForeignEnvelopesReEncrypted() : Number of consumer-owned envelopes re-wrapped (ADR-033).
  • getActorUid() : The acting backend user, or 0 in a CLI context.
  • getRotatedAt() : When the rotation completed.

This is a notification, not a participation hook: a listener cannot re-wrap anything, because both master keys are gone by the time it runs. To have your own envelopes rotated, implement ForeignEnvelopeRotatorInterface .

  • getSecretsReEncrypted() : Number of secrets re-encrypted.
  • getActorUid() : User ID who performed the rotation.
  • getRotatedAt() : DateTimeImmutable of when rotation completed.