fix(tmux): escape serverUrl in pane shell commands

This commit is contained in:
YeonGyu-Kim
2026-03-17 15:16:54 +09:00
parent 1070b9170f
commit 0471078006
14 changed files with 546 additions and 23 deletions
+3
View File
@@ -59,10 +59,13 @@ export function appendSessionId(directory: string, sessionId: string): BoulderSt
if (!Array.isArray(state.session_ids)) {
state.session_ids = []
}
const originalSessionIds = [...state.session_ids]
state.session_ids.push(sessionId)
if (writeBoulderState(directory, state)) {
return state
}
state.session_ids = originalSessionIds
return null
}
return state
@@ -153,3 +153,25 @@ describe("#given git_env_prefix with commit footer", () => {
})
})
})
describe("#given idempotency of prefixGitCommandsInBashCodeBlocks", () => {
describe("#when git_env_prefix is provided and template already has prefixed commands in env prefix section", () => {
it("#then does NOT double-prefix the already-prefixed commands", () => {
const result = injectGitMasterConfig(SAMPLE_TEMPLATE, {
commit_footer: false,
include_co_authored_by: false,
git_env_prefix: "GIT_MASTER=1",
})
expect(result).not.toContain("GIT_MASTER=1 GIT_MASTER=1 git status")
expect(result).not.toContain("GIT_MASTER=1 GIT_MASTER=1 git add")
expect(result).not.toContain("GIT_MASTER=1 GIT_MASTER=1 git commit")
expect(result).not.toContain("GIT_MASTER=1 GIT_MASTER=1 git push")
expect(result).toContain("GIT_MASTER=1 git status")
expect(result).toContain("GIT_MASTER=1 git add")
expect(result).toContain("GIT_MASTER=1 git commit")
expect(result).toContain("GIT_MASTER=1 git push")
})
})
})
@@ -72,8 +72,16 @@ function prefixGitCommandsInBashCodeBlocks(template: string, prefix: string): st
function prefixGitCommandsInCodeBlock(codeBlock: string, prefix: string): string {
return codeBlock
.replace(LEADING_GIT_COMMAND_PATTERN, `$1${prefix} git`)
.replace(INLINE_GIT_COMMAND_PATTERN, `$1${prefix} git`)
.split("\n")
.map((line) => {
if (line.includes(prefix)) {
return line
}
return line
.replace(LEADING_GIT_COMMAND_PATTERN, `$1${prefix} git`)
.replace(INLINE_GIT_COMMAND_PATTERN, `$1${prefix} git`)
})
.join("\n")
}
function buildCommitFooterInjection(
@@ -199,3 +199,236 @@ describe("EXCLUDED_ENV_PATTERNS", () => {
}
})
})
describe("secret env var filtering", () => {
it("filters out ANTHROPIC_API_KEY", () => {
// given
process.env.ANTHROPIC_API_KEY = "sk-ant-api03-secret"
process.env.PATH = "/usr/bin"
// when
const cleanEnv = createCleanMcpEnvironment()
// then
expect(cleanEnv.ANTHROPIC_API_KEY).toBeUndefined()
expect(cleanEnv.PATH).toBe("/usr/bin")
})
it("filters out AWS_SECRET_ACCESS_KEY", () => {
// given
process.env.AWS_SECRET_ACCESS_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
process.env.AWS_ACCESS_KEY_ID = "AKIAIOSFODNN7EXAMPLE"
process.env.HOME = "/home/user"
// when
const cleanEnv = createCleanMcpEnvironment()
// then
expect(cleanEnv.AWS_SECRET_ACCESS_KEY).toBeUndefined()
expect(cleanEnv.AWS_ACCESS_KEY_ID).toBeUndefined()
expect(cleanEnv.HOME).toBe("/home/user")
})
it("filters out GITHUB_TOKEN", () => {
// given
process.env.GITHUB_TOKEN = "ghp_secrettoken123456789"
process.env.GITHUB_API_TOKEN = "another_secret_token"
process.env.SHELL = "/bin/bash"
// when
const cleanEnv = createCleanMcpEnvironment()
// then
expect(cleanEnv.GITHUB_TOKEN).toBeUndefined()
expect(cleanEnv.GITHUB_API_TOKEN).toBeUndefined()
expect(cleanEnv.SHELL).toBe("/bin/bash")
})
it("filters out OPENAI_API_KEY", () => {
// given
process.env.OPENAI_API_KEY = "sk-secret123456789"
process.env.LANG = "en_US.UTF-8"
// when
const cleanEnv = createCleanMcpEnvironment()
// then
expect(cleanEnv.OPENAI_API_KEY).toBeUndefined()
expect(cleanEnv.LANG).toBe("en_US.UTF-8")
})
it("filters out DATABASE_URL with credentials", () => {
// given
process.env.DATABASE_URL = "postgresql://user:password@localhost:5432/db"
process.env.DB_PASSWORD = "supersecretpassword"
process.env.TERM = "xterm-256color"
// when
const cleanEnv = createCleanMcpEnvironment()
// then
expect(cleanEnv.DATABASE_URL).toBeUndefined()
expect(cleanEnv.DB_PASSWORD).toBeUndefined()
expect(cleanEnv.TERM).toBe("xterm-256color")
})
})
describe("suffix-based secret filtering", () => {
it("filters variables ending with _KEY", () => {
// given
process.env.MY_API_KEY = "secret-value"
process.env.SOME_KEY = "another-secret"
process.env.TMPDIR = "/tmp"
// when
const cleanEnv = createCleanMcpEnvironment()
// then
expect(cleanEnv.MY_API_KEY).toBeUndefined()
expect(cleanEnv.SOME_KEY).toBeUndefined()
expect(cleanEnv.TMPDIR).toBe("/tmp")
})
it("filters variables ending with _SECRET", () => {
// given
process.env.AWS_SECRET = "secret-value"
process.env.JWT_SECRET = "jwt-secret-token"
process.env.USER = "testuser"
// when
const cleanEnv = createCleanMcpEnvironment()
// then
expect(cleanEnv.AWS_SECRET).toBeUndefined()
expect(cleanEnv.JWT_SECRET).toBeUndefined()
expect(cleanEnv.USER).toBe("testuser")
})
it("filters variables ending with _TOKEN", () => {
// given
process.env.ACCESS_TOKEN = "token-value"
process.env.BEARER_TOKEN = "bearer-token"
process.env.HOME = "/home/user"
// when
const cleanEnv = createCleanMcpEnvironment()
// then
expect(cleanEnv.ACCESS_TOKEN).toBeUndefined()
expect(cleanEnv.BEARER_TOKEN).toBeUndefined()
expect(cleanEnv.HOME).toBe("/home/user")
})
it("filters variables ending with _PASSWORD", () => {
// given
process.env.DB_PASSWORD = "db-password"
process.env.APP_PASSWORD = "app-secret"
process.env.NODE_ENV = "production"
// when
const cleanEnv = createCleanMcpEnvironment()
// then
expect(cleanEnv.DB_PASSWORD).toBeUndefined()
expect(cleanEnv.APP_PASSWORD).toBeUndefined()
expect(cleanEnv.NODE_ENV).toBe("production")
})
it("filters variables ending with _CREDENTIAL", () => {
// given
process.env.GCP_CREDENTIAL = "json-credential"
process.env.AZURE_CREDENTIAL = "azure-creds"
process.env.PWD = "/current/dir"
// when
const cleanEnv = createCleanMcpEnvironment()
// then
expect(cleanEnv.GCP_CREDENTIAL).toBeUndefined()
expect(cleanEnv.AZURE_CREDENTIAL).toBeUndefined()
expect(cleanEnv.PWD).toBe("/current/dir")
})
it("filters variables ending with _API_KEY", () => {
// given
// given
process.env.STRIPE_API_KEY = "sk_live_secret"
process.env.SENDGRID_API_KEY = "SG.secret"
process.env.SHELL = "/bin/zsh"
// when
const cleanEnv = createCleanMcpEnvironment()
// then
expect(cleanEnv.STRIPE_API_KEY).toBeUndefined()
expect(cleanEnv.SENDGRID_API_KEY).toBeUndefined()
expect(cleanEnv.SHELL).toBe("/bin/zsh")
})
})
describe("safe environment variables preserved", () => {
it("preserves PATH", () => {
// given
process.env.PATH = "/usr/bin:/usr/local/bin"
// when
const cleanEnv = createCleanMcpEnvironment()
// then
expect(cleanEnv.PATH).toBe("/usr/bin:/usr/local/bin")
})
it("preserves HOME", () => {
// given
process.env.HOME = "/home/testuser"
// when
const cleanEnv = createCleanMcpEnvironment()
// then
expect(cleanEnv.HOME).toBe("/home/testuser")
})
it("preserves SHELL", () => {
// given
process.env.SHELL = "/bin/bash"
// when
const cleanEnv = createCleanMcpEnvironment()
// then
expect(cleanEnv.SHELL).toBe("/bin/bash")
})
it("preserves LANG", () => {
// given
process.env.LANG = "en_US.UTF-8"
// when
const cleanEnv = createCleanMcpEnvironment()
// then
expect(cleanEnv.LANG).toBe("en_US.UTF-8")
})
it("preserves TERM", () => {
// given
process.env.TERM = "xterm-256color"
// when
const cleanEnv = createCleanMcpEnvironment()
// then
expect(cleanEnv.TERM).toBe("xterm-256color")
})
it("preserves TMPDIR", () => {
// given
process.env.TMPDIR = "/tmp"
// when
const cleanEnv = createCleanMcpEnvironment()
// then
expect(cleanEnv.TMPDIR).toBe("/tmp")
})
})
@@ -1,10 +1,28 @@
// Filters npm/pnpm/yarn config env vars that break MCP servers in pnpm projects (#456)
// Also filters secret-containing env vars to prevent exposure to malicious stdio MCP servers (#B-02)
export const EXCLUDED_ENV_PATTERNS: RegExp[] = [
// npm/pnpm/yarn config patterns (original)
/^NPM_CONFIG_/i,
/^npm_config_/,
/^YARN_/,
/^PNPM_/,
/^NO_UPDATE_NOTIFIER$/,
// Specific high-risk secret env vars (explicit blocks)
/^ANTHROPIC_API_KEY$/i,
/^AWS_ACCESS_KEY_ID$/i,
/^AWS_SECRET_ACCESS_KEY$/i,
/^GITHUB_TOKEN$/i,
/^DATABASE_URL$/i,
/^OPENAI_API_KEY$/i,
// Suffix-based patterns for common secret naming conventions
/_KEY$/i,
/_SECRET$/i,
/_TOKEN$/i,
/_PASSWORD$/i,
/_CREDENTIAL$/i,
/_API_KEY$/i,
]
export function createCleanMcpEnvironment(