@aaarc/handfree-ssh-mcp 1.0.3 → 1.0.4

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.
@@ -148,6 +148,12 @@ function buildYamlServerConfig(name, serverConfig, options = {}) {
148
148
  if (serverConfig.blacklist !== undefined) {
149
149
  merged.commandBlacklist = serverConfig.blacklist ?? undefined;
150
150
  }
151
+ if (serverConfig.disableBuiltinGuards !== undefined) {
152
+ merged.disableBuiltinGuards = serverConfig.disableBuiltinGuards === true;
153
+ }
154
+ if (serverConfig.disableBuiltinBlacklist !== undefined) {
155
+ merged.disableBuiltinBlacklist = serverConfig.disableBuiltinBlacklist === true;
156
+ }
151
157
  if (serverConfig.safeDirectory !== undefined) {
152
158
  merged.safeDirectory = serverConfig.safeDirectory ?? undefined;
153
159
  }
@@ -175,6 +181,8 @@ function mergeConfigMaps(sshConfigs, yamlConfigs) {
175
181
  commandMode: yamlConfig.commandMode ?? merged[name]?.commandMode,
176
182
  commandWhitelist: yamlConfig.commandWhitelist ?? merged[name]?.commandWhitelist,
177
183
  commandBlacklist: yamlConfig.commandBlacklist ?? merged[name]?.commandBlacklist,
184
+ disableBuiltinGuards: yamlConfig.disableBuiltinGuards ?? merged[name]?.disableBuiltinGuards,
185
+ disableBuiltinBlacklist: yamlConfig.disableBuiltinBlacklist ?? merged[name]?.disableBuiltinBlacklist,
178
186
  safeDirectory: yamlConfig.safeDirectory ?? merged[name]?.safeDirectory,
179
187
  };
180
188
  }
@@ -1,6 +1,6 @@
1
1
  export const SERVER_CONFIG = {
2
2
  name: "ssh-mcp-server",
3
- version: "1.0.3",
3
+ version: "1.0.4",
4
4
  };
5
5
  export const SERVER_INSTRUCTIONS = `This server provides SSH access to servers loaded from OpenSSH config (~/.ssh/config by default) and optional YAML config/policy overlays.
6
6
 
@@ -29,23 +29,10 @@ export const BUILT_IN_COMMAND_BLACKLIST = [
29
29
  { regex: /\bRemove-Item\b(?=.*\s-Recurse(?:\s|$))(?=.*\s-Force(?:\s|$))/i, reason: "recursive force Remove-Item" },
30
30
  { regex: /^\s*(?:del|erase|rd)\b(?=.*(?:\/s\b|\s-Recurse(?:\s|$)))(?=.*(?:\/q\b|\s-Force(?:\s|$)))/i, reason: "recursive quiet Windows delete" },
31
31
  { regex: /^\s*(?:sudo\s+)?rmdir\s+(?:\/|\*|~|\$HOME|%USERPROFILE%|[A-Za-z]:\\)(?:\s|$)/i, reason: "dangerous rmdir target" },
32
- { regex: /^\s*(?:sudo\s+)?dd\b.*\bof=/i, reason: "dd with of= (can overwrite devices/files)" },
33
- { regex: /^\s*(?:sudo\s+)?mkfs(?:\.\S+)?\b/i, reason: "filesystem formatting command" },
34
- { regex: /^\s*(?:sudo\s+)?(?:diskpart|format)(?:\s|$)/i, reason: "disk formatting command" },
35
- { regex: /\b(?:Format-Volume|Clear-Disk|Remove-Partition)\b/i, reason: "Windows disk destructive command" },
36
32
  { regex: /^\s*(?:sudo\s+)?chmod\s+-R\s+777\b/i, reason: "recursive world-writable chmod" },
37
33
  { regex: /^\s*(?:sudo\s+)?chown\s+-R\s+\S+\s+\/(?:\s|$)/i, reason: "recursive chown on root" },
38
34
  ];
39
35
  export const BUILT_IN_DESTRUCTIVE_GUARDS = [
40
- { regex: /\brm\b/, reason: "rm in command chain" },
41
- { regex: /\brmdir\b/, reason: "rmdir in command chain" },
42
- { regex: /\bunlink\b/, reason: "unlink in command chain" },
43
- { regex: /\bshred\b/, reason: "shred in command chain" },
44
- { regex: /\btruncate\b/, reason: "truncate in command chain" },
45
- { regex: /-delete\b/, reason: "find -delete detected" },
46
- { regex: /-exec\s+rm\b/, reason: "find -exec rm detected" },
47
- { regex: /\bmv\b/, reason: "mv detected (can overwrite)" },
48
- { regex: /\bcp\b.*-f/, reason: "cp -f detected (force overwrite)" },
49
36
  { regex: /(?<![0-9])>\s*\/(?!dev\/null)/, reason: "output redirection to absolute path" },
50
37
  { regex: />\s*~/, reason: "output redirection to home path" },
51
38
  ];
@@ -824,7 +811,9 @@ export class SSHConnectionManager {
824
811
  || (config.username
825
812
  ? (config.username === 'root' ? '/root' : `/home/${config.username}`)
826
813
  : SSHConnectionManager.DEFAULT_SAFE_DIRECTORY);
827
- const destructiveReason = this.getDestructiveMatch(command);
814
+ const destructiveReason = config.disableBuiltinGuards
815
+ ? null
816
+ : this.getDestructiveMatch(command);
828
817
  if (destructiveReason) {
829
818
  // Command contains rm/rmdir - check if it's a simple rm command or hidden in a chain
830
819
  const trimmed = command.trim();
@@ -852,13 +841,15 @@ export class SSHConnectionManager {
852
841
  // ========================================
853
842
  // LAYER 2: Built-in blacklist for high-risk operations
854
843
  // ========================================
855
- for (const { regex, reason } of BUILT_IN_COMMAND_BLACKLIST) {
856
- if (regex.test(command)) {
857
- Logger.log(`Command blocked by built-in blacklist (${reason}): ${command}`, "info");
858
- return {
859
- isAllowed: false,
860
- reason: `Command blocked by built-in blacklist: ${reason}`,
861
- };
844
+ if (!config.disableBuiltinBlacklist) {
845
+ for (const { regex, reason } of BUILT_IN_COMMAND_BLACKLIST) {
846
+ if (regex.test(command)) {
847
+ Logger.log(`Command blocked by built-in blacklist (${reason}): ${command}`, "info");
848
+ return {
849
+ isAllowed: false,
850
+ reason: `Command blocked by built-in blacklist: ${reason}`,
851
+ };
852
+ }
862
853
  }
863
854
  }
864
855
  // ========================================
package/package.json CHANGED
@@ -1,61 +1,61 @@
1
- {
2
- "name": "@aaarc/handfree-ssh-mcp",
3
- "version": "1.0.3",
4
- "description": "Handfree SSH MCP Server - A hands-free SSH automation tool via MCP protocol",
5
- "main": "build/index.js",
6
- "type": "module",
7
- "bin": {
8
- "handfree-ssh-mcp": "build/index.js"
9
- },
10
- "repository": {
11
- "type": "git",
12
- "url": "git+https://github.com/Ironboxplus/handfree-ssh-mcp.git"
13
- },
14
- "bugs": {
15
- "url": "https://github.com/Ironboxplus/handfree-ssh-mcp/issues"
16
- },
17
- "homepage": "https://github.com/Ironboxplus/handfree-ssh-mcp#readme",
18
- "publishConfig": {
19
- "access": "public"
20
- },
21
- "scripts": {
22
- "test": "npm run build && node --test build/tests/config.test.js build/tests/command-validation.test.js build/tests/tool-error.test.js build/tests/tools.test.js build/tests/output-collector.test.js build/tests/output-log-writer.test.js build/tests/recent-fixes.test.js",
23
- "test:config": "npm run build && node --test build/tests/config.test.js",
24
- "test:cmd": "npm run build && node --test build/tests/command-validation.test.js",
25
- "test:tools": "npm run build && node --test build/tests/tools.test.js",
26
- "test:integration": "npm run build && node build/tests/integration.test.js",
27
- "test:all": "npm run test && npm run test:integration",
28
- "build": "node scripts/build.js",
29
- "prepublishOnly": "npm run build"
30
- },
31
- "keywords": [
32
- "ssh",
33
- "mcp",
34
- "server",
35
- "cli"
36
- ],
37
- "author": "Junki",
38
- "license": "ISC",
39
- "dependencies": {
40
- "@modelcontextprotocol/sdk": "1.17.5",
41
- "js-yaml": "^4.1.0",
42
- "socks": "^2.8.6",
43
- "ssh2": "^1.16.0",
44
- "zod": "^3.24.2"
45
- },
46
- "devDependencies": {
47
- "@types/js-yaml": "^4.0.9",
48
- "@types/node": "^22.13.10",
49
- "@types/ssh2": "^1.15.5",
50
- "typescript": "^5.8.2"
51
- },
52
- "files": [
53
- "build/config/**/*",
54
- "build/core/**/*",
55
- "build/models/**/*",
56
- "build/services/**/*",
57
- "build/tools/**/*",
58
- "build/utils/**/*",
59
- "build/index.js"
60
- ]
61
- }
1
+ {
2
+ "name": "@aaarc/handfree-ssh-mcp",
3
+ "version": "1.0.4",
4
+ "description": "Handfree SSH MCP Server - A hands-free SSH automation tool via MCP protocol",
5
+ "main": "build/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "handfree-ssh-mcp": "build/index.js"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/Ironboxplus/handfree-ssh-mcp.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/Ironboxplus/handfree-ssh-mcp/issues"
16
+ },
17
+ "homepage": "https://github.com/Ironboxplus/handfree-ssh-mcp#readme",
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "scripts": {
22
+ "test": "npm run build && node --test build/tests/config.test.js build/tests/command-validation.test.js build/tests/tool-error.test.js build/tests/tools.test.js build/tests/output-collector.test.js build/tests/output-log-writer.test.js build/tests/recent-fixes.test.js",
23
+ "test:config": "npm run build && node --test build/tests/config.test.js",
24
+ "test:cmd": "npm run build && node --test build/tests/command-validation.test.js",
25
+ "test:tools": "npm run build && node --test build/tests/tools.test.js",
26
+ "test:integration": "npm run build && node build/tests/integration.test.js",
27
+ "test:all": "npm run test && npm run test:integration",
28
+ "build": "node scripts/build.js",
29
+ "prepublishOnly": "npm run build"
30
+ },
31
+ "keywords": [
32
+ "ssh",
33
+ "mcp",
34
+ "server",
35
+ "cli"
36
+ ],
37
+ "author": "Junki",
38
+ "license": "ISC",
39
+ "dependencies": {
40
+ "@modelcontextprotocol/sdk": "1.17.5",
41
+ "js-yaml": "^4.1.0",
42
+ "socks": "^2.8.6",
43
+ "ssh2": "^1.16.0",
44
+ "zod": "^3.24.2"
45
+ },
46
+ "devDependencies": {
47
+ "@types/js-yaml": "^4.0.9",
48
+ "@types/node": "^22.13.10",
49
+ "@types/ssh2": "^1.15.5",
50
+ "typescript": "^5.8.2"
51
+ },
52
+ "files": [
53
+ "build/config/**/*",
54
+ "build/core/**/*",
55
+ "build/models/**/*",
56
+ "build/services/**/*",
57
+ "build/tools/**/*",
58
+ "build/utils/**/*",
59
+ "build/index.js"
60
+ ]
61
+ }