| 169 | } |
| 170 | |
| 171 | func withMountedCLIShellCommandsInRestrictedBash(workflowData *WorkflowData) map[string]any { |
| 172 | if workflowData == nil { |
| 173 | return nil |
| 174 | } |
| 175 | if workflowData.Tools == nil { |
| 176 | return workflowData.Tools |
| 177 | } |
| 178 | |
| 179 | hasRestrictedBash := hasBashRestrictedAllowlist(workflowData.Tools) |
| 180 | servers := getMountedCLIServerNamesIfBashRestricted(workflowData, workflowData.Tools, workflowData.SafeOutputs, workflowData.MCPScripts) |
| 181 | needsPlaywrightCLI := isPlaywrightCLIMode(workflowData.Tools) && hasRestrictedBash |
| 182 | needsGitHubCLI := isGitHubCLIModeEnabled(workflowData) && hasRestrictedBash |
| 183 | |
| 184 | if len(servers) == 0 && !needsPlaywrightCLI && !needsGitHubCLI { |
| 185 | return workflowData.Tools |
| 186 | } |
| 187 | |
| 188 | bashCommands, ok := workflowData.Tools["bash"].([]any) |
| 189 | if !ok || len(bashCommands) == 0 { |
| 190 | return workflowData.Tools |
| 191 | } |
| 192 | |
| 193 | copiedTools := make(map[string]any, len(workflowData.Tools)) |
| 194 | // A shallow copy is sufficient because we only replace the top-level "bash" |
| 195 | // value with a newly allocated slice and do not mutate nested map/slice values. |
| 196 | maps.Copy(copiedTools, workflowData.Tools) |
| 197 | |
| 198 | augmentedBash := append([]any(nil), bashCommands...) |
| 199 | for _, server := range servers { |
| 200 | command := server + ":*" |
| 201 | exists := false |
| 202 | for _, allowed := range augmentedBash { |
| 203 | if allowedStr, ok := allowed.(string); ok && allowedStr == command { |
| 204 | exists = true |
| 205 | break |
| 206 | } |
| 207 | } |
| 208 | if !exists { |
| 209 | augmentedBash = append(augmentedBash, command) |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | // When playwright is configured in CLI mode, playwright-cli must be executable. |
| 214 | // Automatically add it to the restricted bash allowlist so the agent can invoke it. |
| 215 | // This injection only applies when bash is restricted (explicit allowlist); when bash |
| 216 | // is unrestricted (nil or wildcard), playwright-cli is already accessible without |
| 217 | // explicit allowlisting. |
| 218 | if needsPlaywrightCLI { |
| 219 | const playwrightCLICommand = "playwright-cli:*" |
| 220 | if !slices.ContainsFunc(augmentedBash, func(v any) bool { |
| 221 | s, ok := v.(string) |
| 222 | return ok && s == playwrightCLICommand |
| 223 | }) { |
| 224 | augmentedBash = append(augmentedBash, playwrightCLICommand) |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | // When GitHub CLI mode is enabled (tools.github.mode: gh-proxy), GitHub access |