Skip to content

resolve_sast_vulnerability workflow fails with shell operators like | or 2>/dev/null

Problem to solve

As a user of workflow resolve_sast_vulnerability/v1, I want to run the workflow on a LangSmith dataset with multiple examples to test the feature on a wide variety of vulnerabilities.

When the workflow is invoked, it performs multiple steps like gather_context , execute_fix and commit_and_open_mr, among others. During the execute_fix step, the agent tries to run certain commands like:

find . -name "*.js" -type f | head -20 # or
find / -name "bootstrap.js" -type f 2>/dev/null

However, these commands are:

  1. Using disallowed operators like | defined in duo_workflow_service/tools/command.py#L10

  2. Using certain args like 2>/dev/null that do not work on Golang os/exec executor:

    package main
    
    import (
     "fmt"
     "os/exec"
    )
    
    func main() {
     cmd := exec.Command("find", "/", "-name", `"bootstrap.js"`, "-type", "f", "2>/dev/null")
     output, err := cmd.Output()
     if err != nil {
     	fmt.Println("Error:", err)
     	return
     }
     fmt.Println(string(output))
    }
    
    //Command output: Error: exit status 1

    Reference: https://go.dev/play/p/7GeerG-HIlF

Which results in the agent throwing multiple unknown primary or operator errors during runtime:

DEBUG	runner/runner.go:235	Action result	{"correlation_id": "01K7NZ3GJJXPZGR5P8NC5G9S27", "workflow_id": "39", "result": "Error running tool: exit status 1. Result: find: 2>/dev/null: unknown primary or operator\n"}

As a result, the agent is unable to finish the workflow and therefore it hangs indefinitely until a timeout occurs.

Proposal

Modify duo_workflow_service/tools/command.py to automatically detect shell operators and wrap commands in a shell when needed:

async def _arun(
    self,
    program: str,
    args: Optional[str] = None,
) -> str:
    args = args or ""
    
    # Existing validation checks...
    for disallowed_operator in _DISALLOWED_OPERATORS:
        if disallowed_operator in program or disallowed_operator in args:
            return f"'{disallowed_operator}' operators are not supported..."
    
    for disallowed_command in _DISALLOWED_COMMANDS:
        if program.startswith(disallowed_command):
            return f"{disallowed_command} commands are not supported..."
    
    # NEW: Detect shell operators
    shell_operators = ['>', '', '&>', '>>']
    needs_shell = any(op in args for op in shell_operators)
    
    if needs_shell:
        # Execute via shell to handle operators
        full_command = f"{program} {args}"
        return await _execute_action(
            self.metadata,
            contract_pb2.Action(
                runCommand=contract_pb2.RunCommandAction(
                    program="sh",
                    arguments=["-c", full_command],
                    flags=[],
                )
            ),
        )
    else:
        # Direct execution (existing behavior)
        return await _execute_action(
            self.metadata,
            contract_pb2.Action(
                runCommand=contract_pb2.RunCommandAction(
                    program=program,
                    arguments=args.split(),
                    flags=[],
                )
            ),
        )

Further details

Benefits:

  1. Agents can use standard shell redirection without errors
  2. Commands without shell operators continue to execute directly (more secure)
  3. No breaking changes to existing functionality
  4. Minimal code modification

Links / references

Edited by Fabrizio J. Piva