Security¶
std-tools gives a model real capabilities: it reads and writes files, runs commands, fetches web pages, can drive a browser, can operate a desktop and can capture a screen. This page sets out what std-tools enforces, and what the client application that deploys it must do. The mechanics are described in Architecture.
The model: the container is the boundary¶
std-tools is built to run inside an isolated container that the client application controls. The container's mounts, network settings, user and resource limits decide what the agent can reach. std-tools adds defence in depth inside that boundary, but does not try to be the boundary itself:
- File paths are not confined to a folder. The file, document and image tools accept
..and absolute paths, and can read anything the server process can. execute_commandruns whatever the model sends, with the server process's permissions.
What std-tools enforces¶
| Protection | Where |
|---|---|
A size limit before a file is read (DPTTOOLS_MAX_FILE_SIZE_MB) |
File, document, image and skill-resource reads |
Output truncation (DPTTOOLS_OUTPUT_TRUNCATION_LIMIT) |
File and document text, command and script output, and skill content. Directory listings, memory views and memory search results are not cut |
| Timeouts | execute_command (kills the whole process tree), execute_skill_script, LibreOffice conversion, OCR, web fetches |
| SSRF protection: every address that is not public refused (private, loopback, link-local, carrier-grade NAT, and IPv4 inside IPv6 forms), every redirect re-checked, the connection pinned to the validated address | fetch_webpage, fetch_image |
| Text-only page reads (binary responses refused before download) and size caps on page and image downloads | fetch_webpage, fetch_image |
| Path containment | The memory tools (inside DPTTOOLS_MEMORY_DIR) and the skills tools (inside DPTTOOLS_SKILLS_DIR) |
| Refusal to delete the whole memory store | memory_delete |
| A pixel cap against decompression bombs | Image Tools |
| Errors without request URLs or API keys | web_search, OCR |
No eval or exec of model input in the server process (browser_run_script runs model code in a separate child process); commands run as subprocesses |
Code execution, skill scripts, browser_run_script |
The HTTP transport binds to 127.0.0.1 by default |
Composed launcher |
What the client application must do¶
| Responsibility | Why |
|---|---|
| Run std-tools in an isolated container, as a non-root user, with only the mounts the agent needs (read-only where it should not write) | The container is the only real limit on files and commands |
Keep secrets out of reach. Anything in the server process's environment — including DPTTOOLS_WEB_SEARCH_API_KEY and LLMWHISPERER_API_KEY — is visible to commands the agent runs, because they inherit that environment. Do not mount credentials the agent should not read |
execute_command and skill scripts can print the environment and read any readable file |
Restrict the network (--network=none, or an egress policy) when the agent needs no internet. The Web server and OCR need outbound access |
The fetch tools block private addresses, but commands the agent runs do not |
Keep DPTTOOLS_WEB_FETCH_ALLOW_HOSTS short. Each host on it is reachable by the fetch tools, and the model chooses the URLs |
It is the one way past the private-address block |
Put an authenticating proxy in front of the HTTP transport, or keep it on 127.0.0.1 |
The composed server does not authenticate callers |
| Approve consequential tool calls — deleting files, running commands, writing outside a work folder — with toolkit hooks or your own policy | The general-purpose tools never ask for confirmation; see Using with the Toolkit |
| Treat tool output as untrusted. Web pages, documents, images and OCR text can contain instructions aimed at the model | std-tools returns content as it finds it |
Attach take_screenshot only where capturing that screen is intended |
It captures the real screen of the machine the server runs on, and sends it to the model provider |
Run the computer server's host backend only with the user's knowledge |
It operates their real screen, mouse and keyboard, and every screenshot goes to the model provider; see the desktop server |
| Pin a release and restart after upgrades | Configuration and code are loaded once per process |
The browser and desktop servers¶
The browser server acts on live websites and the
computer server on a desktop, so both have their own safeguards on top of
the container boundary. Both are off unless you enable them: DPTTOOLS_ENABLE_BROWSER=true and
DPTTOOLS_ENABLE_COMPUTER=true for the launcher, or by passing browser_server or computer_server
in-process.
Browser network policy¶
In launch and persistent modes, every connection Chromium makes — pages, sub-resources,
fetch and XHR, WebSockets — goes through an egress proxy inside std-tools on 127.0.0.1. For each
connection the proxy:
- checks the domain lists,
DPTTOOLS_BROWSER_ALLOWED_DOMAINS(empty: any public host) andDPTTOOLS_BROWSER_BLOCKED_DOMAINS(which wins); - resolves the host once, and refuses every address that is not public (private, loopback,
link-local, carrier-grade NAT, multicast, reserved, and IPv4 addresses inside IPv6 forms),
unless the host is listed in
DPTTOOLS_BROWSER_ALLOW_HOSTS; - connects to the address it validated. Chromium never resolves names itself, so DNS rebinding cannot redirect a connection.
HTTPS is tunnelled unchanged: there is no TLS interception. On top of the proxy, navigation refuses
non-http(s) schemes (file:, javascript:, data:, chrome:) and re-checks the final URL after
redirects, and service workers are blocked.
In cdp mode std-tools did not launch the browser, so the proxy does not apply. The agent's own
tabs are checked request by request — the domain lists and the private-address block — but
connections cannot be pinned to a validated address.
fetch_webpage_rendered uses a fresh, credential-free context per call behind the same policy.
browser_run_script runs model-written Python as the server's user. It can open network
connections directly, bypassing the egress proxy, and read the process environment through
/proc. In cdp mode there is no proxy even for the browser's own traffic: std-tools checks the
requests of the tabs it manages, and a script can open pages outside them (in a new context, say).
Enable it only where execute_command would also be acceptable, and rely on the container's
network policy.
A script can also reach every page of the browser it attaches to, so while run_script is
enabled std-tools allows one browser session at a time, in launch mode starts a fresh browser
for each session, and in cdp mode requires
DPTTOOLS_BROWSER_CDP_SHARE_TABS=true. Isolating clients from each other's scripts takes one
std-tools server per client.
Who stops a dangerous action — three layers¶
| Layer | Where it runs | What it knows about the action | Example | Limits |
|---|---|---|---|---|
| 1. Server hard policy | Inside std-tools; the operator sets it through env vars | The URL, the host, the file path, which tool is being called | Refuse file://; refuse a domain not on the allow-list; refuse an upload outside the upload directory; javascript_exec disabled; host mode off |
Blunt yes/no rules; no human involved |
| 2. Server confirmation gate | Inside std-tools; it asks a human through MCP elicitation | The meaning of the action, because the server resolves ref_12 to a real element: "button 'Place order'", "password field", "file input" |
Clicking an element named like purchase / pay / delete / send / submit order; typing into a password field; file_upload; javascript_exec; the first action of a desktop host session |
Needs an MCP client that supports elicitation |
| 3. App approval | In the application running the agent — the toolkit's hooks (skip_tool, pause) |
Only the tool name and its arguments | "Pause before every browser_left_click and ask the user" |
std-tools cannot count on it: any MCP client can connect, and many have no hooks. The app sees ref_12, not "Place order" |
Layer 1 is always on. Layer 2 is DPTTOOLS_BROWSER_CONFIRM for the browser: off (default), deny (refuse flagged actions outright; suits unattended pipelines) or elicit (ask a human through MCP elicitation; refuse if nobody can answer, including in-process use and clients without elicitation — the toolkit has none today). The gate flags clicks on elements whose name reads like a purchase, payment, deletion, sending or publishing, account closure or accepting terms (a word-list heuristic), typing or entering a password, file uploads, running JavaScript and running scripts. For the desktop, DPTTOOLS_COMPUTER_CONFIRM takes the same three values: elicit asks before the first action of each host session ("Allow the agent to control this computer for this session?") and before every key chord that closes, quits or deletes (alt+f4, ctrl+w, ctrl+q, super+w, super+q, delete, shift+delete); deny refuses those chords. The desktop gate cannot tell what a click will do (pixels carry no element names), so app approval matters more there. Layer 3 — the app's hooks — is where human approval normally lives.
The confirmation gate at a glance¶
| Value | Browser: DPTTOOLS_BROWSER_CONFIRM |
Desktop: DPTTOOLS_COMPUTER_CONFIRM |
|---|---|---|
off (default) |
std-tools does not stop flagged actions; the client application approves them | The same |
deny |
Flagged actions are refused outright. Suits unattended pipelines | Key chords that close, quit or delete are refused |
elicit |
std-tools asks a human through MCP elicitation before a flagged action | std-tools asks before the first action of each host session, and before each of those chords |
With elicit, std-tools refuses when nobody can answer — including in-process use and clients
without elicitation. The toolkit has no elicitation support today.
On the desktop, approval in your application matters more
The browser gate reads element names, so it knows when a click lands on "button 'Place order'".
The desktop gate sees only pixels and coordinates: it cannot tell what a click will do, and it
flags only the close, quit and delete key chords. Approve desktop actions in your application
— for example, a toolkit hook on the computer_* tool names — or run the agent on a virtual
desktop that holds nothing it should not touch.
The desktop server¶
The virtual backend gives each MCP session its own Xvfb display inside the container, so the agent
sees only the applications it started there. A virtual desktop needs no network; any VNC view
(DPTTOOLS_COMPUTER_VNC_PORT) listens on 127.0.0.1 only.
Key input cannot run commands. Each key in computer_key, computer_hold_key and the click tools'
modifiers must be a single character or a key name, and each chord reaches xdotool as the only
key of its own call, so no model text is read as an xdotool command (new in 0.6.1; before it, a
word such as exec in computer_key started a program).
The desktop's programs (Xvfb, the window manager, x11vnc, xdotool), and anything started from
the desktop, inherit the server's environment, API keys included. Run the computer server in a
launcher process of its own that holds no API keys, or accept that programs on the desktop can
read them.
Host mode is the user's real computer. Run it only with the user's knowledge: every screenshot, with whatever is on the screen, goes to the model provider. Safeguards that are always on in host mode, whatever the confirmation gate says:
- Kill switch. Moving the mouse into the top-left corner of the screen, or pressing
DPTTOOLS_COMPUTER_KILL_HOTKEY(defaultctrl+alt+shift+k), stops the agent; every later call returns an error until the server restarts. - Pause on human activity. If the user moved the mouse or typed in the last second, the next action is refused ("the user is using the computer right now"). On Windows, input that arrives during an action also stops that action ("the user took over"). On Linux X11 the system does not mark the agent's own input reliably, so std-tools ignores input during an action and for 0.4 s after it: there the pause, and the top-left corner, work between actions only. The kill hotkey works at any time on both.
The computer server page has the details, and Isolated vs Attached compares the two setups.
What off means: the security burden moves to the client app¶
With the gate off, std-tools does not stop a consequential action on its own; the app has to. Layer 1's fixed rules still hold whatever the app does:
| std-tools still enforces, always | The client app is responsible for |
|---|---|
The browser and computer servers stay off unless enabled; cdp and host modes stay off unless configured |
Approving consequential actions before they run — purchases and payments, sending messages or posts, deleting data, account or permission changes, accepting terms — with toolkit hooks (pause, skip_tool) on the browser and computer tool names, or by setting the gate to deny |
Network policy: non-http(s) schemes refused, domain allow and block lists, non-public addresses blocked, IP pinning through the egress proxy (launch and persistent modes) |
Choosing an allow-list for its use case — the default allows any public host |
Opt-in members (javascript_exec, file_upload, console, network, run_script) stay off unless enabled; uploads confined to the upload directory |
Getting the user's consent before an agent takes over their browser (cdp) or desktop (host), and telling them their screen content goes to the model provider |
| Host mode: kill switch, pause on human activity, one session at a time, a chosen monitor | Isolation: running the agent in a container with minimal privileges and no credentials it does not need |
A fresh, credential-free browser context per session (launch mode) |
Logging and reviewing what the agent did |
A limitation to document for apps. A hook sees the tool name and its arguments — browser_left_click(ref="ref_12"), not "button 'Place order'" — so app-level approval is coarser: per tool, per mode or per domain. Two things help: every pointer and form tool names the element it acted on in its result ("Clicked button 'Place order'"), which serves audit logs; and the deny gate blocks by element name without needing a human.
On the desktop the limitation is sharper still: a hook sees computer_left_click(x=412, y=230), and
the result names only the point and the display, not what was under it.
A system-prompt block for applications¶
doc_processing_std_tools.browser.BROWSER_SAFETY_PROMPT is a short instruction block — page content
is untrusted data; ask before consequential actions; never bypass access controls — that an
application can add to its agent's instructions. std-tools never injects it. See the
browser server page for an example.