@@ -22,6 +22,7 @@ use vault_ipc::default_socket_path;
2222#[ cfg( feature = "clipboard" ) ]
2323mod clipboard;
2424mod server;
25+ mod session;
2526mod state;
2627mod unlock;
2728
@@ -65,6 +66,11 @@ struct Args {
6566 /// build without clipboard support.
6667 #[ arg( long) ]
6768 clipboard_clear_secs : Option < u64 > ,
69+ /// Mirror the user key into the Linux kernel session keyring on unlock so a
70+ /// restarted agent resumes without the master password, within the
71+ /// idle-lock TTL (opt-in; PRD §7.3 carve-out). No effect on non-Linux.
72+ #[ arg( long) ]
73+ session_keyring : bool ,
6874}
6975
7076#[ tokio:: main( flavor = "current_thread" ) ]
@@ -82,6 +88,11 @@ async fn main() -> anyhow::Result<()> {
8288 } ;
8389 #[ cfg( not( feature = "clipboard" ) ) ]
8490 let agent = AgentState :: new ( args. idle_lock_secs ) ;
91+ let mut agent = agent;
92+ agent. session_keyring = args. session_keyring ;
93+ // Opt-in: resume an unlocked session left in the kernel keyring by a prior
94+ // agent (e.g. after a crash / restart), within its idle-lock deadline.
95+ try_resume ( & mut agent) ;
8596 let state = Arc :: new ( Mutex :: new ( agent) ) ;
8697
8798 if args. idle_lock_secs > 0 {
@@ -117,6 +128,40 @@ fn resolve_clear_secs(flag: Option<u64>, env: Option<&str>) -> u64 {
117128 . unwrap_or ( 30 )
118129}
119130
131+ /// Opt-in session resume: if a prior agent left an unlocked session in the
132+ /// kernel keyring and it hasn't passed its idle-lock deadline, rebuild the
133+ /// vault from it + the on-disk cache so the agent comes up unlocked. Any
134+ /// failure (disabled, no entry, expired, missing cache) leaves it locked.
135+ fn try_resume ( agent : & mut AgentState ) {
136+ if !agent. session_keyring {
137+ return ;
138+ }
139+ let Some ( blob) = session:: load ( ) else {
140+ return ;
141+ } ;
142+ let now = std:: time:: SystemTime :: now ( )
143+ . duration_since ( std:: time:: UNIX_EPOCH )
144+ . map_or ( 0 , |d| d. as_secs ( ) ) ;
145+ // deadline_unix == 0 means "no expiry" (idle-lock disabled).
146+ if blob. deadline_unix != 0 && now >= blob. deadline_unix {
147+ session:: clear ( ) ;
148+ return ;
149+ }
150+ let Some ( cache) = unlock:: load_cache ( & blob. server , & blob. email ) else {
151+ return ;
152+ } ;
153+ let user_enc = zeroize:: Zeroizing :: new ( blob. user_enc ) ;
154+ let user_mac = zeroize:: Zeroizing :: new ( blob. user_mac ) ;
155+ match unlock:: vault_from_user_key ( & cache, & blob. server , & blob. email , user_enc, user_mac) {
156+ Ok ( vault) => {
157+ agent. vault = Some ( vault) ;
158+ agent. touch ( ) ;
159+ eprintln ! ( "vault-agent: resumed session from kernel keyring" ) ;
160+ }
161+ Err ( e) => eprintln ! ( "vault-agent: keyring resume failed: {e}" ) ,
162+ }
163+ }
164+
120165fn pick_socket ( cli : Option < PathBuf > ) -> anyhow:: Result < PathBuf > {
121166 if let Some ( p) = cli {
122167 return Ok ( p) ;
0 commit comments