Skip to main content

SecretValue

Struct SecretValue 

Source
pub struct SecretValue(/* private fields */);
Expand description

A secret value whose memory is zeroed on drop.

Does not implement Debug, Display, or Clone to prevent accidental leakage. Use as_bytes for comparisons in tests.

§Why not the secrecy crate?

The secrecy crate’s Secret<T> zeroes the outer allocation on drop, which is the same guarantee Zeroizing<Vec<u8>> provides here. For a plain byte buffer that guarantee would be sufficient, and using secrecy would be reasonable.

The problem is extract_field and extract_path_field. Secrets are often stored as JSON objects ({"username":"alice","password":"s3cr3t"}), so callers frequently need to pull a single string field out of the raw bytes. The obvious implementation calls serde_json::from_slice, but serde_json allocates every field value as a plain (non-Zeroizing) String. Even after the parsed map is dropped, those allocations are not zeroed: the password lingers in heap memory until the allocator reuses the page. secrecy::Secret does not help here — it only zeroes what it directly owns.

These methods therefore use a hand-rolled JSON scanner that never allocates non-target fields at all. Only the one requested value is placed into a Zeroizing buffer; everything else is scanned and discarded in place. Switching to secrecy would either require accepting that leak or keeping the custom scanner anyway, gaining a dependency without simplifying the code.

Note on built-in backends: the network-backed backends (aws-sm, azure-kv, doppler, gcp-sm, vault) use serde_json directly rather than these methods, because in those backends the secret arrives in non-Zeroizing heap memory (a reqwest response buffer or an SDK-owned String) before any parsing begins. The custom scanner cannot retroactively zero memory it does not own, so using it there would add complexity without improving the security boundary. These methods are most useful when the SecretValue originates from a backend that does not pre-leak — e.g. secretx-file or secretx-env — and the caller needs to extract a JSON field while minimising additional unzeroed copies.

See the // ── JSON field extractor section below for the full rationale.

Implementations§

Source§

impl SecretValue

Source

pub fn new(bytes: Vec<u8>) -> Self

Wrap raw bytes in a SecretValue.

The bytes are moved into a Zeroizing container and zeroed on drop.

Source

pub fn as_bytes(&self) -> &[u8]

Borrow the secret bytes.

Source

pub fn into_bytes(self) -> Zeroizing<Vec<u8>>

Consume the SecretValue and return the inner Zeroizing<Vec<u8>>.

Source

pub fn from_zeroizing(z: Zeroizing<Vec<u8>>) -> Self

Construct from an already-zeroizing buffer without creating a non-Zeroizing intermediate copy.

Source

pub fn as_str(&self) -> Result<&str, SecretError>

Decode as UTF-8 without copying. Fails if not valid UTF-8.

Source

pub fn extract_field(&self, field: &str) -> Result<SecretValue, SecretError>

Parse as a JSON object and extract a single string field.

Common for secrets that bundle multiple values as JSON, e.g. {"username":"foo","password":"bar"}.

Uses a hand-rolled JSON scanner so that only the requested field’s value is allocated. A full-tree parser (e.g. serde_json) would allocate copies of every field value, leaving other secret strings in unzeroized heap memory even after the parse result is dropped.

Source

pub fn extract_path(&self, path: &[&str]) -> Result<SecretValue, SecretError>

Navigate through nested JSON objects and return the raw bytes of the value at path’s final key as a new SecretValue.

Each key in path must exist in the current JSON object. All intermediate values (path[..path.len()-1]) must be JSON objects. The final value may be any JSON type.

§Example (Vault KV v2)

Given {"data": {"data": {"password": "s3cret"}}, ...}, extract_path(&["data", "data"]) returns a SecretValue containing the bytes of {"password": "s3cret"}. Call SecretValue::extract_field on the result to retrieve a specific secret field.

Source

pub fn extract_path_field( &self, path: &[&str], field: &str, ) -> Result<SecretValue, SecretError>

Navigate through nested JSON objects and extract a string field.

Equivalent to self.extract_path(path)?.extract_field(field) but avoids an intermediate allocation: the nested object bytes are sliced from the original input with no copy, and only the target field value is placed in a Zeroizing buffer.

Trait Implementations§

Source§

impl AsRef<[u8]> for SecretValue

Source§

fn as_ref(&self) -> &[u8]

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl From<Zeroizing<Vec<u8>>> for SecretValue

Source§

fn from(z: Zeroizing<Vec<u8>>) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.