@@ -106,6 +106,11 @@ func (h *Handler) Handle(sess ssh.Session, username string, pubKey gossh.PublicK
106106 return h .handleVaultListAll (sess , username )
107107 case OpVaultDestroy :
108108 return h .handleVaultDestroy (sess , username , cmd .Vault )
109+ case OpDelete :
110+ if cmd .Vault != "" {
111+ return h .handleVaultDelete (sess , username , pubKey , cmd .Vault , cmd .Path )
112+ }
113+ return h .handleDelete (sess , username , pubKey , cmd .Path )
109114 case OpMove :
110115 return h .handleMove (sess , username , pubKey , cmd )
111116 case OpHelp :
@@ -233,6 +238,96 @@ func FormatPath(p string, color bool) string {
233238 return blue + p [:idx + 1 ] + reset + p [idx + 1 :]
234239}
235240
241+ func (h * Handler ) handleDelete (sess ssh.Session , username string , pubKey gossh.PublicKey , path string ) error {
242+ ag , cleanup , err := requireAgent (sess )
243+ if err != nil {
244+ return err
245+ }
246+ defer cleanup ()
247+
248+ // Verify the secret exists and is decryptable before prompting for confirmation
249+ ciphertext , err := h .store .Read (username , path )
250+ if err != nil {
251+ return fmt .Errorf ("read secret: %w" , err )
252+ }
253+
254+ plaintext , err := h .enc .DecryptAndUpgrade (ag , pubKey , h .serverSecret , username , path , ciphertext , func (upgraded []byte ) error {
255+ return h .store .Write (username , path , upgraded )
256+ })
257+ if err != nil {
258+ return fmt .Errorf ("decrypt: %w" , err )
259+ }
260+ crypto .Zeroize (plaintext )
261+
262+ fmt .Fprintf (sess , "Delete %s? [y/N]: " , path )
263+ buf := make ([]byte , 1 )
264+ n , err := sess .Read (buf )
265+ if err != nil || n == 0 || (buf [0 ] != 'y' && buf [0 ] != 'Y' ) {
266+ fmt .Fprintln (sess , "Delete cancelled." )
267+ return nil
268+ }
269+
270+ if err := h .store .Delete (username , path ); err != nil {
271+ return fmt .Errorf ("delete secret: %w" , err )
272+ }
273+
274+ fmt .Fprintln (sess , "Deleted." )
275+ return nil
276+ }
277+
278+ func (h * Handler ) handleVaultDelete (sess ssh.Session , username string , pubKey gossh.PublicKey , vaultName , path string ) error {
279+ ag , cleanup , err := requireAgent (sess )
280+ if err != nil {
281+ return err
282+ }
283+ defer cleanup ()
284+
285+ if ! h .vaultMgr .HasAccess (vaultName , username ) {
286+ if h .auditLog != nil {
287+ h .auditLog .VaultOpDenied ("del" , username , sess .RemoteAddr ().String (), vaultName , "not a member" )
288+ }
289+ return fmt .Errorf ("permission denied: not a member of vault %q" , vaultName )
290+ }
291+
292+ // Verify the secret exists and is decryptable before prompting for confirmation
293+ vaultKey , err := h .vaultMgr .VaultKey (vaultName , username , ag , pubKey )
294+ if err != nil {
295+ return fmt .Errorf ("vault key: %w" , err )
296+ }
297+ defer crypto .Zeroize (vaultKey )
298+
299+ ciphertext , err := h .fileStore .ReadVaultSecret (vaultName , path )
300+ if err != nil {
301+ return fmt .Errorf ("read secret: %w" , err )
302+ }
303+
304+ plaintext , err := decryptVaultSecret (vaultKey , path , h .serverSecret , ciphertext , func (upgraded []byte ) error {
305+ return h .fileStore .WriteVaultSecret (vaultName , path , upgraded )
306+ })
307+ if err != nil {
308+ return fmt .Errorf ("decrypt: %w" , err )
309+ }
310+ crypto .Zeroize (plaintext )
311+
312+ fmt .Fprintf (sess , "Delete %s:%s? [y/N]: " , vaultName , path )
313+ buf := make ([]byte , 1 )
314+ n , err := sess .Read (buf )
315+ if err != nil || n == 0 || (buf [0 ] != 'y' && buf [0 ] != 'Y' ) {
316+ fmt .Fprintln (sess , "Delete cancelled." )
317+ return nil
318+ }
319+
320+ if err := h .fileStore .DeleteVaultSecret (vaultName , path ); err != nil {
321+ return fmt .Errorf ("delete secret: %w" , err )
322+ }
323+
324+ if h .auditLog != nil {
325+ h .auditLog .VaultOp ("del" , username , sess .RemoteAddr ().String (), vaultName )
326+ }
327+ fmt .Fprintln (sess , "Deleted." )
328+ return nil
329+ }
330+
236331func (h * Handler ) handleVaultGet (sess ssh.Session , username string , pubKey gossh.PublicKey , vaultName , path string ) error {
237332 ag , cleanup , err := requireAgent (sess )
238333 if err != nil {
@@ -266,6 +361,9 @@ func (h *Handler) handleVaultGet(sess ssh.Session, username string, pubKey gossh
266361 }
267362 defer crypto .Zeroize (plaintext )
268363
364+ if h .auditLog != nil {
365+ h .auditLog .VaultOp ("get" , username , sess .RemoteAddr ().String (), vaultName )
366+ }
269367 _ , err = sess .Write (plaintext )
270368 return err
271369}
@@ -320,6 +418,9 @@ func (h *Handler) handleVaultSet(sess ssh.Session, username string, pubKey gossh
320418 return fmt .Errorf ("write secret: %w" , err )
321419 }
322420
421+ if h .auditLog != nil {
422+ h .auditLog .VaultOp ("set" , username , sess .RemoteAddr ().String (), vaultName )
423+ }
323424 fmt .Fprintln (sess , "Secret stored." )
324425 return nil
325426}
@@ -854,6 +955,7 @@ func helpText(color bool, version string) string {
854955 bold + "COMMANDS\n " + reset +
855956 cmd ("get" , yellow + "[vault:]<path>" + reset , "Decrypt and print a secret" ) +
856957 cmd ("set" , yellow + "[vault:]<path>" + reset , "Encrypt and store a secret" ) +
958+ cmd ("del" , yellow + "[vault:]<path>" + reset , "Delete a secret" ) +
857959 cmd ("list" , yellow + "[vault:][prefix]" + reset , "List secrets" ) +
858960 cmd ("ls" , yellow + "[vault:][prefix]" + reset , "Alias for list" ) +
859961 cmd ("move" , yellow + "<src> <dst>" + reset , "Move secret between vaults" ) +
0 commit comments