(ctx context.Context, execCtx ExecutionContext, input any)
| 789 | if in.Timeout != nil { |
| 790 | timeoutValue = in.Timeout |
| 791 | } |
| 792 | seconds, errMsg := parseBashTimeoutSeconds(timeoutValue, in.TimeoutSeconds, config.GetConfig().ShellTimeoutSeconds) |
| 793 | if errMsg != "" || seconds <= 0 { |
| 794 | return 0 |
| 795 | } |
| 796 | return time.Duration(seconds*float64(time.Second)) + time.Second |
| 797 | } |
| 798 | |
| 799 | func (t *BashTool) IsReadOnly(input any) bool { |
| 800 | in := deref[BashInput](input) |
| 801 | return bashpkg.ClassifyCommand(in.Command).CommandClass == bashpkg.CommandClassSafe |
| 802 | } |
| 803 | |
| 804 | func (t *BashTool) IsDestructive(input any) bool { |
| 805 | in := deref[BashInput](input) |
| 806 | category := bashpkg.ClassifyBashCommand(in.Command) |
| 807 | return category == "write" || category == "unknown" |
| 808 | } |
| 809 | |
| 810 | func (t *BashTool) Execute(ctx context.Context, execCtx ExecutionContext, input any) (string, error) { |
| 811 | in := deref[BashInput](input) |
| 812 | cwd := stringFromExecCtx(execCtx, "cwd") |
| 813 | if cwd == "" { |
| 814 | cwd = config.GetConfig().CWD |
| 815 | } |
| 816 | cfg := configFromExecCtx(execCtx) |
| 817 | t.configureSandbox(cfg) |
| 818 | var timeoutValue any |
| 819 | if in.Timeout != nil { |
| 820 | timeoutValue = in.Timeout |
| 821 | } |
| 822 | timeoutSeconds, timeoutErr := parseBashTimeoutSeconds(timeoutValue, in.TimeoutSeconds, config.GetConfig().ShellTimeoutSeconds) |
| 823 | if timeoutErr != "" { |
| 824 | return "<tool_use_error>\nInvalid timeout: " + timeoutErr + "\nUse timeout_seconds for seconds, or timeout as milliseconds/seconds string. Maximum 120 seconds.\n</tool_use_error>", nil |
| 825 | } |
| 826 | timeout := time.Duration(timeoutSeconds * float64(time.Second)) |
| 827 | |
| 828 | secResult := bashpkg.RunAllSecurityChecks(in.Command) |
| 829 | if bashpkg.IsBlocking(secResult) { |
| 830 | return "<tool_use_error>\nCommand blocked by security checks:\n " + |
| 831 | firstFindingDescriptions(secResult.Findings, 3) + |
| 832 | "\nThe command contains patterns that are unsafe. Use an alternative approach.\n</tool_use_error>", nil |
| 833 | } |
| 834 | |
| 835 | permResult := bashpkg.AnalyzeCommandPermissions(in.Command) |
| 836 | _ = permResult |
| 837 | |
| 838 | stripped := strings.TrimSpace(in.Command) |
| 839 | if stripped != "" && strings.EqualFold(strings.Fields(stripped)[0], "sed") { |
| 840 | sedResult := bashpkg.ValidateSedCommand(in.Command) |
| 841 | if !sedResult.Safe { |
| 842 | return "<tool_use_error>\nsed command not auto-approved: " + sedResult.Reason + |
| 843 | "\nThe sed expression uses features that require user review. Use read_file + write_file/edit_file instead, or simplify the sed expression to use only safe patterns.\n</tool_use_error>", nil |
| 844 | } |
| 845 | } |
| 846 | |
| 847 | if !bashpkg.IsAbsoluteDestroy(in.Command) { |
| 848 | valid, invalid := bashpkg.ValidatePaths(in.Command, cwd) |
nothing calls this directly
no test coverage detected