Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
5280cc0
docs: implementation plan for 2026-05-09 security audit
muchiny May 9, 2026
cbfd7a5
fix(security): validate firewall protocol against allowlist
muchiny May 9, 2026
07b2a7f
fix(security): allowlist systemd unit_type in list_command
muchiny May 9, 2026
70d28d4
fix(security): validate env var names in file_template builder
muchiny May 9, 2026
d314906
fix(security): RFC 4515-escape values in LDAP filters
muchiny May 9, 2026
2da5d55
fix(security): randomize heredoc terminator in template_apply
muchiny May 9, 2026
380764f
fix(security): sanitize commands before audit log write
muchiny May 9, 2026
fb4aa13
fix(security): canonicalize paths in validate_root_scope (Vuln 11)
muchiny May 9, 2026
72e5e23
fix(security): HTTP transport defaults to loopback; refuse anonymous …
muchiny May 9, 2026
1b6301f
fix(security): verify JWT signatures via jsonwebtoken (Vuln 2)
muchiny May 9, 2026
c60d863
fix(security): unguessable UUID-based PendingRequests ids (Vuln 8 par…
muchiny May 9, 2026
6c047f3
fix(security): per-session PendingRequests (Vuln 8 part 2)
muchiny May 9, 2026
da0bbad
fix(security): per-session SessionCapabilities (Vuln 9)
muchiny May 9, 2026
868d3b7
fix(security): normalize ${IFS}/$'\t'/line-continuation before blackl…
muchiny May 9, 2026
305b7dd
style: rustfmt cleanup across security audit branch
muchiny May 9, 2026
e9fe191
audit(2026-05-09): scaffold workspace and install plugin set
muchiny May 9, 2026
62c2d18
audit(2026-05-09): capture pre-audit baseline snapshots
muchiny May 9, 2026
d4389a9
audit(2026-05-09): capture dependency-health baseline
muchiny May 9, 2026
6188390
audit(2026-05-09): pull upstream security guidance via context7 MCP
muchiny May 9, 2026
ad6fecd
audit(2026-05-09): re-run cargo geiger + udeps with real output
muchiny May 9, 2026
db642b0
audit(2026-05-09): cache audit-context-building summary
muchiny May 9, 2026
841ee76
audit(2026-05-09): map 358 MCP tool entry points with risk scores
muchiny May 9, 2026
d5e5cf8
audit(2026-05-09): inventory security and credential touchpoints
muchiny May 9, 2026
b85ca1b
audit(2026-05-09): static-analysis on security/ssh/mcp modules
muchiny May 9, 2026
859e95d
docs(audit-2026-05-09): seed findings tracker with Tasks 1-8 results
muchiny May 9, 2026
21a8112
audit(2026-05-09): insecure-defaults review on config + ssh
muchiny May 9, 2026
be683a5
audit(2026-05-09): supply-chain risk audit on Cargo deps
muchiny May 9, 2026
2023aa0
audit(2026-05-09): zeroize audit on cred-bearing modules
muchiny May 9, 2026
9e4ed5f
audit(2026-05-09): semgrep rules for top finding patterns
muchiny May 9, 2026
0cc36c3
audit(2026-05-09): variant analysis on Vuln 8/9 cross-session pattern
muchiny May 9, 2026
9194b63
audit(2026-05-09): proptest cases for validator/sanitizer/runbook
muchiny May 9, 2026
978a27e
audit(2026-05-09): mutation testing on src/security/ (skipped per user)
muchiny May 9, 2026
2d213fb
fix(security): enforce saphyr Budget on all YAML parse sites (FIND-00…
muchiny May 9, 2026
79c6d29
docs(audit): mark FIND-001/002/003/004/032 as fixed (2d213fb)
muchiny May 9, 2026
7027de1
refactor(domain): move yaml helper to domain layer (hexagonal complia…
muchiny May 9, 2026
9747fa6
fix(security): add timeout/body-limit/request-id/sensitive-headers mi…
muchiny May 9, 2026
91240e9
docs(audit): mark FIND-005 as fixed (9747fa6)
muchiny May 9, 2026
d031988
fix(security): per-session active_requests map (FIND-038)
muchiny May 9, 2026
e92ff26
docs(audit): mark FIND-038 as fixed (d031988)
muchiny May 9, 2026
0864649
fix(security): load OAuth keys at boot, share Arc<OAuthValidator> (FI…
muchiny May 9, 2026
90da422
docs(audit): mark FIND-006 as fixed (0864649)
muchiny May 9, 2026
ebabff0
fix(security): require sub/iss/aud spec claims on JWT (FIND-007)
muchiny May 9, 2026
6c3db66
docs(audit): mark FIND-007 as fixed (ebabff0)
muchiny May 9, 2026
d8507dd
fix(security): pin russh Preferred algos + rekey limits (FIND-008)
muchiny May 9, 2026
72547dd
docs(audit): mark FIND-008 fixed (d8507dd)
muchiny May 9, 2026
ba8ce36
fix(security)!: flip require_elicitation_on_destructive default to tr…
muchiny May 9, 2026
c2e4966
docs(audit-2026-05-09): record FIND-022 fix sha (ba8ce36)
muchiny May 9, 2026
d025e90
fix(security): Zeroizing<String> on HostConfig.sudo_password (FIND-028)
muchiny May 9, 2026
d75c740
audit(2026-05-09): pin FIND-028 status to commit d025e90
muchiny May 9, 2026
e76aeea
fix(security): Zeroizing<String> on db_password Args (FIND-029)
muchiny May 9, 2026
d4623d3
audit(2026-05-09): pin FIND-029 status to commit e76aeea
muchiny May 9, 2026
d42161f
fix(security): per-session runtime/notification/resource_subs/roots (…
muchiny May 9, 2026
05b069d
audit(2026-05-09): pin FIND-033/034/036/037 status to commit d42161f
muchiny May 9, 2026
d8e591f
audit(2026-05-09): supply-chain monitoring for saphyr + tokio-socks (…
muchiny May 9, 2026
a243d8b
fix(reliability): replace expect with ?/Err on Result-returning fns (…
muchiny May 9, 2026
e45dac5
audit(2026-05-09): pin FIND-010/011/012/013 status to commit a243d8b
muchiny May 9, 2026
11ddeb9
fix(security): Zeroizing<String> on SocksProxyConfig.password (FIND-014)
muchiny May 9, 2026
95eb765
audit(2026-05-09): pin FIND-014 status to commit 11ddeb9
muchiny May 9, 2026
213f57c
fix(security): sanitize SSH errors at all connect-phase sites (FIND-016)
muchiny May 9, 2026
d626b56
audit(2026-05-09): pin FIND-016 status to commit 213f57c
muchiny May 9, 2026
f16b7ab
fix(security): deny_unknown_fields on every config + runbook struct (…
muchiny May 9, 2026
c38d56a
audit(2026-05-09): pin FIND-017 status to commit f16b7ab
muchiny May 9, 2026
a455798
fix(security): SshConfigDiscovery default off (FIND-023)
muchiny May 9, 2026
5df172e
audit(2026-05-09): pin FIND-023 status to commit a455798
muchiny May 9, 2026
601bf01
fix(security)!: default-disable tool groups, ship 8-group minimal pro…
muchiny May 9, 2026
a98fb8d
audit(2026-05-09): pin FIND-024 status to commit 601bf01
muchiny May 9, 2026
e60200c
fix(security): drop archived shellexpand, use dirs::home_dir (FIND-025)
muchiny May 9, 2026
b7bf4eb
audit(2026-05-09): pin FIND-025 status to commit e60200c
muchiny May 9, 2026
0bbbde4
audit(2026-05-09): flip FIND-027 to monitoring (tokio-socks dep-watch)
muchiny May 9, 2026
3216e07
fix(security): Zeroizing<String> on vault Args.data (FIND-030)
muchiny May 9, 2026
5c1cf5c
audit(2026-05-09): pin FIND-030 status to commit 3216e07
muchiny May 9, 2026
9d81e12
fix(security): pipe vault/db secrets via stdin/tempfile (FIND-031)
muchiny May 9, 2026
959b1bc
audit(2026-05-09): pin FIND-031 status to commit 9d81e12
muchiny May 9, 2026
50942bb
fix(audit): clippy clean across workspace --all-targets --all-feature…
muchiny May 9, 2026
1e83f3e
audit(2026-05-09): pin FIND-019/020 status to commit 50942bb
muchiny May 9, 2026
35b0df7
fix(deps): patch winrm-rs to drop obsolete reqwest feature (FIND-018)
muchiny May 9, 2026
4e0e8d5
audit(2026-05-09): pin FIND-018 status to commit 35b0df7
muchiny May 9, 2026
81206bd
fix(ci): cargo-geiger fallback + baseline (FIND-021)
muchiny May 9, 2026
d5b4152
audit(2026-05-09): pin FIND-021 status to commit 81206bd
muchiny May 9, 2026
f35bc94
fix(security): per-session log_level (FIND-035)
muchiny May 9, 2026
d27ebf8
audit(2026-05-09): pin FIND-035 status to commit f35bc94
muchiny May 9, 2026
d1193c6
test(per_session_log_level): use Arc::ptr_eq instead of raw casts (cl…
muchiny May 9, 2026
7a489ef
test: align integration tests with FIND-023/024 default flips
muchiny May 9, 2026
ad28c1e
test(tool_filtering): drop unused HashMap import (clippy)
muchiny May 9, 2026
7f81959
chore(audit): remove 2026-05-09 audit workspace post-merge
muchiny May 9, 2026
15799f3
chore(release): v1.17.0 — security audit 2026-05-09
muchiny May 9, 2026
3d41001
fix(ci): patch winrm-rs via git ref instead of local path (FIND-018)
muchiny May 9, 2026
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ flamegraph.svg
id_rsa
id_rsa.pub
id_ed25519

# Allow OAuth test-only RSA fixtures (synthetic, never used outside tests)
!tests/fixtures/oauth/*.pem
id_ed25519.pub
id_ecdsa
id_ecdsa.pub
Expand Down
2 changes: 1 addition & 1 deletion .well-known/mcp/server-card.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mcp-ssh-bridge",
"version": "1.16.0",
"version": "1.17.0",
"description": "Secure SSH bridge for AI-powered remote server management. 357 tools across 75 groups covering Linux, Windows, Docker, Kubernetes, and more.",
"capabilities": {
"tools": true,
Expand Down
110 changes: 110 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,116 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [1.17.0] - 2026-05-10

### Summary

**Full security audit (2026-05-09) — 30+ findings remediated across YAML
parsing, JWT validation, transport defaults, session isolation, secret
zeroization, and tool-group exposure.** Two BREAKING default flips
(FIND-022 elicitation + FIND-024 tool_groups) plus per-session isolation
hardening (FIND-033/034/035/036/037/038), Zeroizing on every cred-bearing
struct (FIND-014/028/029/030), saphyr Budget on every YAML parse site
(FIND-001/002/004/032), OAuth/JWT spec compliance (FIND-006/007),
russh algo pinning + rekey limits (FIND-008), HTTP middleware hardening
(FIND-005), SSH error sanitization (FIND-016), `deny_unknown_fields`
on every config struct (FIND-017), and supply-chain cleanup
(FIND-018/025/026/027). Test suite: ~6,973 → 7,200+ tests (new
integration suites: `cross_session_cancel`, `multisession_isolation`,
`per_session_state`, `per_session_log_level`, `oauth_keys_loaded`,
`saphyr_budget`, `deny_unknown_fields`, `destructive_default`,
`http_middleware`, `ssh_config_discovery_default`, `ssh_preferred_algos`,
`sudo_password_zeroizing`, `security_audit_redaction`).

### Changed (BREAKING)

- **Security: `tool_groups` default-disabled, ships an 8-group minimal
profile** (FIND-024). The pre-FIND-024 behaviour was "unlisted =
enabled": `tool_groups: { groups: {} }` registered all 75 groups /
357 handlers out of the box. An operator who only needed `docker` +
`service` was also exposed to AD/LDAP/Vault/K8s/AWS/ESXi/HyperV /
Windows-only handlers. Unlisted groups now resolve via membership in
the new `MINIMAL_DEFAULT_GROUPS` const: `[core, file_ops, directory,
process, monitoring, network, systemd, sessions]` — everything else
requires explicit opt-in via `tool_groups.groups: { groupname: true }`.
Operators who relied on the old all-enabled behaviour must enumerate
the groups they need (or set every group they want to `true`).
Migration documented in `config/config.example.yaml`.

- **Security: `security.require_elicitation_on_destructive` now defaults to
`true`** (was `false`). Destructive tools annotated `destructive_hint: true`
(e.g., `ssh_process_kill`, `ssh_file_write`, `ssh_k8s_delete`,
`ssh_terraform_apply`, …) now require MCP `elicitation/create`
confirmation by default. Operators who relied on the old permissive
behaviour must explicitly set
`security.require_elicitation_on_destructive: false` in their config
(NOT RECOMMENDED in production — a compromised MCP client could
otherwise mass-execute destructive tools without surfacing to a human).

- **Security: `SshConfigDiscovery` default off** (FIND-023). `~/.ssh/config`
is no longer parsed unless the operator explicitly opts in. Eliminates
the implicit on-disk attack surface for hosts not declared in
`config.yaml`.

### Security

- **YAML parser: enforce saphyr `Budget` on every parse site**
(FIND-001/002/004/032). Caps node count + alias depth + recursion to
prevent resource-exhaustion via crafted runbooks/configs.
- **HTTP transport: defaults to loopback; refuses anonymous public bind.**
Public bind requires explicit auth + origin allowlist.
- **HTTP middleware: timeout + body-limit + request-id + sensitive-header
redaction** (FIND-005).
- **JWT: signature verification via `jsonwebtoken` (Vuln 2)** + require
`sub`/`iss`/`aud` spec claims (FIND-007).
- **OAuth: load keys at boot, share `Arc<OAuthValidator>`** (FIND-006).
- **russh: pin `Preferred` algorithms + rekey limits** (FIND-008).
- **SSH errors: sanitize at every connect-phase site** (FIND-016) —
no more leaking key paths/host details on auth failure.
- **Path traversal: canonicalize in `validate_root_scope` (Vuln 11).**
- **Audit log: sanitize commands before write** — no plaintext secrets.
- **Heredoc: randomize terminator in `template_apply`** — defeats
attacker-supplied terminator injection.
- **Env vars: validate names in `file_template` builder.**
- **LDAP: RFC 4515-escape values in filters.**
- **systemd: allowlist `unit_type` in `list_command`.**
- **Firewall: allowlist protocol values.**
- **Blacklist matcher: normalize `${IFS}` / `$'\t'` / line-continuation
before match** (defeats common bypass tricks).
- **Per-session isolation:**
- `PendingRequests` (Vuln 8 part 2) + UUID-based unguessable IDs
(Vuln 8 part 1)
- `SessionCapabilities` (Vuln 9)
- `active_requests` map (FIND-038)
- `runtime` / `notification` / `resource_subs` / `roots` (FIND-033/034/036/037)
- `log_level` (FIND-035)
- **Secret zeroization:**
- `HostConfig.sudo_password` (FIND-028)
- `db_password` Args (FIND-029)
- vault `Args.data` (FIND-030)
- `SocksProxyConfig.password` (FIND-014)
- vault/db secrets piped via stdin/tempfile (FIND-031)
- **`deny_unknown_fields` on every config + runbook struct** (FIND-017) —
unknown keys are now hard errors, not silent ignores.

### Changed

- **Reliability: replace `expect`/`unwrap` with `?`/`Err` on Result-returning
functions** (FIND-010/011/012/013).
- **Clippy clean across `--workspace --all-targets --all-features`**
(FIND-019/020).
- **CI: `cargo-geiger` fallback + baseline** (FIND-021).
- **Domain layer: move YAML helper into domain/** (hexagonal compliance).

### Removed / Deps

- **Drop archived `shellexpand`, use `dirs::home_dir`** (FIND-025).
- **Patch `winrm-rs` to drop obsolete `reqwest` feature** (FIND-018).
- **Supply-chain monitoring entries for `saphyr` + `tokio-socks`**
(FIND-026/027).

## [1.16.0] - 2026-05-03

### Summary
Expand Down
49 changes: 38 additions & 11 deletions Cargo.lock

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

16 changes: 13 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = [".", "crates/mcp-ssh-bridge-macros"]

[package]
name = "mcp-ssh-bridge"
version = "1.16.1"
version = "1.17.0"
edition = "2024"
rust-version = "1.94"
description = "MCP server that bridges Claude Code to air-gapped environments via SSH"
Expand Down Expand Up @@ -93,7 +93,6 @@ regex = "1"
aho-corasick = "1"
chrono = { version = "0.4", default-features = false, features = ["clock", "serde", "std"] }
dirs = "6"
shellexpand = "3"
tokio-socks = "0.5"
async-trait = "0.1"
sha2 = "0.10"
Expand All @@ -110,7 +109,7 @@ rayon = "1"
# HTTP transport (optional, for Streamable HTTP + OAuth)
axum = { version = "0.8", features = ["json"], optional = true }
tower = { version = "0.5", optional = true }
tower-http = { version = "0.6", features = ["cors", "limit"], optional = true }
tower-http = { version = "0.6", features = ["cors", "limit", "timeout", "request-id", "sensitive-headers"], optional = true }
hyper-util = { version = "0.1", features = ["tokio"], optional = true }
tokio-stream = { version = "0.1", optional = true }

Expand Down Expand Up @@ -149,6 +148,7 @@ tracing-opentelemetry = { version = "0.32", optional = true }
similar = "2.6"
inventory = "0.3"
mcp-ssh-bridge-macros = { version = "0.1.0", path = "crates/mcp-ssh-bridge-macros" }
jsonwebtoken = "9"

[dev-dependencies]
tempfile = "3"
Expand All @@ -157,6 +157,7 @@ filetime = "0.2"
tracing-test = "0.2"
proptest = "1"
insta = { version = "1", features = ["json", "yaml"] }
base64 = "0.22.1"

[[bench]]
name = "validator_bench"
Expand Down Expand Up @@ -224,3 +225,12 @@ missing_errors_doc = "allow"
missing_panics_doc = "allow"
module_name_repetitions = "allow"
must_use_candidate = "allow"

# =============================================================================
# Patch overrides (FIND-018 / audit 2026-05-09)
# =============================================================================
# winrm-rs 1.0 on crates.io declares an obsolete reqwest feature
# (`webpki-roots`) that blocks `cargo outdated` resolution. Local fork
# carries the fix; pin to it until winrm-rs > 1.0 is published.
[patch.crates-io]
winrm-rs = { git = "https://github.com/muchiny/winrm-rs.git", rev = "573dadf5abcaed681f65999f216164c9f33a6250" }
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,15 @@ security-audit: audit deny security-tests geiger

# Scan for unsafe code in dependencies (requires cargo-geiger)
geiger:
@command -v cargo-geiger >/dev/null 2>&1 && cargo geiger --all-features --output-format ascii || echo "cargo-geiger not installed, run: cargo install cargo-geiger --locked"
@command -v cargo-geiger >/dev/null 2>&1 || { echo "cargo-geiger not installed, run: cargo install cargo-geiger --locked"; exit 0; }
@# FIND-021 (audit 2026-05-09): cloud features (aws-sdk, azure, gcp)
@# pull nkeys-0.4.5 which cargo-geiger fails to extract on a cold
@# graph. Pre-fetch first; if extraction still fails on --all-features,
@# fall back to --forbid-only (acceptable since the workspace already
@# enforces `#![forbid(unsafe_code)]`).
@cargo fetch >/dev/null 2>&1 || true
@cargo geiger --all-features --output-format Ascii 2>/dev/null \
|| cargo geiger --forbid-only --output-format Ascii

# Check for semver-breaking API changes (requires cargo-semver-checks)
semver-checks:
Expand Down
Loading
Loading