Skip to content

Commit a4e464d

Browse files
feat: harden MCP server - confirm gates, command guard, dry-run, public scrub (v0.14.0)
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent e6afa72 commit a4e464d

35 files changed

Lines changed: 399 additions & 108 deletions

.cursor-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "home-lab-developer-tools",
33
"displayName": "Home Lab Developer Tools",
4-
"version": "0.13.2",
4+
"version": "0.14.0",
55
"description": "Home lab and Raspberry Pi workflows for Cursor, Claude Code, and MCP-compatible editors - 22 skills, 11 rules, and 50 MCP tools for managing Docker Compose stacks, monitoring, DNS, reverse proxy, networking, backups, disaster recovery, security auditing, logs, notifications, OS management, certificates, multi-node, diagnostics, and system administration on a Raspberry Pi home lab via SSH.",
66
"author": {
77
"name": "TMHSDigital",

.env.example

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Raspberry Pi SSH connection
2-
HOMELAB_PI_HOST=raspi5.local
3-
HOMELAB_PI_USER=tmhs
4-
HOMELAB_PI_KEY_PATH=~/.ssh/id_ed25519_pi
2+
HOMELAB_PI_HOST=raspberrypi.local
3+
HOMELAB_PI_USER=pi
4+
HOMELAB_PI_KEY_PATH=~/.ssh/id_ed25519
55

66
# Compose project directory on the Pi
77
HOMELAB_COMPOSE_DIR=/opt/homelab/docker
@@ -35,7 +35,11 @@ HOMELAB_BACKUP_REPO=/mnt/backup/restic
3535
# HOMELAB_NTFY_PORT=8080
3636

3737
# Multi-node support (JSON object mapping node names to SSH config)
38-
# HOMELAB_NODES='{"nas":{"host":"nas.local","user":"admin","keyPath":"~/.ssh/id_ed25519_nas"},"pi-zero":{"host":"pizero.local","user":"pi","keyPath":"~/.ssh/id_ed25519_pz"}}'
38+
# HOMELAB_NODES='{"nas":{"host":"nas.local","user":"admin","keyPath":"~/.ssh/id_ed25519"},"pi-zero":{"host":"pizero.local","user":"pi","keyPath":"~/.ssh/id_ed25519"}}'
3939

4040
# Ansible inventory path for node discovery (default /etc/ansible/hosts)
4141
# HOMELAB_ANSIBLE_INVENTORY=/etc/ansible/hosts
42+
43+
# Safety overrides
44+
# HOMELAB_DRY_RUN=true # When set, execSSH prints what it would run without connecting
45+
# HOMELAB_ALLOW_DANGEROUS_COMMANDS=true # Bypasses the runtime command guard (use with caution)

.github/workflows/links.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,6 @@ jobs:
2828
--exclude "localhost"
2929
--exclude "127.0.0.1"
3030
--exclude "example.com"
31-
--exclude "raspi5.local"
31+
--exclude "raspberrypi.local"
3232
"**/*.md"
3333
"**/*.mdc"

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.14.0] - 2026-05-22
9+
10+
### Added
11+
12+
- Runtime command safety guard in `execSSH` -- blocks catastrophic patterns (`rm -rf /`, `mkfs` on block devices, `dd` to block devices, fork bomb, `chmod -R 777 /`, curl/wget pipe to shell, `poweroff`/`halt`/`shutdown -h`) before opening an SSH connection. Override with `HOMELAB_ALLOW_DANGEROUS_COMMANDS=true`.
13+
- `HOMELAB_DRY_RUN` mode -- when set, `execSSH` returns a description string without connecting. Unsafe commands are still rejected in dry-run mode.
14+
- `UnsafeCommandError` class in `utils/errors.ts` for runtime guard rejections.
15+
- Shared `confirmParam` and `confirmCancelled` helpers in `utils/confirm-param.ts` consumed by all confirm-gated tools.
16+
- `mcp-server/src/utils/__tests__/ssh-guard.test.ts` -- unit tests for the command guard and dry-run mode using the real module (no mock).
17+
18+
### Changed
19+
20+
- `homelab_serviceRestart`, `homelab_composeUp`, and `homelab_composeDown` now require `confirm=true` to proceed. Calls without it return a cancellation message and make no changes.
21+
- Default SSH host changed from personal hostname to generic `raspberrypi.local`; default user changed from personal username to `pi`. Override via `HOMELAB_PI_HOST` and `HOMELAB_PI_USER`.
22+
- All confirm-gated tools now use the shared `confirmParam`/`confirmCancelled` helpers for consistent messaging (standardizes "aborted" -> "cancelled" in `certRenew` and `nodeExec`).
23+
- `composePull` remains ungated (pull only downloads images, does not change running state).
24+
825
## [0.13.2] - 2026-04-26
926

1027
See [release notes](https://github.com/TMHSDigital/Home-Lab-Developer-Tools/releases/tag/v0.13.2) for details.

CLAUDE.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Home Lab Developer Tools integrates home lab and Raspberry Pi workflows into AI-
1212

1313
This is a monorepo -- the skills, rules, and companion MCP server live in the same repository. The MCP server connects to a Raspberry Pi via SSH to execute commands.
1414

15-
**Version:** 0.13.2
15+
**Version:** 0.14.0
1616
**License:** CC-BY-NC-ND-4.0
1717
**npm:** @tmhs/homelab-mcp
1818
**Author:** TMHSDigital
@@ -179,6 +179,13 @@ npm install -g @tmhs/homelab-mcp
179179
- Skills use kebab-case directory names with YAML frontmatter (`name`, `description`, `tools`).
180180
- Rules use kebab-case filenames with `description` and `alwaysApply` frontmatter.
181181

182+
## Security
183+
184+
- Tools that change running state require `confirm=true`: `homelab_piReboot`, `homelab_backupRun`, `homelab_backupRestore`, `homelab_volumeBackup`, `homelab_certRenew`, `homelab_nodeExec`, `homelab_serviceRestart`, `homelab_composeUp`, `homelab_composeDown`. Use the shared `confirmParam` and `confirmCancelled` helpers from `utils/confirm-param.ts`.
185+
- `execSSH()` runs `assertCommandSafe()` before opening any SSH connection. This blocks catastrophic patterns (`rm -rf /`, `mkfs` on block devices, fork bombs, `poweroff`, etc.). Override with `HOMELAB_ALLOW_DANGEROUS_COMMANDS=true`.
186+
- `HOMELAB_DRY_RUN=true` makes `execSSH` return a description string instead of connecting. The command guard still runs in dry-run mode.
187+
- No personal SSH usernames, hostnames, or key names in code or docs. Use `pi` / `raspberrypi.local` / `~/.ssh/id_ed25519` as generic defaults.
188+
182189
## Release Hygiene
183190

184191
When preparing a new release version, ALL of the following files must be updated to reflect the new version number, tool/skill/rule counts, and any new entries:

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,9 @@ Add to your Cursor MCP config (`.cursor/mcp.json`):
146146
"args": ["./mcp-server/dist/index.js"],
147147
"cwd": "<path-to>/Home-Lab-Developer-Tools",
148148
"env": {
149-
"HOMELAB_PI_HOST": "raspi5.local",
150-
"HOMELAB_PI_USER": "tmhs",
151-
"HOMELAB_PI_KEY_PATH": "~/.ssh/id_ed25519_pi"
149+
"HOMELAB_PI_HOST": "raspberrypi.local",
150+
"HOMELAB_PI_USER": "pi",
151+
"HOMELAB_PI_KEY_PATH": "~/.ssh/id_ed25519"
152152
}
153153
}
154154
}

ROADMAP.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
| v0.10.0 | Testing Infrastructure | +2 | -- | -- | 50 |
1717
| v0.11.0 | Documentation Site | -- | -- | -- | 50 |
1818
| v0.12.0 | Polish and Hardening | +2 | -- | -- | 52 |
19+
| v0.14.0 | Security Hardening | -- | -- | -- | 50 |
1920
| **v1.0.0** | **Stable Release** | -- | -- | -- | **52** |
2021

2122
---
@@ -215,6 +216,18 @@ Real test coverage before v1.0.
215216

216217
---
217218

219+
## v0.14.0 - Security Hardening
220+
221+
- [x] Runtime command safety guard in `execSSH` blocking catastrophic patterns
222+
- [x] `HOMELAB_DRY_RUN` env var for testing without SSH connections
223+
- [x] Confirm gate extended to `homelab_serviceRestart`, `homelab_composeUp`, `homelab_composeDown`
224+
- [x] Shared `confirmParam`/`confirmCancelled` helpers used by all gated tools
225+
- [x] Personal SSH defaults replaced with generic `raspberrypi.local` / `pi` placeholders
226+
- [x] `UnsafeCommandError` error class for guard rejections
227+
- [x] Unit tests for command guard and dry-run mode
228+
229+
---
230+
218231
## v0.11.0 - Documentation Site
219232

220233
Upgrade GitHub Pages from placeholder to full tool reference.

SECURITY.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,25 @@ This project connects to remote hosts via SSH. Security considerations:
1515
- SSH private keys must never be committed to the repository
1616
- The `.env` file containing connection details is gitignored
1717
- MCP tools execute commands on remote hosts -- review tool actions before running
18-
- Tools with destructive actions require explicit `confirm=true` parameters: `homelab_piReboot`, `homelab_backupRun`, `homelab_backupRestore`, `homelab_volumeBackup`, `homelab_certRenew`, `homelab_nodeExec`
18+
- Tools with destructive or state-changing actions require explicit `confirm=true` parameters: `homelab_piReboot`, `homelab_backupRun`, `homelab_backupRestore`, `homelab_volumeBackup`, `homelab_certRenew`, `homelab_nodeExec`, `homelab_serviceRestart`, `homelab_composeUp`, `homelab_composeDown`
19+
20+
## Runtime Command Guard
21+
22+
`execSSH` blocks a focused set of catastrophic command patterns before opening an SSH connection:
23+
24+
- `rm -rf /` (and `--no-preserve-root` variant)
25+
- `mkfs` targeting a block device
26+
- `dd` writing to a block device
27+
- Fork bomb (`:(){ :|:& };:`)
28+
- `chmod -R 777 /`
29+
- Piping a remote download to a shell (`curl ... | sh`, `wget ... | bash`)
30+
- `shutdown -h`, `halt`, `poweroff` (note: `shutdown -r` used by `homelab_piReboot` is explicitly allowed)
31+
32+
To bypass the guard (not recommended): set `HOMELAB_ALLOW_DANGEROUS_COMMANDS=true`.
33+
34+
## Dry-Run Mode
35+
36+
Set `HOMELAB_DRY_RUN=true` to make `execSSH` print what it would execute without opening an SSH connection. Unsafe commands are still rejected in dry-run mode.
1937

2038
## Best Practices
2139

docs/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1479,11 +1479,11 @@ <h2>Related Tools</h2>
14791479
</div>
14801480
<div class="footer-divider"></div>
14811481
<div class="footer-meta">
1482-
<span>v0.11.0</span>
1482+
<span>v0.14.0</span>
14831483
<span>&middot;</span>
14841484
<span>CC-BY-NC-ND-4.0</span>
14851485
<span>&middot;</span>
1486-
<span>Built 2026-04-08</span>
1486+
<span>Built 2026-05-22</span>
14871487
</div>
14881488
<div class="footer-directory">
14891489
Part of <a href="https://tmhsdigital.github.io/Developer-Tools-Directory/">TMHSDigital Developer Tools</a>

mcp-server/README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ Set environment variables:
7171

7272
| Variable | Default | Description |
7373
|----------|---------|-------------|
74-
| `HOMELAB_PI_HOST` | `raspi5.local` | Pi hostname or IP |
75-
| `HOMELAB_PI_USER` | `tmhs` | SSH username |
76-
| `HOMELAB_PI_KEY_PATH` | (empty) | Path to SSH private key |
74+
| `HOMELAB_PI_HOST` | `raspberrypi.local` | Pi hostname or IP |
75+
| `HOMELAB_PI_USER` | `pi` | SSH username |
76+
| `HOMELAB_PI_KEY_PATH` | (empty) | Path to SSH private key (e.g. `~/.ssh/id_ed25519`) |
7777
| `HOMELAB_COMPOSE_DIR` | `/opt/homelab/docker` | Compose project directory on Pi |
7878
| `HOMELAB_BACKUP_REPO` | `/mnt/backup/restic` | Restic backup repo path on Pi |
7979
| `HOMELAB_GRAFANA_TOKEN` | (empty) | Grafana API token (preferred auth method) |
@@ -93,6 +93,8 @@ Set environment variables:
9393
| `HOMELAB_NTFY_PORT` | `8080` | Ntfy server port override |
9494
| `HOMELAB_NODES` | (empty) | JSON object mapping node names to SSH config for multi-node support |
9595
| `HOMELAB_ANSIBLE_INVENTORY` | `/etc/ansible/hosts` | Ansible inventory file path for node discovery |
96+
| `HOMELAB_DRY_RUN` | (unset) | When set to any truthy value, `execSSH` prints the command without connecting |
97+
| `HOMELAB_ALLOW_DANGEROUS_COMMANDS` | (unset) | Set to `true` to bypass the runtime command safety guard |
9698

9799
## Usage with Cursor
98100

@@ -105,9 +107,9 @@ Add to `.cursor/mcp.json`:
105107
"command": "node",
106108
"args": ["path/to/Home-Lab-Developer-Tools/mcp-server/dist/index.js"],
107109
"env": {
108-
"HOMELAB_PI_HOST": "raspi5.local",
109-
"HOMELAB_PI_USER": "tmhs",
110-
"HOMELAB_PI_KEY_PATH": "~/.ssh/id_ed25519_pi"
110+
"HOMELAB_PI_HOST": "raspberrypi.local",
111+
"HOMELAB_PI_USER": "pi",
112+
"HOMELAB_PI_KEY_PATH": "~/.ssh/id_ed25519"
111113
}
112114
}
113115
}

0 commit comments

Comments
 (0)