-
-
Notifications
You must be signed in to change notification settings - Fork 0
Fix shell injection, SSRF, broken fallback, add /agent command, improve LLM resilience #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ | |
| from __future__ import annotations | ||
|
|
||
| import glob as glob_module | ||
| import shlex | ||
| import subprocess | ||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING | ||
|
|
@@ -62,6 +63,7 @@ def handle(self, text: str) -> bool: | |
| "/help": self._cmd_help, | ||
| "/exit": self._cmd_exit, | ||
| "/quit": self._cmd_exit, | ||
| "/agent": self._cmd_agent, | ||
| }.get(cmd) | ||
|
|
||
| if handler: | ||
|
|
@@ -175,8 +177,9 @@ def _cmd_run(self, args: str) -> None: | |
| self.io.print_warning("Usage: /run <shell command>") | ||
| return | ||
| try: | ||
| cmd = shlex.split(args) | ||
| result = subprocess.run( | ||
| args, shell=True, capture_output=True, text=True, timeout=60 | ||
| cmd, shell=False, capture_output=True, text=True, timeout=60 | ||
| ) | ||
| output = (result.stdout + result.stderr).strip() | ||
| if output: | ||
|
|
@@ -280,10 +283,10 @@ def _cmd_git(self, args: str) -> None: | |
| if not args: | ||
| self.io.print_warning("Usage: /git <git subcommand>") | ||
| return | ||
| cmd = f"git {args}" | ||
| try: | ||
| cmd = ["git"] + shlex.split(args) | ||
| result = subprocess.run( | ||
| cmd, shell=True, capture_output=True, text=True, timeout=30 | ||
| cmd, shell=False, capture_output=True, text=True, timeout=30 | ||
| ) | ||
| output = (result.stdout + result.stderr).strip() | ||
| if output: | ||
|
|
@@ -325,6 +328,61 @@ def _cmd_ls(self, args: str) -> None: | |
| except PermissionError: | ||
| self.io.print_error(f"Permission denied: {path}") | ||
|
|
||
| def _cmd_agent(self, args: str) -> None: | ||
| """Run the multi-agent pipeline (or a single named agent). | ||
|
|
||
| Usage: | ||
| /agent <task description> | ||
| /agent --agent coder <task description> | ||
| """ | ||
| if not args: | ||
| self.io.print_warning( | ||
| "Usage: /agent [--agent <name>] <task description>\n" | ||
| "Available agents: planner, coder, reviewer, tester, " | ||
| "debugger, doc_writer, security, refactor" | ||
| ) | ||
| return | ||
|
|
||
| # Parse optional --agent flag | ||
| agent_name: str | None = None | ||
| task = args | ||
| parts = args.split(maxsplit=2) | ||
| if len(parts) >= 2 and parts[0] == "--agent": | ||
| agent_name = parts[1] | ||
| task = parts[2] if len(parts) > 2 else "" | ||
| if not task: | ||
| self.io.print_warning("Please provide a task description after --agent <name>.") | ||
| return | ||
|
Comment on lines
+346
to
+355
|
||
|
|
||
| try: | ||
| from qgo.agents.orchestrator import AgentOrchestrator | ||
|
|
||
| files = { | ||
| str(fc.path): fc.content | ||
| for fc in self.coder.chat_files | ||
| } | ||
| orchestrator = AgentOrchestrator( | ||
| llm=self.coder.llm, | ||
| config=self.coder.config, | ||
| io=self.io, | ||
| ) | ||
| if agent_name: | ||
| result = orchestrator.run_single(agent_name, task, context={"files": files}) | ||
| report = result.output if result.success else f"Agent error: {result.error}" | ||
| else: | ||
| report = orchestrator.run(task, files=files) | ||
|
|
||
| self.io.print_assistant(report) | ||
| # Add the report to conversation context | ||
| self.coder.messages.append({ | ||
| "role": "assistant", | ||
| "content": report, | ||
| }) | ||
|
Comment on lines
+375
to
+380
|
||
| except ValueError as exc: | ||
| self.io.print_error(str(exc)) | ||
| except Exception as exc: | ||
| self.io.print_error(f"Multi-agent pipeline error: {exc}") | ||
|
|
||
| def _cmd_config(self, args: str) -> None: | ||
| self.io.print_info(str(self.coder.config)) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -24,6 +24,11 @@ def fetch_url(url: str, timeout: int = 15) -> str: | |||||||||
| Cleaned text content suitable for LLM context. | ||||||||||
| Empty string if fetching fails. | ||||||||||
| """ | ||||||||||
| try: | ||||||||||
| _validate_url(url) | ||||||||||
| except ValueError as exc: | ||||||||||
| return f"[{exc}]" | ||||||||||
|
Comment on lines
+29
to
+30
|
||||||||||
| except ValueError as exc: | |
| return f"[{exc}]" | |
| except ValueError: | |
| return "" |
Copilot
AI
Apr 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_validate_url() only restricts the URL scheme. This prevents file:// etc., but it does not prevent SSRF to internal services over http(s) (e.g. http://localhost, http://169.254.169.254). If the goal is SSRF mitigation rather than just scheme hardening, consider additionally rejecting loopback/private/link-local IPs and optionally enforcing an allowlist of hosts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_cmd_agentadvertisesplanneras an available--agentoption, butAgentOrchestrator.run_single()only supports names inorchestrator._agents(coder/reviewer/tester/debugger/doc_writer/security/refactor) and will raiseValueErrorforplanner. Either removeplannerfrom the CLI/help list, or updaterun_single()/ orchestrator internals to allow running the planner directly.