(skill SkillSpec, command string)
| 25 | } |
| 26 | |
| 27 | func DecideInlineShellExecution(skill SkillSpec, command string) SkillShellDecision { |
| 28 | if strings.TrimSpace(command) == "" { |
| 29 | return SkillShellDecision{ |
| 30 | Allowed: false, |
| 31 | Reason: "Skill '" + skill.CanonicalName + "' has an empty inline shell command.", |
| 32 | } |
| 33 | } |
| 34 | executable := NormalizeShellExecutable(skill.Frontmatter.Shell) |
| 35 | if skill.Frontmatter.Shell != nil && !SafeInlineShellExecutables[executable] { |
| 36 | return SkillShellDecision{ |
| 37 | Allowed: false, |
| 38 | Reason: "Skill '" + skill.CanonicalName + "' shell executable '" + *skill.Frontmatter.Shell + "' is not allowed for inline shell commands.", |
| 39 | NormalizedExecutable: executable, |
| 40 | } |
| 41 | } |
| 42 | switch skill.Source { |
| 43 | case SkillSourceBundled: |
| 44 | decision := SkillShellDecision{Allowed: true, RequiresApproval: skill.Frontmatter.Shell != nil, NormalizedExecutable: executable} |
| 45 | if decision.RequiresApproval { |
| 46 | decision.Reason = "Bundled skill requests a custom shell executable." |
| 47 | } |
| 48 | return decision |
| 49 | case SkillSourceUser, SkillSourceProject: |
| 50 | return SkillShellDecision{ |
| 51 | Allowed: true, |
| 52 | RequiresApproval: true, |
| 53 | Reason: strings.Title(string(skill.Source)) + " skills require approval for inline shell commands.", |
| 54 | NormalizedExecutable: executable, |
| 55 | } |
| 56 | default: |
| 57 | return SkillShellDecision{ |
| 58 | Allowed: false, |
| 59 | Reason: "Skill source '" + string(skill.Source) + "' is not allowed to execute inline shell commands.", |
| 60 | NormalizedExecutable: executable, |
| 61 | } |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | func NormalizeShellExecutable(executable *string) string { |
| 66 | if executable == nil { |
no test coverage detected