Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/advanced/bots/copilot-bot.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ When multiple auth methods are configured simultaneously, CopilotBot resolves th
| `wire_api` | `str` | `None` | API format: `"completions"` or `"responses"`. |
| `azure_api_version` | `str` | `None` | Azure API version (for `provider_type="azure"` only). |
| `token_provider` | `Callable[[], str]` | `None` | Callable returning a bearer token string. |
| `sudo_access` | `bool` | `False` | Give the agent passwordless `sudo` inside the sandbox. See [Sudo access](../sandbox-images.md#sudo-access). |

### `run()` method

Expand Down
20 changes: 20 additions & 0 deletions docs/advanced/sandbox-images.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,23 @@ env = LocalDockerEnvironment(port=8080, image="mcr.microsoft.com/devcontainers/b
## Tools and distros

You never declare the sandbox's distribution. Tools read it from the running container's `/etc/os-release` and pick their [distro-specific commands](tools/tesseract_ocr_tool_use.md#distro-specific-commands) from it. A tool that only defines `debian` commands, such as `cscope` or `browser-use`, fails to install on the Azure Linux image with `UnsupportedDistroError`.

## Sudo access

By default the sandbox shell drops to an unprivileged `agent` account that has your host user's uid, and the container blocks privilege escalation (`no-new-privileges`). Some tools, such as `mock`, need root for part of their work. Pass `sudo_access=True` to any bot or to `LocalDockerEnvironment` to give the agent passwordless `sudo`:

```python
bot = WritingBot(
model="azure-openai/your-deployment",
folder_to_mount="/path/to/repo",
image=AZURELINUX_IMAGE,
sudo_access=True,
)
```

- The agent's commands still run as your uid unless it explicitly uses `sudo`, so files it writes in a `READ_WRITE` mount stay owned by you. Files it writes through `sudo` are owned by root and are left that way.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if the agent opens its own bash session using sudo bash and run the comamnds?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is true. When the Agent is given sudo access, all sorts of privileged things are possible. So, we included multiple warnings in this flow. The documentation assumes that the agent will be co-operative.

If the model becomes rogue, we don't have any real guardrail against it. This is applicable for all the privileged agents. Thus, the developer should be cautious before enabling this and the system where this agent runs shouldn't be critical for production operations.

- `sudo_access` applies to the environment the bot creates. If you pass your own `environment`, create it with `LocalDockerEnvironment(..., sudo_access=True)`.
- The container is started without `no-new-privileges`, so `sudo` and setuid helpers (such as `mock`'s launcher) work. If `sudo` doesn't work in the image, the environment fails to start with a `RuntimeError`.

!!! warning
Sudo access turns the sandbox into a guardrail rather than an isolation boundary. Through `sudo` the agent has root with the container's `SYS_ADMIN` capability and AppArmor unconfined, so it can mount filesystems and reach host-wide kernel interfaces (for example `core_pattern` or a cgroup `release_agent`). A misbehaving or prompt-injected agent could therefore remount a `READ_ONLY` mount as writable, or gain root on the host. A `READ_ONLY` mount still works for a cooperative agent, and its changes are discarded with the sandbox. Enable sudo access only for trusted tasks and trusted repositories, skills, and instructions.
24 changes: 23 additions & 1 deletion src/microbots/MicroBot.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ class MicroBot:
image : Optional[str]
Shell-server image of the auto-created environment, for example
``images.AZURELINUX_IMAGE``. Defaults to None (``DEFAULT_IMAGE``).
sudo_access : bool
Give the sandbox's agent account passwordless ``sudo`` (needed by
tools such as ``mock``). Applies to the auto-created environment;
a provided ``environment`` must already have sudo access.
Defaults to False.
"""

def __init__(
Expand All @@ -170,6 +175,7 @@ def __init__(
folder_to_mount: Optional[Mount] = None,
token_provider: Optional[any] = None,
image: Optional[str] = None,
sudo_access: bool = False,
):
"""
Init function for MicroBot class.
Expand Down Expand Up @@ -205,13 +211,28 @@ def __init__(
Shell-server image of the auto-created environment, for example
``images.AZURELINUX_IMAGE``. Defaults to None (``DEFAULT_IMAGE``).
Cannot be combined with ``environment``.
sudo_access : bool
Give the sandbox's ``agent`` account (the host user's uid)
passwordless ``sudo``. Needed by tools such as ``mock``.
Applies to the auto-created environment; a provided
``environment`` must already have been created with
``sudo_access=True``. Defaults to False.

Raises
------
ValueError
If both ``image`` and ``environment`` are provided.
If both ``image`` and ``environment`` are provided, or if
``sudo_access`` is requested with an ``environment`` that does
not grant sudo access.
"""

if sudo_access and environment is not None and not getattr(environment, "sudo_access", False):
raise ValueError(
"sudo_access=True requires the provided environment to be created "
"with LocalDockerEnvironment(..., sudo_access=True)."
)
self.sudo_access = sudo_access

if image is not None and environment is not None:
raise ValueError(
"Pass either `image` or `environment`, not both: the image of a "
Expand Down Expand Up @@ -510,6 +531,7 @@ def _create_environment(self, folder_to_mount: Optional[Mount]):
port=free_port,
folder_to_mount=folder_to_mount,
image=self.image,
sudo_access=self.sudo_access,
)

def _create_llm(self):
Expand Down
8 changes: 8 additions & 0 deletions src/microbots/bot/AgentBoss.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class AgentBoss(MicroBot):
image : Optional[str]
Shell-server image of the auto-created environment, for example
``images.AZURELINUX_IMAGE``. Defaults to None (Debian image).
sudo_access : bool
Give the sandbox's agent account passwordless ``sudo`` (needed by
tools such as ``mock``). Defaults to False.

Workflow
--------
Expand All @@ -49,6 +52,7 @@ def __init__(
environment: Optional[any] = None,
additional_tools: Optional[list[ToolAbstract]] = None,
image: Optional[str] = None,
sudo_access: bool = False,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might give sudo access all the sub-agents that AgentBoss creates

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is expected. We don't have fine-grained control over the sub-agents as of now. We'll address this later along with improving overall AgentBoss experience.

):
"""
Create the bot with the ``microbot_sub`` tool and the repository mounted read-write.
Expand All @@ -67,6 +71,9 @@ def __init__(
image : Optional[str]
Shell-server image of the auto-created environment, for example
``images.AZURELINUX_IMAGE``. Defaults to None (Debian image).
sudo_access : bool
Give the sandbox's agent account passwordless ``sudo`` (needed by
tools such as ``mock``). Defaults to False.
"""
if additional_tools is None:
additional_tools = []
Expand Down Expand Up @@ -123,6 +130,7 @@ def __init__(
additional_tools=additional_tools,
folder_to_mount=folder_mount_info,
image=image,
sudo_access=sudo_access,
)

def run(
Expand Down
5 changes: 5 additions & 0 deletions src/microbots/bot/BrowsingBot.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ class BrowsingBot(MicroBot):
image : Optional[str]
Shell-server image of the auto-created environment. The browser tool
only supports Debian-based images (the default). Defaults to None.
sudo_access : bool
Give the sandbox's agent account passwordless ``sudo``.
Defaults to False.
"""

def __init__(
Expand All @@ -38,6 +41,7 @@ def __init__(
additional_tools: Optional[list[ToolAbstract]] = None,
token_provider: Optional[any] = None,
image: Optional[str] = None,
sudo_access: bool = False,
):
# validate init values before assigning
bot_type = BotType.BROWSING_BOT
Expand All @@ -53,6 +57,7 @@ def __init__(
additional_tools=(additional_tools or []) + [BROWSER_USE_TOOL],
token_provider=token_provider,
image=image,
sudo_access=sudo_access,
)

def run(self, task, timeout_in_seconds=200) -> BotRunResult:
Expand Down
22 changes: 22 additions & 0 deletions src/microbots/bot/CopilotBot.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,15 @@ class CopilotBot:
token_provider : Optional[Callable[[], str]]
A callable returning a bearer token (e.g. Azure AD token
provider). Requires ``base_url``.
sudo_access : bool
Give the agent passwordless ``sudo`` inside the sandbox. Needed by
tools such as ``mock``. Commands still run as the host user unless
the agent uses ``sudo``. Defaults to *False*. The sandbox then only
acts as a guardrail: a misbehaving agent could remount ``READ_ONLY``
mounts writable or escape to the host, so use it only with trusted
tasks and repositories.
Applies to the auto-created environment; a passed-in ``environment``
must already have been created with ``sudo_access=True``.
"""

def __init__(
Expand All @@ -316,6 +325,7 @@ def __init__(
wire_api: Optional[str] = None,
azure_api_version: Optional[str] = None,
token_provider: Optional[Callable[[], str]] = None,
sudo_access: bool = False,
):
try:
from copilot import CopilotClient, ExternalServerConfig
Expand All @@ -342,6 +352,17 @@ def __init__(
"e.g. pip install -U microbots[ghcp]."
) from exc

if (
environment is not None
and sudo_access
and not getattr(environment, "sudo_access", False)
):
raise ValueError(
"sudo_access=True requires the provided environment to be created "
"with LocalDockerEnvironment(..., sudo_access=True)."
)
self.sudo_access = sudo_access

self.additional_tools = additional_tools or []

# ── Resolve auth: BYOK vs native GitHub Copilot ─────────────
Expand Down Expand Up @@ -515,6 +536,7 @@ def _create_environment(self):
self.environment = LocalDockerEnvironment(
port=free_port,
folder_to_mount=self.folder_to_mount,
sudo_access=self.sudo_access,
)

def _install_copilot_cli(self):
Expand Down
8 changes: 8 additions & 0 deletions src/microbots/bot/LogAnalysisBot.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ class LogAnalysisBot(MicroBot):
image : Optional[str]
Shell-server image of the auto-created environment, for example
``images.AZURELINUX_IMAGE``. Defaults to None (Debian image).
sudo_access : bool
Give the sandbox's agent account passwordless ``sudo`` (needed by
tools such as ``mock``). Defaults to False.

Attributes
----------
Expand Down Expand Up @@ -80,6 +83,7 @@ def __init__(
additional_tools: Optional[list[ToolAbstract]] = None,
token_provider: Optional[any] = None,
image: Optional[str] = None,
sudo_access: bool = False,
):
"""
Initialize a LogAnalysisBot instance.
Expand All @@ -104,6 +108,9 @@ def __init__(
image : Optional[str]
Shell-server image of the auto-created environment, for example
``images.AZURELINUX_IMAGE``. Defaults to None (Debian image).
sudo_access : bool
Give the sandbox's agent account passwordless ``sudo`` (needed by
tools such as ``mock``). Defaults to False.
"""
# validate init values before assigning
bot_type = BotType.LOG_ANALYSIS_BOT
Expand Down Expand Up @@ -132,6 +139,7 @@ def __init__(
folder_to_mount=folder_mount_info,
token_provider=token_provider,
image=image,
sudo_access=sudo_access,
)

def run(
Expand Down
8 changes: 8 additions & 0 deletions src/microbots/bot/ReadingBot.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class ReadingBot(MicroBot):
image : Optional[str]
Shell-server image of the auto-created environment, for example
``images.AZURELINUX_IMAGE``. Defaults to None (Debian image).
sudo_access : bool
Give the sandbox's agent account passwordless ``sudo`` (needed by
tools such as ``mock``). Defaults to False.
"""

def __init__(
Expand All @@ -41,6 +44,7 @@ def __init__(
additional_tools: Optional[list[ToolAbstract]] = None,
token_provider: Optional[any] = None,
image: Optional[str] = None,
sudo_access: bool = False,
):
"""
Create the bot with the repository mounted read-only.
Expand All @@ -62,6 +66,9 @@ def __init__(
image : Optional[str]
Shell-server image of the auto-created environment, for example
``images.AZURELINUX_IMAGE``. Defaults to None (Debian image).
sudo_access : bool
Give the sandbox's agent account passwordless ``sudo`` (needed by
tools such as ``mock``). Defaults to False.
"""
# validate init values before assigning
bot_type = BotType.READING_BOT
Expand Down Expand Up @@ -90,4 +97,5 @@ def __init__(
folder_to_mount=folder_mount_info,
token_provider=token_provider,
image=image,
sudo_access=sudo_access,
)
8 changes: 8 additions & 0 deletions src/microbots/bot/WritingBot.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class WritingBot(MicroBot):
image : Optional[str]
Shell-server image of the auto-created environment, for example
``images.AZURELINUX_IMAGE``. Defaults to None (Debian image).
sudo_access : bool
Give the sandbox's agent account passwordless ``sudo`` (needed by
tools such as ``mock``). Defaults to False.
"""

def __init__(
Expand All @@ -41,6 +44,7 @@ def __init__(
additional_tools: Optional[list[ToolAbstract]] = None,
token_provider: Optional[any] = None,
image: Optional[str] = None,
sudo_access: bool = False,
):
"""
Create the bot with the repository mounted read-write.
Expand All @@ -62,6 +66,9 @@ def __init__(
image : Optional[str]
Shell-server image of the auto-created environment, for example
``images.AZURELINUX_IMAGE``. Defaults to None (Debian image).
sudo_access : bool
Give the sandbox's agent account passwordless ``sudo`` (needed by
tools such as ``mock``). Defaults to False.
"""
# validate init values before assigning
bot_type = BotType.WRITING_BOT
Expand Down Expand Up @@ -95,4 +102,5 @@ def __init__(
folder_to_mount=folder_mount_info,
token_provider=token_provider,
image=image,
sudo_access=sudo_access,
)
Loading
Loading