1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//! Helper for wrapping secret bytes in a `SecretString` with optional
//! memory-locking when the `memory-lock` feature is enabled.
use crateSecretString;
/// Wrap `value` in a `SecretString`.
///
/// When the `memory-lock` feature is enabled, the backing allocation
/// is locked into physical memory and configured to be excluded from
/// core dumps and process forks via `lock_secret_pages`. On failure
/// (e.g., `RLIMIT_MEMLOCK` exhausted) the secret is still wrapped and
/// returned — the degrade is silent at this call site. Callers that
/// want to surface the outcome can call
/// `hasp_core::lock_secret_pages(s.expose_secret().as_bytes())` directly.
///
/// ## Scope of the lock
///
/// `lock_secret_pages` pins the pages backing the **final** `SecretString`
/// allocation. It does **not** address heap residue from prior
/// allocations:
///
/// - `String::into_boxed_str()` may shrink-to-fit, freeing the
/// original `String` buffer without zeroization.
/// - The caller's `String` may have been grown via `push_str` /
/// `read_to_string`, leaving the secret bytes in freed segments.
///
/// In practice this is acceptable for unprivileged CLI processes —
/// the residue lives in the same uid's address space that already had
/// the plaintext on the stack — but it is **not** a defense against
/// kernel-side swap or post-mortem heap forensics. Callers that need
/// the stronger guarantee should fetch directly into a pre-allocated
/// `SecretString` (e.g., via a sized `Read` interface) rather than
/// growing a `String` and wrapping it.
///
/// # Usage pattern for backend implementors
///
/// ```no_run
/// use hasp_core::secret_mem::wrap_secret;
/// // Instead of:
/// // Ok(hasp_core::SecretString::new(value.into()))
/// // Write:
/// // Ok(wrap_secret(value))
/// ```