Redact command environment variables from log output (#32985)

Before/After (linebreaks added for readability)
```log 
# before
INFO  [project::context_server_store::extension]
loaded command for context server mcp-server-github:
Command { 
  command: "/Users/peter/Library/Application Support/Zed/extensions/work/mcp-server-github/github-mcp-server-v0.5.0/github-mcp-server", 
  args: ["stdio"], 
  env: [("GITHUB_PERSONAL_ACCESS_TOKEN", "gho_WOOOOOOOOOOOOOOO")] 
}

#after
INFO  [project::context_server_store::extension]
loaded command for context server mcp-server-github:
Command {
  command: "/Users/peter/Library/Application Support/Zed/extensions/work/mcp-server-github/github-mcp-server-v0.5.0/github-mcp-server",
  args: ["stdio"],
  env: [("GITHUB_PERSONAL_ACCESS_TOKEN", "[REDACTED]")]
}
```

Release Notes:

- Redact sensitive environment variables from MCP logs
This commit is contained in:
Peter Tripp 2025-06-21 11:19:23 -04:00 committed by GitHub
parent 76e3136369
commit a713c66a9d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 45 additions and 2 deletions

View file

@ -16,6 +16,7 @@ use gpui::AsyncApp;
use parking_lot::RwLock;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use util::redact::should_redact;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ContextServerId(pub Arc<str>);
@ -26,13 +27,29 @@ impl Display for ContextServerId {
}
}
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema)]
pub struct ContextServerCommand {
pub path: String,
pub args: Vec<String>,
pub env: Option<HashMap<String, String>>,
}
impl std::fmt::Debug for ContextServerCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let filtered_env = self.env.as_ref().map(|env| {
env.iter()
.map(|(k, v)| (k, if should_redact(k) { "[REDACTED]" } else { v }))
.collect::<Vec<_>>()
});
f.debug_struct("ContextServerCommand")
.field("path", &self.path)
.field("args", &self.args)
.field("env", &filtered_env)
.finish()
}
}
enum ContextServerTransport {
Stdio(ContextServerCommand),
Custom(Arc<dyn crate::transport::Transport>),

View file

@ -5,6 +5,8 @@ mod slash_command;
use std::ops::Range;
use util::redact::should_redact;
pub use context_server::*;
pub use dap::*;
pub use lsp::*;
@ -14,7 +16,6 @@ pub use slash_command::*;
pub type EnvVars = Vec<(String, String)>;
/// A command.
#[derive(Debug)]
pub struct Command {
/// The command to execute.
pub command: String,
@ -24,6 +25,22 @@ pub struct Command {
pub env: EnvVars,
}
impl std::fmt::Debug for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let filtered_env = self
.env
.iter()
.map(|(k, v)| (k, if should_redact(k) { "[REDACTED]" } else { v }))
.collect::<Vec<_>>();
f.debug_struct("Command")
.field("command", &self.command)
.field("args", &self.args)
.field("env", &filtered_env)
.finish()
}
}
/// A label containing some code.
#[derive(Debug, Clone)]
pub struct CodeLabel {

View file

@ -0,0 +1,8 @@
/// Whether a given environment variable name should have its value redacted
pub fn should_redact(env_var_name: &str) -> bool {
const REDACTED_SUFFIXES: &[&str] =
&["KEY", "TOKEN", "PASSWORD", "SECRET", "PASS", "CREDENTIALS"];
REDACTED_SUFFIXES
.iter()
.any(|suffix| env_var_name.ends_with(suffix))
}

View file

@ -4,6 +4,7 @@ pub mod command;
pub mod fs;
pub mod markdown;
pub mod paths;
pub mod redact;
pub mod serde;
pub mod shell_env;
pub mod size;