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 AccessDeniedException

If the per-secret ACL or the operation permission refuses the write — secret.create on a new secret, secret.rotate on an existing one, and additionally secret.manage_policy when the options change the owner or the group tiers.

throws EncryptionException

If encryption fails.

Operation permissions and the per-secret ACL are two independent gates. Holding one never implies the other, and both are asserted before the value is written.

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.

retrieveForFrontend ( string $identifier)

Frontend-scoped counterpart of retrieve(). Only secrets flagged frontend_accessible resolve here, and that requirement holds for every caller — including a request that happens to carry a backend session, whose ambient privileges would otherwise widen retrieve()'s access decision. Expiry, decryption, audit logging and read statistics behave as in retrieve(). See ADR-035: Per-request allow-set of frontend-resolvable identifiers.

param string $identifier

The secret identifier.

returntype

string|null

throws AccessDeniedException

If the secret is not frontend-accessible, or the actor 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.

assertDeletable ( string $identifier) : void

Assert that delete() is permitted for this identifier — without deleting. Exists for callers that delete several secrets as one logical unit, such as a record delete spanning multiple vault fields: a vault delete is a hard delete with no restore, so a partially applied batch cannot be compensated, and the only way to keep it all-or-nothing is to run every permission gate up front and abort before the first deletion.

A secret that does not exist returns without throwing — the goal state is already reached. Ask exists() to distinguish absent from present. Passing does not guarantee the subsequent delete succeeds: an audit-write failure, or a permission revoked in between, can still abort it.

param string $identifier

The secret identifier.

throws AccessDeniedException

If the current actor 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).

throws SecretNotFoundException

If the secret does not exist.

throws AccessDeniedException

If the per-secret ACL or the secret.rotate operation permission refuses it.

throws EncryptionException

If encryption fails.

setEnabled ( string $identifier, bool $enabled, string $reason = '') : void

Enable or disable a secret — the single write path for its availability.

Disabling withdraws the secret from every read path at once: the record carries TCA's disabled enable column, so a disabled secret resolves to nothing in retrieve() , retrieveForFrontend() and every placeholder that goes through them. It stays administrable — it can be re-enabled, rotated, deleted, and its metadata read — and reports its state in SecretDetails::$enabled .

The state is absolute rather than a toggle: setting the state a secret already has is a no-op and writes no audit entry. A change is audited as metadata_update, the same action the FormEngine path writes for the same column, and is all-or-nothing with that entry (ADR-036: Mutation and audit are all-or-nothing): if the audit write fails the previous availability is restored and the failure surfaces.

param string $identifier

The secret identifier.

param bool $enabled

The availability the secret should have afterwards.

param string $reason

Optional justification, recorded in the audit entry alongside the direction of the change.

throws SecretNotFoundException

If the secret does not exist.

throws AccessDeniedException

If the per-secret ACL or the secret.manage_policy operation permission refuses it.

list ( ?string $pattern = null, bool $includeDisabled = false) : array

List accessible secrets.

param string|null $pattern

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

param bool $includeDisabled

Also return disabled secrets. Off by default, so a consumer asking which secrets are available keeps the answer it had; the management surfaces pass true, because a disabled secret that never appears in a listing cannot be re-enabled.

Returns

A list<SecretMetadata> of secret metadata DTOs (Netresearch\NrVault\Domain\Dto\SecretMetadata); each entry reports its availability in $enabled.

getMetadata ( string $identifier) : SecretDetails

Get metadata for a secret without retrieving its value. Resolves a disabled secret too — metadata is not the value, and withholding it would hide the record from the form that re-enables it.

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, availability ($enabled), etc.

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.

Cancelling an outbound request 

PSR-18 returns a response, never a handle, so sendRequest() cannot be aborted once it is running — a caller whose own work was cancelled still waits out the timeout. CancellableHttpClientInterface adds a second send that polls a caller-supplied signal and tears the socket down when it turns true.

It is a send, not an exported handle, and that is a security property rather than a matter of taste. Four of this client's protections — the scheme allowlist, the allowed_hosts gate, the credential injection and the audit write — are statements inside the sending method, not middleware on the handler stack, so they do not travel with a transport. The rule the package holds to is therefore narrow and sharp: nothing hands you a client that already carries a vault secret, and VaultHttpClient is the only place nr-vault attaches a secret to your request. There is no send you can drive that puts a vault secret on the wire without those four: every public method of that class returns a configured clone, a PSR-7 response or a bool, and sendCancellable() takes no options parameter — asserted by VaultHttpClientCancellableTest::theCredentialBearingClientExportsNoTransportAndNoPromise().

nr-vault sends two credentials of its own on paths that are not your request and do not carry all four: the X-Vault-Token header of the transit master-key provider, on a plain Guzzle client, and the client_secret of the OAuth token leg, which applies the allowed_hosts gate, writes one oauth_token_request audit row per attempted round trip, and rides sendCancellable() 's cancellation signal (issue #303). They are listed in ADR-037: A cancellable send is a method, not an exported handle.

Building a hardened transport without vault credentials remains a supported, public case: SecureHttpClientFactory::create() and createCancellable() return one, carrying the SSRF reject middleware and the CURLOPT_RESOLVE DNS pin — and no secret, no allowlist gate, no audit write.

The interface is separate from VaultHttpClientInterface and purely additive, so consumers feature-detect instead of raising a version floor:

Aborting a call when the surrounding operation is cancelled
use Netresearch\NrVault\Http\CancellableHttpClientInterface;
use Netresearch\NrVault\Http\CancellationSignalInterface;
use Netresearch\NrVault\Exception\RequestCancelledException;

final class RunCancellationSignal implements CancellationSignalInterface
{
    public function __construct(private readonly MyRunState $run) {}

    public function isCancelled(): bool
    {
        return $this->run->wasCancelled();
    }
}

$client = $this->vaultService->http()
    ->withAuthentication('tool_api_key')
    ->withReason('MCP tool call')
    ->withTimeout(15);

try {
    $response = $client instanceof CancellableHttpClientInterface && $client->supportsCancellation()
        ? $client->sendCancellable($request, new RunCancellationSignal($run))
        : $client->sendRequest($request);
} catch (RequestCancelledException $e) {
    // The call was abandoned on purpose; the audit log already recorded it.
}
Copied!

The signal is polled once before the request is sent and then between ticks of the transport event loop. Implementations must be cheap and must not throw: the method is called several times a second per in-flight request, and it runs after the credential has been injected. A signal that throws anyway still leaves an audit row — the write is in a finally — but the exception reaches the caller unchanged (VaultHttpClientCancellableTest::aSignalThatThrowsMidFlightStillLeavesAnAuditRow()).

Every sendCancellable() writes exactly one audit row — and so does every sendRequest() — so the log is complete with respect to calls and not merely to egress. The outcome-to-test table in ADR-037: A cancellable send is a method, not an exported handle is the enumeration that backs this sentence: one row per way a call can end, one named test per row. Three actions can result, and each means one thing — the action is what you filter and count on, so none of them needs the error message to be understood:

http_call_cancelled (badge: warning)
The signal stopped an in-flight request. The credential was retrieved, injected and handed to the transport: treat it as exposed. Nothing else is filed under this action, so "which calls were abandoned after their credential went out?" is a query on this one value (theTwoCancellationOutcomesAreToldApartByTheirAction()).
http_call_cancelled_before_send (badge: info)
The signal was already set when the send began. No secret was retrieved and nothing was handed to the transport. Its own action so you can exclude these rows by query rather than by reading messages (cancellingBeforeSendReadsNoSecretAndStillLeavesADistinguishableRow()).
http_call with success = false
Everything that failed rather than was cancelled — a refused scheme or host, a transport that could not be built, a credential that could not be obtained, a transport error, the defensive wall-clock bound, a settlement that is not a response, or a throw from your signal or the ticker. Nobody asked for those, so they sit with the other failures. ADR-037 lists the test for each.

Within an action, the row's error message is a fixed literal shown under the badge:

Request cancelled before send: nothing egressed and no secret was retrieved
The pre-flight refusal.
Request cancelled after send began: credential injected and transfer handed to the transport
In the usual case the bytes are already out; if the signal turns true before the first tick they may not be, which is why the literal does not claim more than it can.
Cancellable transfer exceeded its wall-clock budget and was aborted
The defensive bound above the curl timeouts tripped, i.e. the transport stopped settling its promise.
Cancellable transport settled with a value that is not an HTTP response
The transfer settled with something unusable.
Cancellable transfer aborted by an unexpected error after the credential was injected: …
Guzzle's option handling, your signal or the ticker threw.
Blocking send aborted by an unexpected error after the credential was injected: …
The degraded blocking branch threw something that is not a PSR-18 ClientExceptionInterface. The same literal can appear for a plain sendRequest() , which runs the same send-and-audit helper.
Cancellable transport could not be built; nothing was sent: …
Building the transport for sendCancellable() threw. It is built after the two guards and before the credential is read, so nothing egressed and no secret was retrieved. This one is specific to sendCancellable() .
Cancellable transfer was rejected
The promise rejected with a reason that is not a Throwable, so there is no foreign message to append. Specific to sendCancellable() .
Request refused before any secret was read: unsupported URI scheme "…"
The URI was neither http nor https. The scheme is in the message because the audit context records method, host, path and status only.
Request refused before any secret was read: host is not in the allowed hosts list
$GLOBALS['TYPO3_CONF_VARS']['HTTP']['allowed_hosts'] refused the destination. The host is on the row, in the context.
Credential injection failed; nothing was sent: …
The vault read, or the OAuth token leg, threw. Nothing egressed.

The last three appear for sendRequest() as well: they are refusals of a call that was asked for, and a call that was asked for shows up in the log. The exception you receive is unchanged by that row — pinned by three characterization tests in VaultHttpClientTest, written before the rows existed.

See ADR-037: A cancellable send is a method, not an exported handle for the transport details and the residual gaps — in particular that the OAuth token round trip preceding an OAuth-authenticated call is not cancellable.

interface CancellableHttpClientInterface
Fully qualified name
\Netresearch\NrVault\Http\CancellableHttpClientInterface

An outbound send that can be aborted while it is still on the wire. Implemented by VaultHttpClient alongside VaultHttpClientInterface .

sendCancellable ( RequestInterface $request, CancellationSignalInterface $signal) : ResponseInterface

Send an HTTP request, aborting the transfer as soon as $signal says so. Runs the same guard sequence as sendRequest() : scheme allowlist, host allowlist, credential injection, audit write.

Accepts no per-request transport options, deliberately — see ADR-037: A cancellable send is a method, not an exported handle.

When supportsCancellation() is false the call still completes, blocking, with an ordinary http_call audit row. It degrades; it does not fail (aNonGuzzleInnerClientDegradesToABlockingSendWithAnOrdinaryAuditRow()).

param RequestInterface $request

PSR-7 request.

param CancellationSignalInterface $signal

Polled before the send and between transport ticks.

throws RequestCancelledException

If the signal aborted the call.

throws ClientExceptionInterface

If the transfer itself failed.

throws VaultException

If the scheme or host is rejected, or secret retrieval fails.

Returns

PSR-7 response.

supportsCancellation ( ) : bool

Whether this instance can abort a transfer in flight. False when the platform has no curl_multi_* support, and false when the inner client was supplied by the caller instead of built by SecureHttpClientFactory — a supplied client may carry your own middleware or proxy, so it stays the one that sends. A pre-flight signal is still honoured in either case, because nothing has egressed yet, and the call is still audited (aPreFlightSignalIsHonouredEvenWhenCancellationIsUnsupported()).

interface CancellationSignalInterface
Fully qualified name
\Netresearch\NrVault\Http\CancellationSignalInterface
isCancelled ( ) : bool

Return true to abort the in-flight request. Must not throw, and must be cheap.

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 .

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

Dispatched when audit verification produces a finding.

  • getAlert() : The alert value object.
  • getReason() : The stable reason code (TABLE_RESET, EPOCH_DOWNGRADE, SINK_FAILURE, NO_EXTERNAL_SINK, …).
  • isTamperEvidence() : Whether this finding is evidence of tampering rather than an availability problem — the discriminator a pager rule should key on.
class BreakGlassActivatedEvent
Fully qualified name
\Netresearch\NrVault\Event\BreakGlassActivatedEvent

Dispatched when a break-glass window opens.

  • getActorUid() / getActorUsername() : Who opened it.
  • getReason() : The mandatory justification.
  • getExpiresAt() : When the window lapses on its own.
class BreakGlassDeactivatedEvent
Fully qualified name
\Netresearch\NrVault\Event\BreakGlassDeactivatedEvent

Dispatched when a window is closed deliberately. A window that merely expires dispatches nothing — nothing runs at the moment it lapses, so reconstruct the closed interval from the activation event's expiry.

  • getActorUid() / getActorUsername() : Who closed it.
  • getReason() : The closing note.