fix(wasm): impl Drop for SessionHandle clears registry entry
Closes the P1.1 defense-in-depth gap: wasm-bindgen's auto-generated .free() previously dropped the SessionHandle wrapper (a u32) without removing the SESSIONS HashMap entry, leaving the master key and image_secret in WASM linear memory until JS explicitly called lock(handle). Drop now wires .free() to session::remove, and the new native test pins the contract. Refs: docs/superpowers/specs/2026-05-04-security-polish-design.md (Phase 1) Refs: docs/superpowers/reviews/2026-05-04-architecture-review.md (P1.1) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,13 @@ use zeroize::Zeroizing;
|
||||
|
||||
use relicario_core::{derive_master_key, imgsecret, KdfParams};
|
||||
|
||||
/// Handle type returned from `unlock`. Backed by a `u32`; opaque to JS.
|
||||
/// Handle returned from `unlock`. Backed by a `u32`; opaque to JS.
|
||||
///
|
||||
/// Dropping the handle (or invoking `.free()` from JS) removes the entry from
|
||||
/// the session registry, zeroizing the wrapped master key and image_secret.
|
||||
/// `lock(handle)` remains available as the explicit early-cleanup path; the
|
||||
/// `Drop` impl is the safety net that catches code paths which forget to call
|
||||
/// `lock` before letting the handle go out of scope.
|
||||
#[wasm_bindgen]
|
||||
pub struct SessionHandle(u32);
|
||||
|
||||
@@ -22,6 +28,23 @@ impl SessionHandle {
|
||||
pub fn value(&self) -> u32 { self.0 }
|
||||
}
|
||||
|
||||
impl Drop for SessionHandle {
|
||||
fn drop(&mut self) { let _ = session::remove(self.0); }
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn __test_make_handle() -> SessionHandle {
|
||||
SessionHandle(session::insert(
|
||||
Zeroizing::new([0x77u8; 32]),
|
||||
Zeroizing::new([0u8; 32]),
|
||||
))
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn __test_session_exists(handle: u32) -> bool {
|
||||
session::with(handle, |_| ()).is_some()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn unlock(
|
||||
passphrase: &str,
|
||||
@@ -533,6 +556,19 @@ mod session_tests {
|
||||
assert!(!session::remove(h)); // second remove false
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dropping_session_handle_clears_registry_entry() {
|
||||
session::clear();
|
||||
let handle = SessionHandle(session::insert(
|
||||
Zeroizing::new([0x33u8; 32]),
|
||||
Zeroizing::new([0u8; 32]),
|
||||
));
|
||||
let id = handle.value();
|
||||
assert!(session::with(id, |_| ()).is_some());
|
||||
drop(handle);
|
||||
assert!(session::with(id, |_| ()).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_yields_key_only_while_session_lives() {
|
||||
session::clear();
|
||||
|
||||
Reference in New Issue
Block a user