aiterm-mcp is published from a single 0.x line on npm. Security fixes land on the latest released 0.x version only; there are no maintained back-port branches for older releases.
| Version | Supported |
|---|---|
Latest published 0.x |
Yes — fixes released here |
Any older 0.x |
No — upgrade to the latest |
If you are pinned to an older version, upgrade (npm i -g aiterm-mcp@latest, or just npx -y aiterm-mcp, which always fetches the latest) before reporting an issue, in case it is already fixed.
Please report security issues privately, not as a public GitHub issue or pull request.
Use GitHub's private vulnerability reporting on the repository:
- Go to https://github.com/kitepon/aiterm-mcp.
- Open the Security tab → Report a vulnerability (GitHub Security Advisories).
That opens a private advisory thread visible only to you and the maintainer. Please do not email; the maintainer responds through GitHub.
A useful report includes:
- the affected version (the npm version of
aiterm-mcpyou ran), - your OS and how aiterm is launched (Linux/WSL2, macOS, or native Windows),
- the multiplexer version (
tmux -Von POSIX orpsmux -Von native Windows), - a minimal sequence of tool calls (
pty_open/pty_send/pty_read…) that reproduces it, - the impact you observed.
aiterm-mcp is a solo, best-effort open-source project. There is no SLA. The maintainer aims to acknowledge a valid report and assess it as time allows, then fix and release on the latest 0.x line. Please allow a reasonable window for a fix before any public disclosure, and coordinate timing through the advisory thread. Credit in the advisory/release notes is offered on request.
Read this before deploying aiterm-mcp anywhere the consequences matter. The design goal is an honest, observable terminal — not a sandbox. The guards below are tripwires and input hygiene, not a security boundary.
aiterm-mcp grabs one real local terminal (a tmux pane running your shell) and lets the connected MCP client drive it with pty_send. Anything you could type into that shell, the AI can run — with your user, your environment, your filesystem, your network, your SSH keys and agent. Sending ssh host or docker exec -it … bash simply nests into that one PTY, so the AI's reach extends to wherever that shell can go.
Consequences for operators:
- Run aiterm-mcp under a user account whose privileges you are willing to expose to the connected client. Do not run it as root, and prefer a least-privilege account for sensitive environments.
- Only connect MCP clients / models you trust to drive a shell on that account.
- Sessions are backed by tmux on POSIX and psmux on native Windows, and persist across MCP-server and client restarts while that multiplexer server remains running. A session opened earlier is still live and drivable later. Use
pty_listto see them andpty_close(or the platform-specific kill-server command) to remove them. - Sessions live on a shared tmux socket or psmux namespace, so a human can
attachto the same pane (thepty_openreturn value prints the exact command). This is intended co-driving, but it also means anyone with access to that socket/namespace and host user can observe and drive the session.
When throughline_source_session is supplied, aiterm asks the locally installed
Throughline CLI for that session's read-only handoff context and prepends it to
the new mission before launching the selected harness CLI. Aiterm does not copy
or reassign the source database rows and does not add network transport, but the
returned memory becomes input to the launched agent and is therefore subject to
that product's normal processing and retention behavior. Omit the field when
that transfer is not intended.
Canonical agent_launch and its four compatibility aliases accept env_vars as an allowlist of environment-variable
names. At launch, aiterm reads present values from its current MCP process and
shell-quotes them into the one harness launch command. This deliberately bypasses
the potentially stale environment of a tmux server that was started earlier.
The values are not supplied in MCP tool arguments, but they do pass through the
PTY command and aiterm's per-session .lastcmd file, and the launched harness can
read them normally. Any process with access to the same OS account may also be
able to inspect that state. Use env_vars only for values such as seat identity
or workflow routing that the selected harness and host user are allowed to see.
Do not use it as a credential or secret-delivery mechanism. Missing names are
omitted, invalid shell variable names fail before session creation, and aiterm
does not copy the whole environment or mutate/restart the tmux server.
Before sending, pty_send matches the text against a small set of regexes for well-known catastrophic forms (e.g. rm -rf / and ~/$HOME/glob-root variants, mkfs, dd … of=/dev/…, redirects to raw block devices, DROP TABLE/DROP DATABASE/DROP SCHEMA/TRUNCATE TABLE, curl … | sh, the classic fork bomb, chmod -R 000 /, git reset --hard). A match is blocked and the caller must pass force: true to proceed.
This is a speed-bump against obvious accidents, not a safety boundary. It is a syntactic pattern match on the literal text you send, so it does not catch:
- Relative-path destruction — e.g.
rm -rf somedirfrom a directory that matters; the patterns target absolute/~/$HOME/glob roots (and a bare trailing./*), not arbitrary relative paths. - Danger that only appears after expansion — anything that becomes destructive once the shell expands a variable, glob, command substitution, or alias (
rm -rf "$DIR",rm -rf $EMPTY/, etc.). The gate sees the pre-expansion text, not the resolved command. - Commands on the far side of a nested session — once you've
ssh'd ordocker exec'd into another host/container, the destructive text runs there, and the gate (which inspects the bytes you send, not what the remote shell ultimately executes) does not meaningfully protect the remote. - Anything not on the list — it is a denylist of blocked forms, not an exhaustive classifier. Novel or obfuscated destructive commands pass through.
Do not rely on the gate as your only protection. Use OS-level isolation (a dedicated user, container, VM, restricted credentials, backups) for anything you actually care about. force: true bypasses the gate entirely, and raw: true bypasses input sanitization (see below).
By default pty_send strips bracketed-paste terminators (ESC[200~ / ESC[201~), ANSI/CSI/OSC escape sequences, and other control characters from the text before it reaches the pane (tab and newline are preserved). This reduces the chance that pasted/streamed content smuggles in terminal escape sequences or breaks out of a bracketed-paste region into unintended execution.
This sanitization is opt-out: raw: true sends the bytes verbatim, and force: true bypasses the destructive gate. Both exist deliberately for legitimate use; understand that using them removes the corresponding protection.
pty_read strips/normalizes control characters and ANSI escapes from the text it returns (carriage-return progress overwrites are collapsed to their final state, escape sequences removed). This keeps captured terminal output from injecting control sequences into the consuming client's display, in addition to reducing tokens. pty_read({ raw: true }) returns the unprocessed text and therefore does not neutralize it.
Session names are validated against ^[A-Za-z0-9_-]{1,64}$ at every entry point (pty_open, pty_send, pty_read, pty_key, pty_close). Names flow into filesystem paths (the per-session .log / .offset / .lastcmd files) and into the pipe-pane shell string, so the restriction blocks path traversal (../) and shell-injection metacharacters (quotes, $, ;, …). The log path written into pipe-pane's /bin/sh -c argument is additionally single-quoted with '\'' escaping, defending the tmux-internal path even against quote-bearing temp directories.
The multiplexer is started with an empty config (-f /dev/null on POSIX, -f NUL on native Windows), so it does not read the user's tmux/psmux config. This is for reproducibility across machines, but it also means local multiplexer hardening does not apply to aiterm's sessions. All aiterm sessions share one socket or namespace; the platform's kill-server command terminates all of them at once.
Native Windows uses psmux 3.3.8 or newer and Git for Windows directly; it does not bridge through WSL. The shell, session, psmux namespace, agent credentials, and human attach therefore belong to the Windows user and Windows filesystem. WSL2 remains a separate supported POSIX environment with its own tmux server and state; do not treat the two as one credential or session boundary.
aiterm-mcp does not attempt to sandbox, audit, or rate-limit what the connected client does with the terminal, and it does not authenticate clients — it speaks stdio to whatever MCP client launched it. Isolation and access control are the operator's responsibility (OS user, container/VM, credential scoping). Reports that amount to "the AI ran a command I gave it the ability to run" describe intended behavior, not a vulnerability. Genuine issues — e.g. a way to bypass session-name validation, escape the pipe-pane quoting, inject escape sequences past the pty_send/pty_read sanitizers, or otherwise make aiterm do something its documented model says it should not — are in scope; please report them as above.