Zum Hauptinhalt springen

ISecretAttributeProtector

Namespace: Meshmakers.Octo.Runtime.Contracts.Secrets

Encrypts and decrypts values of Secret attributes with the instance key ring (AB#5528, concept §3.4, §3.5, §3.7). Implemented in Runtime.Engine and registered by AddRuntimeEngine() from the configuration section SecretEncryption.

public interface ISecretAttributeProtector

Remarks:

Decryption is a privileged operation: every ISecretAttributeProtector.Unprotect(RtSecretValue, SecretAccessContext) is counted (octo.secrets.decrypt), values are never logged, and the public APIs never decrypt (architecture tests in asset repo and MCP allowlist the callers).

Without configured keys the service starts; ISecretAttributeProtector.IsConfigured is false and every operation that needs key material throws SecretEncryptionNotConfiguredException.

Properties​

IsConfigured​

True when an active key is configured, i.e. ISecretAttributeProtector.Protect(String) can work.

public abstract bool IsConfigured { get; }

Property Value​

Boolean

ActiveKeyId​

The key id new values are encrypted with; null when not configured.

public abstract string ActiveKeyId { get; }

Property Value​

String

IsStrictMode​

True when strict mode is on (configuration SecretEncryption:StrictMode, concept decision 10, §5.2 phase 5): legacy clear text is no longer readable through ISecretAttributeProtector.Unprotect(RtSecretValue, SecretAccessContext) / ISecretAttributeProtector.Unprotect(RtSecretValue, SecretAccessContext), which then throw LegacyPlaintextSecretRejectedException. ISecretAttributeProtector.Reprotect(RtSecretValue, SecretAccessContext) still converts it. Implementations without strict mode return false.

public bool IsStrictMode { get; }

Property Value​

Boolean

IsLegacyV1KeyConfigured​

True when the legacy enc:v1 key (configuration SecretEncryption:LegacyV1Key) is configured, i.e. a legacy enc:v1 string can be decrypted. Without it such a value is classified like a protected value with an unknown key id (SecretValueState.KeyMissing, key id SecretValueStates.LegacyV1KeyId) by ISecretAttributeProtector.GetReadState(RtSecretValue, SecretAccessContext), ISecretAttributeProtector.DescribeSecret(RtSecretValue, SecretAccessContext), the secrets overview and the sweep (AB#5532: a key-free Verify lists it as a re-entry task). Implementations without that knowledge return true (the value counts as set).

public bool IsLegacyV1KeyConfigured { get; }

Property Value​

Boolean

Methods​

IsKnownKeyId(String)​

True when keyId names a key of the key ring (case-insensitive), i.e. an enc:v2 envelope with this key id can be decrypted. The sweep (AB#5532) classifies values with an unknown key id (decision 5: restored from another environment) with it.

bool IsKnownKeyId(string keyId)

Parameters​

keyId String

Returns​

Boolean

Protect(String)​

Encrypts a plaintext with the active key: enc:v2:<kid>:..., AES-256-GCM, random nonce, associated data = the ASCII header. The result's RtSecretValue.SetAt is the current UTC time (new input).

RtSecretValue Protect(string plaintext)

Parameters​

plaintext String

Returns​

RtSecretValue

Exceptions​

SecretEncryptionNotConfiguredException
No active key

Unprotect(RtSecretValue, SecretAccessContext)​

Returns the plaintext of a secret value: decrypts enc:v2 (key ring) and enc:v1 (legacy key), returns legacy clear text (counted as a plaintext read) and the plaintext of a pending value. In strict mode (ISecretAttributeProtector.IsStrictMode) legacy clear text is rejected. A legacy value (a string found in a Secret slot) may be clear text or enc:v1; an enc:v2 envelope as legacy text is never decrypted (AB#5532: the engine stores enc:v2 only in the protected form, so such a text was copied there).

string Unprotect(RtSecretValue value, SecretAccessContext context)

Parameters​

value RtSecretValue

context SecretAccessContext

Returns​

String

Exceptions​

LegacyPlaintextSecretRejectedException
Strict mode and the value is legacy clear text

SecretEnvelopeNotAllowedException
A legacy value whose text is an enc:v2 envelope

SecretEncryptionNotConfiguredException
The needed key material is not configured

UnknownSecretKeyIdException
The envelope's key id is not in the key ring

CryptographicException
The value was tampered with or the key is wrong

Unprotect(String, SecretAccessContext)​

Returns the plaintext of a stored string: an enc:v2 or enc:v1 envelope is decrypted, anything else is treated as legacy clear text (counted as a plaintext read; rejected in strict mode, see ISecretAttributeProtector.IsStrictMode).

string Unprotect(string storedValue, SecretAccessContext context)

Parameters​

storedValue String

context SecretAccessContext

Returns​

String

Exceptions​

LegacyPlaintextSecretRejectedException
Strict mode and the value is clear text

SecretEncryptionNotConfiguredException
The needed key material is not configured

UnknownSecretKeyIdException
The envelope's key id is not in the key ring

CryptographicException
The value was tampered with or the key is wrong

Remarks:

This overload is the explicit envelope entry point (a caller that holds an envelope it trusts, e.g. InstanceSecretCrypto) and still decrypts enc:v2. Values read from a Secret slot go through ISecretAttributeProtector.Unprotect(RtSecretValue, SecretAccessContext), which refuses an enc:v2 envelope stored as a legacy string; never pass the text of a RtSecretValueState.LegacyPlaintext value here.

GetReadState(RtSecretValue, SecretAccessContext)​

Classifies a value for readers (decisions 2026-10-06, item 2) without decrypting it: SecretValueState.Set (protected with a known key id, non-empty legacy or pending), SecretValueState.KeyMissing (protected, key id not in the ring - the ciphertext is kept and becomes readable once the key is added) or SecretValueState.NotSet (null, empty, a legacy placeholder, or corrupt - see SecretValueStates.IsCorrupt(RtSecretValue)). APIs map it to isSet = (state == Set) and keyMissing = (state == KeyMissing).

SecretValueState GetReadState(RtSecretValue value, SecretAccessContext context)

Parameters​

value RtSecretValue
The stored value; null = not set

context SecretAccessContext
Where the value comes from (log and counter tags only)

Returns​

SecretValueState
The read state

Remarks:

The default implementation is SecretValueStates.GetReadState(RtSecretValue, Func<String, Boolean>) with ISecretAttributeProtector.IsKnownKeyId(String) and ISecretAttributeProtector.IsLegacyV1KeyConfigured (a legacy enc:v1 string without the legacy key is SecretValueState.KeyMissing); the engine implementation additionally logs a warning and counts (octo.secrets.unreadable, reason=corrupt) a corrupt value - never the value.

DescribeSecret(RtSecretValue, SecretAccessContext)​

Like ISecretAttributeProtector.GetReadState(RtSecretValue, SecretAccessContext), plus storage form, key id and "set at" (RtSecretValue.SetAt) - the data behind GraphQL isSet / keyMissing / setAt and the secrets overview. Never decrypts.

SecretReadInfo DescribeSecret(RtSecretValue value, SecretAccessContext context)

Parameters​

value RtSecretValue
The stored value; null = not set

context SecretAccessContext
Where the value comes from (log and counter tags only)

Returns​

SecretReadInfo
The description

RevealOrNull(RtSecretValue, SecretAccessContext)​

Reveal helper for the server-side paths that need the plaintext (controller adapter configuration, mesh adapter RevealSecret@1, identity providers, AI services, service-account tokens; decisions 2026-10-06, item 2): like ISecretAttributeProtector.Unprotect(RtSecretValue, SecretAccessContext), but a value that is stored and cannot be read is treated as NOT SET and returns null instead of throwing - an unknown key id (UnknownSecretKeyIdException), a tampered / wrong-key envelope (CryptographicException) and an enc:v2 envelope stored as a legacy string (SecretEnvelopeNotAllowedException). null, empty values and legacy placeholders return null as well. The stored value is never changed.

string RevealOrNull(RtSecretValue value, SecretAccessContext context)

Parameters​

value RtSecretValue
The stored value

context SecretAccessContext
Reader (decrypt counter, log)

Returns​

String
The plaintext, or null when not set or unreadable

Remarks:

Configuration problems still throw: SecretEncryptionNotConfiguredException (no keys at all / no legacy key on this host - the engine implementation; the default implementation maps an unknown key id to null without that distinction) and, in strict mode, LegacyPlaintextSecretRejectedException. The engine implementation logs an error (tenant, CK type, attribute, key id - never the value) and counts octo.secrets.unreadable for every value it maps to null. The default implementation only maps.

IsProtectedEnvelope(String)​

Strict check: true only for a structurally valid enc:v1 or enc:v2 envelope (see SecretEnvelope). A plaintext starting with enc: is not one.

bool IsProtectedEnvelope(string value)

Parameters​

value String

Returns​

Boolean

TryParseEnvelope(String, out SecretEnvelopeInfo)​

Parses an envelope strictly and returns version and key id.

bool TryParseEnvelope(string value, out SecretEnvelopeInfo info)

Parameters​

value String

info SecretEnvelopeInfo

Returns​

Boolean

NeedsReprotect(RtSecretValue)​

True when the value is not protected with the active key: pending, legacy (clear text or enc:v1) or enc:v2 with another key id. The re-protect sweep (AB#5532) uses it.

bool NeedsReprotect(RtSecretValue value)

Parameters​

value RtSecretValue

Returns​

Boolean

Reprotect(RtSecretValue, SecretAccessContext)​

Returns the value protected with the active key; a value that already is, is returned unchanged. RtSecretValue.SetAt is kept for a protected value, null for a converted legacy value and the current time for a pending value. Decrypting for the re-encryption is counted like any other decrypt. Legacy clear text is converted in strict mode as well (the strict check is bypassed and the read is counted as a plaintext read): the encrypt / reprotect sweep must be able to clear the remainder. A legacy value whose text is an enc:v2 envelope is refused like in ISecretAttributeProtector.Unprotect(RtSecretValue, SecretAccessContext).

RtSecretValue Reprotect(RtSecretValue value, SecretAccessContext context)

Parameters​

value RtSecretValue

context SecretAccessContext

Returns​

RtSecretValue

Exceptions​

SecretEnvelopeNotAllowedException
A legacy value whose text is an enc:v2 envelope