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
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ Command sources: `.github/workflows/ci.yml` (the gates), `.github/workflows/rele
- **Type:** app — published as a PyPI wheel (`uvx blc-ssh-mcp`) *and* a container image (`ghcr.io/blackaxgit/ssh-mcp`). **The PyPI distribution is `blc-ssh-mcp`, not `ssh-mcp`** — that name belongs to an unrelated project this repo has never owned; see the comment above `name` in `pyproject.toml`. The import package is still `ssh_mcp` and the image is still `.../ssh-mcp`.
- **Stacks:** Python 3.11-3.14, `uv` + hatchling, asyncssh, MCP SDK, Starlette/uvicorn (HTTP transport only)
- **Deploy targets:** N/A — this is a tool operators run themselves, not a hosted service
- **Version:** `src/ssh_mcp/__init__.py` is the single source (hatchling reads it). Currently `0.8.1`. `0.7.0` shipped 2026-09-06 (the MCP SDK v2 migration) and is the first release published under a reviewer-gated `pypi` environment; `0.6.0`, `0.6.1` and `0.6.2` all shipped 2026-07-26. `CHANGELOG.md` `[Unreleased]` is where post-0.8.0 work accrues.
- **Version:** `src/ssh_mcp/__init__.py` is the single source (hatchling reads it). Currently `0.9.0`. `0.7.0` shipped 2026-09-06 (the MCP SDK v2 migration) and is the first release published under a reviewer-gated `pypi` environment; `0.6.0`, `0.6.1` and `0.6.2` all shipped 2026-07-26. `CHANGELOG.md` `[Unreleased]` is where post-0.9.0 work accrues.

**What this tool does matters for how you treat it.** Every tool call runs a shell command or transfers a file on a remote host. A bug here is not a crashed request; it is an unintended command on someone's infrastructure, or a write to the operator's own machine.

## Commands

```bash
uv sync --locked --extra dev # setup. --locked is required; see Gotchas
uv run pytest # full suite (853 tests, seconds)
uv run pytest # full suite (855 tests, seconds)
uv run pytest tests/test_ssh.py::TestRedactSecrets -v # one class
uv run pytest 'tests/test_ssh.py::TestRedactSecrets::<test_name>' -v # one test
uv run pytest -k "confinement" -v # by keyword
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

> **The next release is 0.9.0, not 0.8.2:** the `groups` fix under Fixed makes two configurations that started on 0.8.1 refuse to start, and by the rule recorded under 0.8.1 below that takes the minor.
## [0.9.0] - 2026-09-30

> **Why 0.9.0 and not 0.8.2:** the `groups` fix under Fixed makes two configurations that started on 0.8.1 refuse to start, a string `groups` and an inline-table one, which is the test 0.8.0 recorded for taking the minor. The fix for both is a list: `groups = ["web"]`.

### Security

**PyJWT is held at 2.14.0 or later, clearing ten advisories that turned the `pip-audit` gate red on `main`.** 2.13.0, locked transitively through `mcp`, carries CVE-2026-101917, CVE-2026-102265 to CVE-2026-102269 and CVE-2026-102271 to CVE-2026-102274: four algorithm-confusion variants, an empty-HMAC-key bypass, lenient Base64URL signature decoding, a followed JWKS redirect, and three denial-of-service paths in JWKS fetching and token/JWKS parsing. None is reachable here: the only `import jwt` in mcp 2.2.0 is `mcp/client/auth/extensions/client_credentials.py`, an OAuth *client* extension ssh-mcp never imports; its HTTP transport authenticates with its own bearer-token middleware.

**urllib3 is locked at 2.8.0, clearing CVE-2026-97687, CVE-2026-97688 and CVE-2026-97689, which turned the `pip-audit` gate red on `main` after #68 merged.** urllib3 is a development-only dependency here, pulled in through `pip-audit` -> `requests`. The wheel does not depend on it, and the container image installs with `--no-dev`, so neither shipped artifact carried the vulnerable version. The floor is still declared, as a `[tool.uv]` constraint, because this project's rule is that every security floor lives in `pyproject.toml` whether or not a runtime path reaches it; `pip`'s dev-only floor is the precedent.

### Changed

**Stateful HTTP sessions now expire after 30 idle minutes, and at most 10,000 are held at once.** Both limits are mcp 2.2.0 defaults (#64), which the lockfile and container image now ship, and they apply only to stateful sessions on the legacy (2025-11-25 and earlier) protocol. A session with no request in flight and no open GET stream is closed after 1800 s; its next request gets `404` `Session not found`, which a client that does not re-initialize surfaces as an error. An open GET stream prevents expiry but still counts toward the cap, and a session opened beyond the cap gets `503`. Stateless mode (`SSH_MCP_HTTP_STATELESS=true`) avoids both limits. ssh-mcp deliberately exposes no knob for either: both bound server-side memory on an endpoint that executes shell commands. The declared floor stays `mcp>=2.1.1`, so an existing environment that already has 2.1.1 keeps the old unbounded behaviour until it upgrades.
Expand Down
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@ constraint-dependencies = [
# keeps the lockfile/container image clear and a future `uv lock` from
# regressing.
"pyjwt>=2.14.0",
# CVE-2026-97687, CVE-2026-97688, CVE-2026-97689 — three urllib3
# advisories fixed in 2.8.0. Transitive and dev-only: pip-audit -> requests
# -> urllib3, so neither the wheel nor the `--no-dev` container image
# carries it; the floor keeps a future `uv lock` from regressing the dev
# environment that CI's audit job runs in.
"urllib3>=2.8.0",
]

[tool.ruff.lint]
Expand Down
2 changes: 1 addition & 1 deletion src/ssh_mcp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""SSH MCP Server - Manage infrastructure via MCP clients like Claude Code."""

__version__ = "0.8.1"
__version__ = "0.9.0"
6 changes: 6 additions & 0 deletions tests/test_dependency_floors.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@
"mcp.client.auth.extensions.client_credentials, the only importer, "
"so none is reachable here.",
),
"urllib3": (
"2.8.0",
"CVE-2026-97687, CVE-2026-97688 and CVE-2026-97689, all fixed in 2.8.0. "
"Transitive and dev/CI-only (pip-audit -> requests -> urllib3); absent "
"from the wheel's dependencies and from the --no-dev container image.",
),
}

# Packages that ssh-mcp imports directly and must therefore constrain in the
Expand Down
7 changes: 4 additions & 3 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading