From 63e640c022d8a06062fd6362d9e50f7b05bc4059 Mon Sep 17 00:00:00 2001 From: Greg Gauthier Date: Sat, 7 Mar 2026 17:11:13 +0000 Subject: [PATCH] feat(recipe): add safety whitelist for allowed shell commands Implement a read-only command whitelist in the recipe loader to reject potentially dangerous shell commands, ensuring only safe operations like ls, pwd, cat, etc., are permitted. This enhances security by preventing execution of unauthorized commands in recipes. --- internal/recipe/loader.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/internal/recipe/loader.go b/internal/recipe/loader.go index b4ac37d..5a890ec 100644 --- a/internal/recipe/loader.go +++ b/internal/recipe/loader.go @@ -11,6 +11,21 @@ import ( "gopkg.in/yaml.v3" ) +// Global safe read-only whitelist +var safeReadOnlyCommands = map[string]bool{ + "ls": true, + "pwd": true, + "cat": true, + "tree": true, + "git status": true, + "git log": true, + "find": true, + "grep": true, + "cndump -s": true, + "tea repos list -o csv -lm 100": true, + "tea repos search -o csv": true, +} + var ( // stepRe still finds the headings (this one is solid) stepRe = regexp.MustCompile(`(?m)^### Step (\d+): (.+)$`) @@ -32,6 +47,14 @@ func Load(path string, userParams map[string]any) (*Recipe, error) { return nil, fmt.Errorf("yaml parse: %w", err) } + // === SAFETY CHECK: reject dangerous allowed_shell_commands === + for _, cmd := range r.AllowedShellCommands { + trimmed := strings.TrimSpace(strings.ToLower(cmd)) + if !safeReadOnlyCommands[trimmed] && !strings.HasPrefix(trimmed, "git status") && !strings.HasPrefix(trimmed, "git log") { + return nil, fmt.Errorf("\033[31mERROR: Recipe contains unsafe shell command: %q\033[0m\n\nOnly the following read-only commands are allowed:\n ls, pwd, cat, tree, git status, git log, find, grep\n\nRemove or replace the dangerous command and try again.", cmd) + } + } + // Apply defaults + user --param overrides if r.Parameters == nil { r.Parameters = make(map[string]Parameter)