Skip to content

Arbitrary code execution: model-grounded action string passed to eval() in create_pyautogui_code #201

Description

@Jiangrong-W

Summary

The S3 worker pipeline evaluates the model's grounded-action string with the built-in eval(), with no restriction to the intended agent.<action>(...) dispatch. A model that emits arbitrary Python — via indirect prompt injection from on-screen/observation content, or a compromised model endpoint — gets it executed in the Agent-S host process.

Details

gui_agents/s3/utils/common_utils.py:

def create_pyautogui_code(agent, code: str, obs: Dict) -> str:
    agent.assign_screenshot(obs)  # Necessary for grounding
    exec_code = eval(code)        # <-- model-produced `code` run with bare eval()
    return exec_code

code is model-derived: gui_agents/s3/agents/worker.py calls parse_code_from_string(plan), which returns the contents of the last fenced code block of the model response with no agent.X() filtering (unlike the legacy S1/S2/S2.5 path, which goes through extract_first_agent_function). The two format checkers (SINGLE_ACTION_FORMATTER, CODE_VALID_FORMATTER) only drive a re-prompt loop — call_llm_formatted returns the response after its retries regardless, and the "valid code" check is create_pyautogui_code, i.e. it runs the eval itself. single_action_check uses a greedy agent\.\w+\(\s*.*\) regex, so a payload that prepends arbitrary code before a single agent.click() passes the check.

This runs on the host orchestrator (cli_app.py: run_agent drives the local desktop via pyautogui), not in a sandbox, and on the default S3 path — no opt-in required.

Reproduction (deweaponized)

A model "plan" whose last python code block is:

__import__('os').system('touch /tmp/agent_s_poc') or agent.click(0, 0)

passes single_action_check and reaches eval(code) in create_pyautogui_code, executing os.system('touch /tmp/agent_s_poc') (sentinel only — no destructive action). Verified against current main (commit 73ea172) by reproducing the exact parse_code_from_string + single_action_check + create_pyautogui_code logic; the sentinel file was created.

Impact

Arbitrary code execution in the Agent-S host process from model-controlled output, on the default S3 path.

Suggested fix

A PR is attached: dispatch the grounded action via a safe ast-based parser — allow only a single agent.<method>(...) call where <method> is a real grounding-agent action (is_agent_action) and every argument is a plain literal (ast.literal_eval) — instead of eval. This preserves every valid grounded action while rejecting arbitrary expressions/builtins. A regression test is included.

Minor, related (not fixed in the attached PR): cli_app.py:215 runs exec(code[0]) under a comment "Ask for permission before executing", but there is no actual prompt — worth a separate look.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions