ssh-agent-workspace 1.0.5 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +471 -471
- package/dist/__tests__/SSHManager.test.js +59 -37
- package/dist/__tests__/SSHManager.test.js.map +1 -1
- package/dist/__tests__/SessionManager.test.js +53 -51
- package/dist/__tests__/SessionManager.test.js.map +1 -1
- package/dist/__tests__/StorageManager.test.js +74 -74
- package/dist/__tests__/StorageManager.test.js.map +1 -1
- package/dist/__tests__/ansi.test.js +33 -33
- package/dist/__tests__/ansi.test.js.map +1 -1
- package/dist/__tests__/security.test.js +54 -54
- package/dist/__tests__/security.test.js.map +1 -1
- package/dist/__tests__/validation.test.js +17 -17
- package/dist/__tests__/validation.test.js.map +1 -1
- package/dist/core/HostSecurityManager.d.ts.map +1 -1
- package/dist/core/HostSecurityManager.js +9 -9
- package/dist/core/HostSecurityManager.js.map +1 -1
- package/dist/core/SSHManager.d.ts +1 -1
- package/dist/core/SSHManager.d.ts.map +1 -1
- package/dist/core/SSHManager.js +31 -31
- package/dist/core/SSHManager.js.map +1 -1
- package/dist/core/SessionManager.d.ts +4 -4
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +14 -14
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/StorageManager.d.ts.map +1 -1
- package/dist/core/StorageManager.js +11 -11
- package/dist/core/StorageManager.js.map +1 -1
- package/dist/core/TmuxManager.d.ts +3 -3
- package/dist/core/TmuxManager.d.ts.map +1 -1
- package/dist/core/TmuxManager.js +16 -16
- package/dist/core/TmuxManager.js.map +1 -1
- package/dist/core/ToolConfigManager.d.ts.map +1 -1
- package/dist/core/ToolConfigManager.js +10 -10
- package/dist/core/ToolConfigManager.js.map +1 -1
- package/dist/index.js +35 -35
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +4 -4
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +41 -41
- package/dist/server.js.map +1 -1
- package/dist/tools/backup.d.ts +3 -3
- package/dist/tools/backup.d.ts.map +1 -1
- package/dist/tools/backup.js +86 -49
- package/dist/tools/backup.js.map +1 -1
- package/dist/tools/connect.d.ts +4 -4
- package/dist/tools/connect.d.ts.map +1 -1
- package/dist/tools/connect.js +54 -42
- package/dist/tools/connect.js.map +1 -1
- package/dist/tools/connection_status.d.ts +4 -4
- package/dist/tools/connection_status.d.ts.map +1 -1
- package/dist/tools/connection_status.js +23 -16
- package/dist/tools/connection_status.js.map +1 -1
- package/dist/tools/db_query.d.ts +3 -3
- package/dist/tools/db_query.d.ts.map +1 -1
- package/dist/tools/db_query.js +130 -78
- package/dist/tools/db_query.js.map +1 -1
- package/dist/tools/deploy.d.ts +3 -3
- package/dist/tools/deploy.d.ts.map +1 -1
- package/dist/tools/deploy.js +103 -53
- package/dist/tools/deploy.js.map +1 -1
- package/dist/tools/disconnect.d.ts +3 -3
- package/dist/tools/disconnect.d.ts.map +1 -1
- package/dist/tools/disconnect.js +16 -19
- package/dist/tools/disconnect.js.map +1 -1
- package/dist/tools/exec.d.ts +3 -3
- package/dist/tools/exec.d.ts.map +1 -1
- package/dist/tools/exec.js +40 -45
- package/dist/tools/exec.js.map +1 -1
- package/dist/tools/group_exec.d.ts +3 -3
- package/dist/tools/group_exec.d.ts.map +1 -1
- package/dist/tools/group_exec.js +60 -44
- package/dist/tools/group_exec.js.map +1 -1
- package/dist/tools/health_check.d.ts +3 -3
- package/dist/tools/health_check.d.ts.map +1 -1
- package/dist/tools/health_check.js +27 -23
- package/dist/tools/health_check.js.map +1 -1
- package/dist/tools/host_security.d.ts +1 -1
- package/dist/tools/host_security.d.ts.map +1 -1
- package/dist/tools/host_security.js +30 -36
- package/dist/tools/host_security.js.map +1 -1
- package/dist/tools/index.d.ts +23 -23
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +23 -23
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/interrupt.d.ts +3 -3
- package/dist/tools/interrupt.d.ts.map +1 -1
- package/dist/tools/interrupt.js +18 -18
- package/dist/tools/interrupt.js.map +1 -1
- package/dist/tools/list_hosts.d.ts.map +1 -1
- package/dist/tools/list_hosts.js +5 -7
- package/dist/tools/list_hosts.js.map +1 -1
- package/dist/tools/list_sessions.d.ts +1 -1
- package/dist/tools/list_sessions.js +4 -4
- package/dist/tools/list_sessions.js.map +1 -1
- package/dist/tools/read_output.d.ts +3 -3
- package/dist/tools/read_output.d.ts.map +1 -1
- package/dist/tools/read_output.js +17 -17
- package/dist/tools/read_output.js.map +1 -1
- package/dist/tools/reconnect_to_tmux.d.ts +4 -4
- package/dist/tools/reconnect_to_tmux.d.ts.map +1 -1
- package/dist/tools/reconnect_to_tmux.js +44 -37
- package/dist/tools/reconnect_to_tmux.js.map +1 -1
- package/dist/tools/send_input.d.ts +3 -3
- package/dist/tools/send_input.d.ts.map +1 -1
- package/dist/tools/send_input.js +18 -18
- package/dist/tools/send_input.js.map +1 -1
- package/dist/tools/sftp_download.d.ts +3 -3
- package/dist/tools/sftp_download.d.ts.map +1 -1
- package/dist/tools/sftp_download.js +41 -27
- package/dist/tools/sftp_download.js.map +1 -1
- package/dist/tools/sftp_list.d.ts +3 -3
- package/dist/tools/sftp_list.d.ts.map +1 -1
- package/dist/tools/sftp_list.js +35 -26
- package/dist/tools/sftp_list.js.map +1 -1
- package/dist/tools/sftp_upload.d.ts +3 -3
- package/dist/tools/sftp_upload.d.ts.map +1 -1
- package/dist/tools/sftp_upload.js +43 -29
- package/dist/tools/sftp_upload.js.map +1 -1
- package/dist/tools/ssh_tunnel.d.ts +3 -3
- package/dist/tools/ssh_tunnel.d.ts.map +1 -1
- package/dist/tools/ssh_tunnel.js +130 -76
- package/dist/tools/ssh_tunnel.js.map +1 -1
- package/dist/tools/sync.d.ts +3 -3
- package/dist/tools/sync.d.ts.map +1 -1
- package/dist/tools/sync.js +103 -61
- package/dist/tools/sync.js.map +1 -1
- package/dist/tools/tail_log.d.ts +3 -3
- package/dist/tools/tail_log.d.ts.map +1 -1
- package/dist/tools/tail_log.js +38 -26
- package/dist/tools/tail_log.js.map +1 -1
- package/dist/tools/tools_config.d.ts +1 -1
- package/dist/tools/tools_config.d.ts.map +1 -1
- package/dist/tools/tools_config.js +25 -35
- package/dist/tools/tools_config.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/ansi.js +1 -1
- package/dist/utils/ansi.js.map +1 -1
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.js +2 -2
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/security.d.ts +1 -1
- package/dist/utils/security.js +3 -3
- package/dist/utils/security.js.map +1 -1
- package/dist/utils/ssh.d.ts +2 -2
- package/dist/utils/ssh.js +5 -5
- package/dist/utils/ssh.js.map +1 -1
- package/dist/utils/sshConfig.d.ts +1 -1
- package/dist/utils/sshConfig.d.ts.map +1 -1
- package/dist/utils/sshConfig.js +21 -21
- package/dist/utils/sshConfig.js.map +1 -1
- package/dist/utils/validation.d.ts +1 -1
- package/dist/utils/validation.js +3 -3
- package/dist/utils/validation.js.map +1 -1
- package/docs/SECURITY.md +213 -213
- package/docs/TOOLS.md +425 -425
- package/package.json +59 -48
- package/vitest.config.ts +10 -10
package/docs/SECURITY.md
CHANGED
|
@@ -1,213 +1,213 @@
|
|
|
1
|
-
# Security Model
|
|
2
|
-
|
|
3
|
-
`ssh-agent-workspace` implements a **three-layer defense** architecture. Each layer adds granularity without breaking the layers above it.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Layer 1: Global (Environment Variables)
|
|
8
|
-
|
|
9
|
-
Set at server startup via environment variables. Applies to **all hosts**.
|
|
10
|
-
|
|
11
|
-
| Variable | Effect |
|
|
12
|
-
|---|---|
|
|
13
|
-
| `MCP_SSH_READONLY=true` | Blocks all write operations globally |
|
|
14
|
-
| `MCP_SSH_ALLOWED_HOSTS=prod,staging` | Only these host aliases can be connected to |
|
|
15
|
-
| `MCP_SSH_DENYLIST_COMMANDS=rm,shutdown,dd` | Blocks commands containing these substrings (case-insensitive) |
|
|
16
|
-
|
|
17
|
-
### Read-Only Mode: Blocked Tools
|
|
18
|
-
|
|
19
|
-
When `MCP_SSH_READONLY=true`, the following tools return errors:
|
|
20
|
-
|
|
21
|
-
- `exec` — Command execution disabled
|
|
22
|
-
- `send_input` — Input sending disabled
|
|
23
|
-
- `sftp_upload` — SFTP upload disabled
|
|
24
|
-
- `sftp_download` — SFTP download disabled
|
|
25
|
-
- `deploy` — Deploy disabled
|
|
26
|
-
- `backup` — Backup disabled
|
|
27
|
-
- `sync` — Sync disabled
|
|
28
|
-
- `group_exec` — Group exec disabled
|
|
29
|
-
- `db_query` — DB query disabled
|
|
30
|
-
- `ssh_tunnel_open` — Tunnels disabled
|
|
31
|
-
|
|
32
|
-
### Tools Always Allowed (Read Operations)
|
|
33
|
-
|
|
34
|
-
`list_hosts`, `connect`, `reconnect_to_tmux`, `read_output`, `interrupt`, `disconnect`, `list_sessions`, `sftp_list`, `connection_status`, `health_check`, `tail_log`, `ssh_tunnel_close`, `ssh_tunnel_list`, `tools_config`, `host_security`
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## Layer 2: Per-Host (Host Security)
|
|
39
|
-
|
|
40
|
-
Managed via the `host_security` tool and persisted in `~/.dynamic-ssh-mcp/host_security.json`.
|
|
41
|
-
|
|
42
|
-
Per-host settings **override** global settings for that specific host only.
|
|
43
|
-
|
|
44
|
-
### Configuration
|
|
45
|
-
|
|
46
|
-
```json
|
|
47
|
-
{
|
|
48
|
-
"prod": {
|
|
49
|
-
"readonly": true,
|
|
50
|
-
"deny_commands": ["shutdown", "reboot", "dd if="]
|
|
51
|
-
},
|
|
52
|
-
"staging": {
|
|
53
|
-
"deny_commands": ["rm -rf /", "shutdown"]
|
|
54
|
-
},
|
|
55
|
-
"dev": {
|
|
56
|
-
"allow_commands": [
|
|
57
|
-
"ls", "cat", "echo", "ps aux",
|
|
58
|
-
"docker ps", "docker logs",
|
|
59
|
-
"git status", "git diff", "git log",
|
|
60
|
-
"npm run", "node"
|
|
61
|
-
]
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Rules
|
|
67
|
-
|
|
68
|
-
| Field | Behavior |
|
|
69
|
-
|---|---|
|
|
70
|
-
| `readonly: true` | Host locked to read-only, regardless of global `MCP_SSH_READONLY` |
|
|
71
|
-
| `readonly: false` | Host explicitly writeable (does **not** override global readonly) |
|
|
72
|
-
| `allow_commands: [...]` | If set, **only** commands matching these patterns are allowed (case-insensitive substring match) |
|
|
73
|
-
| `deny_commands: [...]` | Commands matching these patterns are blocked (case-insensitive substring match) |
|
|
74
|
-
|
|
75
|
-
**Precedence:** `deny_commands` wins over `allow_commands`. If a command matches both, it's blocked.
|
|
76
|
-
|
|
77
|
-
### Enforcement Points
|
|
78
|
-
|
|
79
|
-
Per-host read-only is checked **after session lookup** in every write tool:
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
exec, send_input, sftp_upload, sftp_download,
|
|
83
|
-
deploy, backup, sync, group_exec, db_query, ssh_tunnel_open
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
If a session's host has `readonly: true`, the tool returns:
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
Error: Host 'prod' is in read-only mode. <operation> is disabled.
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### Per-Host Command Filtering
|
|
93
|
-
|
|
94
|
-
`isHostCommandDenied(host, command)` checks:
|
|
95
|
-
1. Global `MCP_SSH_DENYLIST_COMMANDS`
|
|
96
|
-
2. Per-host `allow_commands` (if set, command must match at least one)
|
|
97
|
-
3. Per-host `deny_commands`
|
|
98
|
-
|
|
99
|
-
Per-host command filtering is available to all tools via the shared security utility.
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
## Layer 3: Per-Operation (Tool-Level)
|
|
104
|
-
|
|
105
|
-
### Path Sanitization
|
|
106
|
-
|
|
107
|
-
All file paths used in SFTP, backup, deploy, sync, and tail_log are validated:
|
|
108
|
-
|
|
109
|
-
- Rejects paths containing `;`, `&&`, `||`, `|`
|
|
110
|
-
- Rejects paths starting with `-` (option injection)
|
|
111
|
-
- Paths are shell-escaped before passing to exec commands
|
|
112
|
-
|
|
113
|
-
### Command Denylist (Global)
|
|
114
|
-
|
|
115
|
-
Commands containing these substrings are blocked (case-insensitive):
|
|
116
|
-
|
|
117
|
-
```
|
|
118
|
-
rm -rf, shutdown, reboot, dd, mkfs, fdisk, :(){ :|:& };: (fork bomb)
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
Configured via `MCP_SSH_DENYLIST_COMMANDS` — comma-separated:
|
|
122
|
-
```
|
|
123
|
-
MCP_SSH_DENYLIST_COMMANDS=rm -rf,shutdown,dd if=,mkfs,chmod 777
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### SQL/MongoDB Query Security
|
|
127
|
-
|
|
128
|
-
The `db_query` tool enforces read-only queries:
|
|
129
|
-
|
|
130
|
-
**SQL (MySQL/PostgreSQL):**
|
|
131
|
-
- Blocked keywords: `DROP`, `DELETE`, `INSERT`, `UPDATE`, `ALTER`, `CREATE`, `TRUNCATE`, `GRANT`, `REVOKE`, `RENAME`, `REPLACE`, `MERGE`, `UPSERT`, `LOAD`
|
|
132
|
-
- Only allowed prefixes: `SELECT`, `SHOW`, `EXPLAIN`, `DESCRIBE`, `WITH`
|
|
133
|
-
|
|
134
|
-
**MongoDB:**
|
|
135
|
-
- Blocked methods: `deleteOne`, `deleteMany`, `insertOne`, `insertMany`, `updateOne`, `updateMany`, `replaceOne`, `drop`, `dropDatabase`, `createIndex`, `createCollection`
|
|
136
|
-
|
|
137
|
-
### Shell Injection Protection
|
|
138
|
-
|
|
139
|
-
All arguments passed to tmux commands are:
|
|
140
|
-
1. Validated via Zod schemas before use
|
|
141
|
-
2. Passed through `sanitizeTmuxSessionName()` for session names
|
|
142
|
-
3. Base64-encoded buffer pipeline for command input (not raw string interpolation)
|
|
143
|
-
4. Escaped via `escapeShellArg()` for exec channel commands
|
|
144
|
-
|
|
145
|
-
### SFTP Security
|
|
146
|
-
|
|
147
|
-
- `sftp_upload` / `sftp_download`: paths validated before transfer
|
|
148
|
-
- `sftp_list`: read-only, allowed even in read-only mode
|
|
149
|
-
- `deploy`: validates all paths in the files array, plus exec commands for chmod/chown/restart
|
|
150
|
-
|
|
151
|
-
---
|
|
152
|
-
|
|
153
|
-
## Proxy Jump / Bastion Security
|
|
154
|
-
|
|
155
|
-
- Both the bastion host AND the target host must pass `isHostAllowed()` checks
|
|
156
|
-
- Bastion resolved from `~/.ssh/config` (`ProxyJump` directive) or explicit `proxy_jump` parameter
|
|
157
|
-
- Each hop uses its own SSH key from the corresponding `Host` config block
|
|
158
|
-
- The bastion connection is established first, then `forwardOut` is used to reach the target
|
|
159
|
-
|
|
160
|
-
---
|
|
161
|
-
|
|
162
|
-
## Session Isolation
|
|
163
|
-
|
|
164
|
-
- Each session = **one dedicated SSH connection** + **one dedicated tmux session**
|
|
165
|
-
- Sessions cannot see each other's tmux panes
|
|
166
|
-
- Session metadata stored locally at `~/.dynamic-ssh-mcp/sessions.json`
|
|
167
|
-
- No passwords are stored — auth is key-based via `~/.ssh/config`
|
|
168
|
-
- SSH key passphrases not stored; use `ssh-agent` for passphrase-protected keys
|
|
169
|
-
|
|
170
|
-
---
|
|
171
|
-
|
|
172
|
-
## Hardening Recommendations
|
|
173
|
-
|
|
174
|
-
### Production
|
|
175
|
-
|
|
176
|
-
```bash
|
|
177
|
-
# Global read-only on production
|
|
178
|
-
export MCP_SSH_READONLY=true
|
|
179
|
-
|
|
180
|
-
# Only prod and staging hosts
|
|
181
|
-
export MCP_SSH_ALLOWED_HOSTS=prod,staging
|
|
182
|
-
|
|
183
|
-
# Block dangerous operations
|
|
184
|
-
export MCP_SSH_DENYLIST_COMMANDS="rm -rf,shutdown,reboot,dd if=,chmod 777,fdisk,mkfs"
|
|
185
|
-
|
|
186
|
-
# Only restore known sessions (don't discover new ones)
|
|
187
|
-
export MCP_SSH_RESTORE_SESSIONS=true
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### Per-Host Fine-Tuning (via tools_config / host_security)
|
|
191
|
-
|
|
192
|
-
```
|
|
193
|
-
# Lock production to read-only
|
|
194
|
-
host_security set host=prod readonly=true
|
|
195
|
-
|
|
196
|
-
# Limit dev commands
|
|
197
|
-
host_security set host=dev deny_commands=["rm -rf", "shutdown", "reboot"]
|
|
198
|
-
|
|
199
|
-
# Disable unused tools to reduce attack surface
|
|
200
|
-
tools_config disable db_query
|
|
201
|
-
tools_config disable backup
|
|
202
|
-
tools_config disable sync
|
|
203
|
-
tools_config disable deploy
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
---
|
|
207
|
-
|
|
208
|
-
## Audit & Monitoring
|
|
209
|
-
|
|
210
|
-
- All tool invocations logged to stderr via pino (configurable `LOG_LEVEL`)
|
|
211
|
-
- Session create/remove/restore events logged with session IDs
|
|
212
|
-
- Failed connections, auth rejections, and denied commands logged at `warn` level
|
|
213
|
-
- Log format: structured JSON with timestamps
|
|
1
|
+
# Security Model
|
|
2
|
+
|
|
3
|
+
`ssh-agent-workspace` implements a **three-layer defense** architecture. Each layer adds granularity without breaking the layers above it.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Layer 1: Global (Environment Variables)
|
|
8
|
+
|
|
9
|
+
Set at server startup via environment variables. Applies to **all hosts**.
|
|
10
|
+
|
|
11
|
+
| Variable | Effect |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `MCP_SSH_READONLY=true` | Blocks all write operations globally |
|
|
14
|
+
| `MCP_SSH_ALLOWED_HOSTS=prod,staging` | Only these host aliases can be connected to |
|
|
15
|
+
| `MCP_SSH_DENYLIST_COMMANDS=rm,shutdown,dd` | Blocks commands containing these substrings (case-insensitive) |
|
|
16
|
+
|
|
17
|
+
### Read-Only Mode: Blocked Tools
|
|
18
|
+
|
|
19
|
+
When `MCP_SSH_READONLY=true`, the following tools return errors:
|
|
20
|
+
|
|
21
|
+
- `exec` — Command execution disabled
|
|
22
|
+
- `send_input` — Input sending disabled
|
|
23
|
+
- `sftp_upload` — SFTP upload disabled
|
|
24
|
+
- `sftp_download` — SFTP download disabled
|
|
25
|
+
- `deploy` — Deploy disabled
|
|
26
|
+
- `backup` — Backup disabled
|
|
27
|
+
- `sync` — Sync disabled
|
|
28
|
+
- `group_exec` — Group exec disabled
|
|
29
|
+
- `db_query` — DB query disabled
|
|
30
|
+
- `ssh_tunnel_open` — Tunnels disabled
|
|
31
|
+
|
|
32
|
+
### Tools Always Allowed (Read Operations)
|
|
33
|
+
|
|
34
|
+
`list_hosts`, `connect`, `reconnect_to_tmux`, `read_output`, `interrupt`, `disconnect`, `list_sessions`, `sftp_list`, `connection_status`, `health_check`, `tail_log`, `ssh_tunnel_close`, `ssh_tunnel_list`, `tools_config`, `host_security`
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Layer 2: Per-Host (Host Security)
|
|
39
|
+
|
|
40
|
+
Managed via the `host_security` tool and persisted in `~/.dynamic-ssh-mcp/host_security.json`.
|
|
41
|
+
|
|
42
|
+
Per-host settings **override** global settings for that specific host only.
|
|
43
|
+
|
|
44
|
+
### Configuration
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"prod": {
|
|
49
|
+
"readonly": true,
|
|
50
|
+
"deny_commands": ["shutdown", "reboot", "dd if="]
|
|
51
|
+
},
|
|
52
|
+
"staging": {
|
|
53
|
+
"deny_commands": ["rm -rf /", "shutdown"]
|
|
54
|
+
},
|
|
55
|
+
"dev": {
|
|
56
|
+
"allow_commands": [
|
|
57
|
+
"ls", "cat", "echo", "ps aux",
|
|
58
|
+
"docker ps", "docker logs",
|
|
59
|
+
"git status", "git diff", "git log",
|
|
60
|
+
"npm run", "node"
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Rules
|
|
67
|
+
|
|
68
|
+
| Field | Behavior |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `readonly: true` | Host locked to read-only, regardless of global `MCP_SSH_READONLY` |
|
|
71
|
+
| `readonly: false` | Host explicitly writeable (does **not** override global readonly) |
|
|
72
|
+
| `allow_commands: [...]` | If set, **only** commands matching these patterns are allowed (case-insensitive substring match) |
|
|
73
|
+
| `deny_commands: [...]` | Commands matching these patterns are blocked (case-insensitive substring match) |
|
|
74
|
+
|
|
75
|
+
**Precedence:** `deny_commands` wins over `allow_commands`. If a command matches both, it's blocked.
|
|
76
|
+
|
|
77
|
+
### Enforcement Points
|
|
78
|
+
|
|
79
|
+
Per-host read-only is checked **after session lookup** in every write tool:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
exec, send_input, sftp_upload, sftp_download,
|
|
83
|
+
deploy, backup, sync, group_exec, db_query, ssh_tunnel_open
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
If a session's host has `readonly: true`, the tool returns:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
Error: Host 'prod' is in read-only mode. <operation> is disabled.
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Per-Host Command Filtering
|
|
93
|
+
|
|
94
|
+
`isHostCommandDenied(host, command)` checks:
|
|
95
|
+
1. Global `MCP_SSH_DENYLIST_COMMANDS`
|
|
96
|
+
2. Per-host `allow_commands` (if set, command must match at least one)
|
|
97
|
+
3. Per-host `deny_commands`
|
|
98
|
+
|
|
99
|
+
Per-host command filtering is available to all tools via the shared security utility.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Layer 3: Per-Operation (Tool-Level)
|
|
104
|
+
|
|
105
|
+
### Path Sanitization
|
|
106
|
+
|
|
107
|
+
All file paths used in SFTP, backup, deploy, sync, and tail_log are validated:
|
|
108
|
+
|
|
109
|
+
- Rejects paths containing `;`, `&&`, `||`, `|`
|
|
110
|
+
- Rejects paths starting with `-` (option injection)
|
|
111
|
+
- Paths are shell-escaped before passing to exec commands
|
|
112
|
+
|
|
113
|
+
### Command Denylist (Global)
|
|
114
|
+
|
|
115
|
+
Commands containing these substrings are blocked (case-insensitive):
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
rm -rf, shutdown, reboot, dd, mkfs, fdisk, :(){ :|:& };: (fork bomb)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Configured via `MCP_SSH_DENYLIST_COMMANDS` — comma-separated:
|
|
122
|
+
```
|
|
123
|
+
MCP_SSH_DENYLIST_COMMANDS=rm -rf,shutdown,dd if=,mkfs,chmod 777
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### SQL/MongoDB Query Security
|
|
127
|
+
|
|
128
|
+
The `db_query` tool enforces read-only queries:
|
|
129
|
+
|
|
130
|
+
**SQL (MySQL/PostgreSQL):**
|
|
131
|
+
- Blocked keywords: `DROP`, `DELETE`, `INSERT`, `UPDATE`, `ALTER`, `CREATE`, `TRUNCATE`, `GRANT`, `REVOKE`, `RENAME`, `REPLACE`, `MERGE`, `UPSERT`, `LOAD`
|
|
132
|
+
- Only allowed prefixes: `SELECT`, `SHOW`, `EXPLAIN`, `DESCRIBE`, `WITH`
|
|
133
|
+
|
|
134
|
+
**MongoDB:**
|
|
135
|
+
- Blocked methods: `deleteOne`, `deleteMany`, `insertOne`, `insertMany`, `updateOne`, `updateMany`, `replaceOne`, `drop`, `dropDatabase`, `createIndex`, `createCollection`
|
|
136
|
+
|
|
137
|
+
### Shell Injection Protection
|
|
138
|
+
|
|
139
|
+
All arguments passed to tmux commands are:
|
|
140
|
+
1. Validated via Zod schemas before use
|
|
141
|
+
2. Passed through `sanitizeTmuxSessionName()` for session names
|
|
142
|
+
3. Base64-encoded buffer pipeline for command input (not raw string interpolation)
|
|
143
|
+
4. Escaped via `escapeShellArg()` for exec channel commands
|
|
144
|
+
|
|
145
|
+
### SFTP Security
|
|
146
|
+
|
|
147
|
+
- `sftp_upload` / `sftp_download`: paths validated before transfer
|
|
148
|
+
- `sftp_list`: read-only, allowed even in read-only mode
|
|
149
|
+
- `deploy`: validates all paths in the files array, plus exec commands for chmod/chown/restart
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Proxy Jump / Bastion Security
|
|
154
|
+
|
|
155
|
+
- Both the bastion host AND the target host must pass `isHostAllowed()` checks
|
|
156
|
+
- Bastion resolved from `~/.ssh/config` (`ProxyJump` directive) or explicit `proxy_jump` parameter
|
|
157
|
+
- Each hop uses its own SSH key from the corresponding `Host` config block
|
|
158
|
+
- The bastion connection is established first, then `forwardOut` is used to reach the target
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Session Isolation
|
|
163
|
+
|
|
164
|
+
- Each session = **one dedicated SSH connection** + **one dedicated tmux session**
|
|
165
|
+
- Sessions cannot see each other's tmux panes
|
|
166
|
+
- Session metadata stored locally at `~/.dynamic-ssh-mcp/sessions.json`
|
|
167
|
+
- No passwords are stored — auth is key-based via `~/.ssh/config`
|
|
168
|
+
- SSH key passphrases not stored; use `ssh-agent` for passphrase-protected keys
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Hardening Recommendations
|
|
173
|
+
|
|
174
|
+
### Production
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Global read-only on production
|
|
178
|
+
export MCP_SSH_READONLY=true
|
|
179
|
+
|
|
180
|
+
# Only prod and staging hosts
|
|
181
|
+
export MCP_SSH_ALLOWED_HOSTS=prod,staging
|
|
182
|
+
|
|
183
|
+
# Block dangerous operations
|
|
184
|
+
export MCP_SSH_DENYLIST_COMMANDS="rm -rf,shutdown,reboot,dd if=,chmod 777,fdisk,mkfs"
|
|
185
|
+
|
|
186
|
+
# Only restore known sessions (don't discover new ones)
|
|
187
|
+
export MCP_SSH_RESTORE_SESSIONS=true
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Per-Host Fine-Tuning (via tools_config / host_security)
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
# Lock production to read-only
|
|
194
|
+
host_security set host=prod readonly=true
|
|
195
|
+
|
|
196
|
+
# Limit dev commands
|
|
197
|
+
host_security set host=dev deny_commands=["rm -rf", "shutdown", "reboot"]
|
|
198
|
+
|
|
199
|
+
# Disable unused tools to reduce attack surface
|
|
200
|
+
tools_config disable db_query
|
|
201
|
+
tools_config disable backup
|
|
202
|
+
tools_config disable sync
|
|
203
|
+
tools_config disable deploy
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Audit & Monitoring
|
|
209
|
+
|
|
210
|
+
- All tool invocations logged to stderr via pino (configurable `LOG_LEVEL`)
|
|
211
|
+
- Session create/remove/restore events logged with session IDs
|
|
212
|
+
- Failed connections, auth rejections, and denied commands logged at `warn` level
|
|
213
|
+
- Log format: structured JSON with timestamps
|