//! SECRETS namespace (0x0003) — secret retrieval from env or Vault. use async_trait::async_trait; use crate::shellstream::{secrets, ShellstreamResponse}; use super::NamespaceHandler; pub struct SecretsHandler; impl SecretsHandler { pub fn new() -> Self { Self } } #[async_trait] impl NamespaceHandler for SecretsHandler { async fn handle( &self, function_id: u16, payload: &[u8], session_id: &[u8; 16], ) -> ShellstreamResponse { match function_id { secrets::GET => self.get(payload, session_id).await, secrets::PUT => ShellstreamResponse::denied(*session_id, 0, "PUT not supported in soft mode"), secrets::ROTATE => ShellstreamResponse::denied(*session_id, 0, "ROTATE not supported in soft mode"), _ => ShellstreamResponse::error( *session_id, 0, &format!("Unknown SECRETS function: {function_id:#06x}"), ), } } } impl SecretsHandler { async fn get(&self, payload: &[u8], session_id: &[u8; 16]) -> ShellstreamResponse { let payload_parsed: serde_json::Value = rmp_serde::from_slice(payload).unwrap_or(serde_json::Value::Null); let path = payload_parsed .get("path") .and_then(|v| v.as_str()) .unwrap_or(""); if path.is_empty() { return ShellstreamResponse::error(*session_id, 0, "Missing 'path'"); } // Soft mode: look up environment variable // Convert path like "db/password" to env var "DB_PASSWORD" let env_key = path.replace('/', "_").to_uppercase(); match std::env::var(&env_key) { Ok(value) => { let response = rmp_serde::to_vec(&serde_json::json!({ "value": value, "source": "env", })) .unwrap_or_default(); ShellstreamResponse::ok(*session_id, 0, response) } Err(_) => ShellstreamResponse::error( *session_id, 0, &format!("Secret not found: {path} (env: {env_key})"), ), } } }