agent: reject option-like resume session ids · Entire
agent: reject option-like resume session ids
e8c185a→main · peyton-alt · 2w ago · 4 files · +37 added/-5 removed
Sessions
f68a8bbbdf1d View transcript
?\ Add Trail Resume Subcommand Codex · GPT-5.5 · 1 step
Changes
4
cmd/entire/cli
agent
- Mresume_command.go +16/-5
- Mresume_command_test.go +6
validation
- Mvalidators.go +6
- Mvalidators_test.go +9
6 unmodified lines
7
8
9
10
11
12
13
10 unmodified lines
24
25
26
26
27
28
29
30
31
31
32
33
34
35
36
36
37
38
39
40
41
41
42
43
44
45
46
46
47
48
49
50
1 unmodified line
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
6 unmodified lines
"strings"
"github.com/entireio/cli/cmd/entire/cli/agent/types"
"github.com/entireio/cli/cmd/entire/cli/validation"
// ForegroundCommandSpec describes a command Entire can launch in the caller's
10 unmodified lines
sessionID = strings.TrimSpace(sessionID)
switch name {
case AgentNameClaudeCode:
if sessionID == "" {
if !isLaunchableResumeSessionID(sessionID) {
return ForegroundCommandSpec{}, false
}
return ForegroundCommandSpec{Binary: "claude", Args: []string{"-r", sessionID}}, true
}
case AgentNameCodex:
if sessionID == "" {
if !isLaunchableResumeSessionID(sessionID) {
return ForegroundCommandSpec{}, false
}
return ForegroundCommandSpec{Binary: "codex", Args: []string{"resume", sessionID}}, true
}
case AgentNameCopilotCLI:
if sessionID == "" {
if !isLaunchableResumeSessionID(sessionID) {
return ForegroundCommandSpec{}, false
}
return ForegroundCommandSpec{Binary: "copilot", Args: []string{"--resume", sessionID}}, true
}
case AgentNameFactoryAIDroid:
if sessionID == "" {
if !isLaunchableResumeSessionID(sessionID) {
return ForegroundCommandSpec{}, false
}
return ForegroundCommandSpec{Binary: "droid", Args: []string{"--session-id", sessionID}}, true
}
case AgentNameGemini:
if sessionID == "" {
if !isLaunchableResumeSessionID(sessionID) {
return ForegroundCommandSpec{}, false
}
return ForegroundCommandSpec{Binary: "gemini", Args: []string{"--resume", sessionID}}, true
}
1 unmodified line
if sessionID == "" {
return ForegroundCommandSpec{Binary: "opencode"}, true
}
if !isLaunchableResumeSessionID(sessionID) {
return ForegroundCommandSpec{}, false
}
return ForegroundCommandSpec{Binary: "opencode", Args: []string{"-s", sessionID}}, true
case AgentNamePi:
if sessionID == "" {
return ForegroundCommandSpec{Binary: "pi", Args: []string{"--continue"}}, true
}
if !isLaunchableResumeSessionID(sessionID) {
return ForegroundCommandSpec{}, false
}
return ForegroundCommandSpec{Binary: "pi", Args: []string{"--session", sessionID}}, true
default:
return ForegroundCommandSpec{}, false
}
}
func isLaunchableResumeSessionID(sessionID string) bool {
return sessionID != "" && validation.ValidateSessionID(sessionID) == nil
}
// NewResumeForegroundCommand builds a foreground command for resuming a session,
// when the agent has a launchable resume command. ok=false means callers should
// print FormatResumeCommand for the user instead.
Mcmd/entire/cli/agent/resume_command.go +16/-5
65 unmodified lines
66
67
68
69
70
71
72
73
74
75
76
77
65 unmodified lines
want: ForegroundCommandSpec{Binary: "pi", Args: []string{"--session", "session-123"}},
wantOK: true,
},
{
name: "leading dash session id is not launchable",
agentName: AgentNameClaudeCode,
sessionID: "--dangerously-skip-permissions",
wantOK: false,
},
{
name: "cursor is print only",
agentName: AgentNameCursor,
Mcmd/entire/cli/agent/resume_command_test.go +6
20 unmodified lines
21
22
23
24
25
26
27
28
29
53 unmodified lines
83
84
85
86
87
88
89
90
91
20 unmodified lines
if strings.TrimSpace(id) == "" {
return errors.New("session ID cannot be empty")
}
if strings.HasPrefix(id, "-") {
return fmt.Errorf("invalid session ID %q: starts with dash", id)
}
if strings.ContainsAny(id, "/\\") {
return fmt.Errorf("invalid session ID %q: contains path separators", id)
}
53 unmodified lines
if id == "" {
return errors.New("agent session ID cannot be empty")
}
if strings.HasPrefix(id, "-") {
return fmt.Errorf("invalid agent session ID %q: starts with dash", id)
}
if !pathSafeRegex.MatchString(id) {
return fmt.Errorf("invalid agent session ID %q: must be alphanumeric with underscores/hyphens only", id)
}
Mcmd/entire/cli/validation/validators.go +6
40 unmodified lines
41
42
43
44
45
46
47
48
49
50
51
52
53
223 unmodified lines
277
278
279
280
281
282
283
284
40 unmodified lines
wantErr: true,
errMsg: "session ID cannot be empty",
},
// Leading dash (security-critical - option injection prevention)
{
name: "leading dash",
sessionID: "--dangerously-skip-permissions",
wantErr: true,
errMsg: "starts with dash",
},
// Path separators (security-critical - path traversal prevention)
{
name: "session ID with forward slash",
223 unmodified lines
{name: "test session id", id: "test-session-1", wantErr: false},
{name: "alphanumeric", id: "session123", wantErr: false},
{name: "with underscores", id: "test_session_1", wantErr: false},
// Invalid - option injection
{name: "leading dash", id: "--dangerously-skip-permissions", wantErr: true},
// Invalid - empty (required field)
{name: "empty rejected", id: "", wantErr: true},
// Invalid - path traversal