Skip to content

Commit 1537528

Browse files
fix(backend): redact MCP secrets at interactive-runner log site, clean orphan config
- The interactive runner logged req.Command verbatim on every passthrough start/restart/resume, re-leaking the Codex MCP -c env/header tokens that the manager log sites redacted. Add a LogCommand field (redacted) and log it. - writeFileNoFollow now removes the empty/partial file on write or close failure so a later SkipIfExists probe doesn't skip writing the real config.
1 parent 96f1038 commit 1537528

3 files changed

Lines changed: 21 additions & 4 deletions

File tree

apps/backend/internal/agent/runtime/lifecycle/manager_passthrough.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -450,9 +450,13 @@ func (m *Manager) writeFileNoFollow(path string, content []byte) (bool, error) {
450450
}
451451
if _, werr := file.Write(content); werr != nil {
452452
_ = file.Close()
453+
// Remove the empty file so a later SkipIfExists probe doesn't see it and
454+
// silently skip writing the real config.
455+
_ = os.Remove(path)
453456
return false, fmt.Errorf("write passthrough MCP config: %w", werr)
454457
}
455458
if cerr := file.Close(); cerr != nil {
459+
_ = os.Remove(path)
456460
return false, fmt.Errorf("close passthrough MCP config: %w", cerr)
457461
}
458462
return true, nil
@@ -640,8 +644,11 @@ func (m *Manager) profileCLIFlagTokens(p *AgentProfileInfo) []string {
640644
// terminal WebSocket is already connected).
641645
func buildInteractiveStartRequest(sessionID string, execution *AgentExecution, pt agents.PassthroughConfig, env map[string]string, cmd agents.Command, immediateStart bool) process.InteractiveStartRequest {
642646
return process.InteractiveStartRequest{
643-
SessionID: sessionID,
644-
Command: cmd.Args(),
647+
SessionID: sessionID,
648+
Command: cmd.Args(),
649+
// Redacted copy logged in place of Command by the interactive runner so
650+
// Codex MCP `-c` overrides (env/headers tokens) never reach process logs.
651+
LogCommand: redactPassthroughArgs(cmd.Args()),
645652
WorkingDir: execution.WorkspacePath,
646653
Env: env,
647654
PromptPattern: pt.PromptPattern,

apps/backend/internal/agentctl/server/process/interactive_lifecycle.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ func (r *InteractiveRunner) Start(ctx context.Context, req InteractiveStartReque
153153
r.logger.Info("interactive process created (waiting for terminal dimensions)",
154154
zap.String("process_id", id),
155155
zap.String("session_id", req.SessionID),
156-
zap.Strings("command", req.Command),
156+
zap.Strings("command", req.commandForLog()),
157157
zap.String("working_dir", req.WorkingDir),
158158
)
159159
}
@@ -208,7 +208,7 @@ func (r *InteractiveRunner) immediateStartProcess(req InteractiveStartRequest, p
208208
r.logger.Info("interactive process started immediately",
209209
zap.String("process_id", id),
210210
zap.String("session_id", req.SessionID),
211-
zap.Strings("command", req.Command),
211+
zap.Strings("command", req.commandForLog()),
212212
zap.String("working_dir", req.WorkingDir),
213213
)
214214
return nil

apps/backend/internal/agentctl/server/process/interactive_runner.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
type InteractiveStartRequest struct {
2323
SessionID string `json:"session_id"` // Required: Agent session owning this process
2424
Command []string `json:"command"` // Required: Command and args to execute
25+
LogCommand []string `json:"log_command,omitempty"` // Optional: redacted copy of Command for logging (e.g. MCP `-c` overrides with tokens); falls back to Command when unset
2526
WorkingDir string `json:"working_dir"` // Working directory
2627
Env map[string]string `json:"env,omitempty"` // Additional environment variables
2728
PromptPattern string `json:"prompt_pattern,omitempty"` // Regex pattern to detect agent prompt for turn completion
@@ -38,6 +39,15 @@ type InteractiveStartRequest struct {
3839
IsUserShell bool `json:"is_user_shell,omitempty"` // Mark as user shell process (excluded from session-level lookups)
3940
}
4041

42+
// commandForLog returns the command to log: the redacted LogCommand when the
43+
// caller supplied one, otherwise the raw Command.
44+
func (r InteractiveStartRequest) commandForLog() []string {
45+
if len(r.LogCommand) > 0 {
46+
return r.LogCommand
47+
}
48+
return r.Command
49+
}
50+
4151
// InteractiveProcessInfo represents the state of an interactive process.
4252
type InteractiveProcessInfo struct {
4353
ID string `json:"id"`

0 commit comments

Comments
 (0)