Skip to content

Commit 2e82981

Browse files
committed
feat: comment on security of runExternal, refactor based on gemini suggestions
1 parent 07cb5a1 commit 2e82981

File tree

2 files changed

+45
-24
lines changed

2 files changed

+45
-24
lines changed

cmd/cli/commands/launch.go

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,16 @@ var containerApps = map[string]containerApp{
5454

5555
// hostApp describes a native CLI app launched on the host.
5656
type hostApp struct {
57-
envFn func(baseURL string) []string
57+
envFn func(baseURL string) []string
58+
configInstructions func(baseURL string) []string // for apps that need manual config
5859
}
5960

6061
// hostApps are launched as native executables on the host.
6162
var hostApps = map[string]hostApp{
6263
"opencode": {envFn: openaiEnv(openaiPathSuffix)},
6364
"codex": {envFn: openaiEnv("/v1")},
6465
"claude": {envFn: anthropicEnv},
65-
"clawdbot": {envFn: nil},
66+
"clawdbot": {configInstructions: clawdbotConfigInstructions},
6667
}
6768

6869
// supportedApps is derived from the registries above.
@@ -198,18 +199,18 @@ func launchContainerApp(cmd *cobra.Command, ca containerApp, baseURL string, ima
198199
// launchHostApp launches a native host app executable.
199200
func launchHostApp(cmd *cobra.Command, bin string, baseURL string, cli hostApp, appArgs []string, dryRun bool) error {
200201
if _, err := exec.LookPath(bin); err != nil {
201-
cmd.Printf("%q executable not found in PATH.\n", bin)
202+
cmd.PrintErrf("%q executable not found in PATH.\n", bin)
202203
if cli.envFn != nil {
203-
cmd.Printf("Configure your app to use:\n")
204+
cmd.PrintErrf("Configure your app to use:\n")
204205
for _, e := range cli.envFn(baseURL) {
205-
cmd.Printf(" %s\n", e)
206+
cmd.PrintErrf(" %s\n", e)
206207
}
207208
}
208209
return fmt.Errorf("%s not found; please install it and re-run", bin)
209210
}
210211

211212
if cli.envFn == nil {
212-
return launchUnconfigurableHostApp(cmd, bin, baseURL, dryRun)
213+
return launchUnconfigurableHostApp(cmd, bin, baseURL, cli, dryRun)
213214
}
214215

215216
env := cli.envFn(baseURL)
@@ -224,24 +225,35 @@ func launchHostApp(cmd *cobra.Command, bin string, baseURL string, cli hostApp,
224225
}
225226

226227
// launchUnconfigurableHostApp handles host apps that need manual config rather than env vars.
227-
func launchUnconfigurableHostApp(cmd *cobra.Command, bin string, baseURL string, dryRun bool) error {
228+
func launchUnconfigurableHostApp(cmd *cobra.Command, bin string, baseURL string, cli hostApp, dryRun bool) error {
228229
enginesEP := baseURL + openaiPathSuffix
229230
cmd.Printf("Configure %s to use Docker Model Runner:\n", bin)
230231
cmd.Printf(" Base URL: %s\n", enginesEP)
231232
cmd.Printf(" API type: openai-completions\n")
232-
cmd.Printf(" API key: docker-model-runner\n")
233-
if bin == "clawdbot" {
233+
cmd.Printf(" API key: %s\n", dummyAPIKey)
234+
235+
if cli.configInstructions != nil {
234236
cmd.Printf("\nExample:\n")
235-
cmd.Printf(" clawdbot config set models.providers.docker-model-runner.baseUrl %q\n", enginesEP)
236-
cmd.Printf(" clawdbot config set models.providers.docker-model-runner.api openai-completions\n")
237-
cmd.Printf(" clawdbot config set models.providers.docker-model-runner.apiKey docker-model-runner\n")
237+
for _, line := range cli.configInstructions(baseURL) {
238+
cmd.Printf(" %s\n", line)
239+
}
238240
}
239241
if dryRun {
240242
return nil
241243
}
242244
return runExternal(cmd, nil, bin)
243245
}
244246

247+
// clawdbotConfigInstructions returns configuration commands for clawdbot.
248+
func clawdbotConfigInstructions(baseURL string) []string {
249+
ep := baseURL + openaiPathSuffix
250+
return []string{
251+
fmt.Sprintf("clawdbot config set models.providers.docker-model-runner.baseUrl %q", ep),
252+
"clawdbot config set models.providers.docker-model-runner.api openai-completions",
253+
fmt.Sprintf("clawdbot config set models.providers.docker-model-runner.apiKey %s", dummyAPIKey),
254+
}
255+
}
256+
245257
// openaiEnv returns an env builder that sets OpenAI-compatible
246258
// environment variables using the given path suffix.
247259
func openaiEnv(suffix string) func(string) []string {
@@ -280,6 +292,8 @@ func withEnv(extra ...string) []string {
280292
}
281293

282294
// runExternal executes a program inheriting stdio.
295+
// Security: prog and progArgs are either hardcoded values or user-provided
296+
// arguments that the user explicitly intends to pass to the launched app.
283297
func runExternal(cmd *cobra.Command, env []string, prog string, progArgs ...string) error {
284298
c := exec.Command(prog, progArgs...)
285299
c.Stdout = cmd.OutOrStdout()

cmd/cli/commands/launch_test.go

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -283,44 +283,51 @@ func TestLaunchHostAppDryRunAnthropic(t *testing.T) {
283283
}
284284

285285
func TestLaunchHostAppNotFound(t *testing.T) {
286-
buf := new(bytes.Buffer)
287-
cmd := newTestCmd(buf)
286+
stdout := new(bytes.Buffer)
287+
stderr := new(bytes.Buffer)
288+
cmd := &cobra.Command{}
289+
cmd.SetOut(stdout)
290+
cmd.SetErr(stderr)
288291

289292
cli := hostApp{envFn: openaiEnv(openaiPathSuffix)}
290293
err := launchHostApp(cmd, "nonexistent-binary-xyz", testBaseURL, cli, nil, false)
291294
require.Error(t, err)
292295
require.Contains(t, err.Error(), "not found")
293296

294-
output := buf.String()
295-
require.Contains(t, output, "not found in PATH")
296-
require.Contains(t, output, "Configure your app to use:")
297+
errOutput := stderr.String()
298+
require.Contains(t, errOutput, "not found in PATH")
299+
require.Contains(t, errOutput, "Configure your app to use:")
297300
}
298301

299302
func TestLaunchHostAppNotFoundNilEnvFn(t *testing.T) {
300-
buf := new(bytes.Buffer)
301-
cmd := newTestCmd(buf)
303+
stdout := new(bytes.Buffer)
304+
stderr := new(bytes.Buffer)
305+
cmd := &cobra.Command{}
306+
cmd.SetOut(stdout)
307+
cmd.SetErr(stderr)
302308

303309
cli := hostApp{envFn: nil}
304310
err := launchHostApp(cmd, "nonexistent-binary-xyz", testBaseURL, cli, nil, false)
305311
require.Error(t, err)
306312

307-
output := buf.String()
308-
require.Contains(t, output, "not found in PATH")
309-
require.NotContains(t, output, "Configure your app to use:")
313+
errOutput := stderr.String()
314+
require.Contains(t, errOutput, "not found in PATH")
315+
require.NotContains(t, errOutput, "Configure your app to use:")
310316
}
311317

312318
func TestLaunchUnconfigurableHostAppDryRun(t *testing.T) {
313319
buf := new(bytes.Buffer)
314320
cmd := newTestCmd(buf)
315321

316-
err := launchUnconfigurableHostApp(cmd, "clawdbot", testBaseURL, true)
322+
cli := hostApp{configInstructions: clawdbotConfigInstructions}
323+
err := launchUnconfigurableHostApp(cmd, "clawdbot", testBaseURL, cli, true)
317324
require.NoError(t, err)
318325

319326
output := buf.String()
320327
require.Contains(t, output, "Configure clawdbot to use Docker Model Runner:")
321328
require.Contains(t, output, "Base URL: "+testBaseURL+"/engines/v1")
322329
require.Contains(t, output, "API type: openai-completions")
323-
require.Contains(t, output, "API key: docker-model-runner")
330+
require.Contains(t, output, "API key: "+dummyAPIKey)
324331
require.Contains(t, output, "clawdbot config set models.providers.docker-model-runner.baseUrl")
325332
}
326333

0 commit comments

Comments
 (0)