@aaarc/handfree-ssh-mcp 1.0.0 → 1.0.1

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.
@@ -1,7 +1,71 @@
1
1
  import { z } from "zod";
2
- import { SSHConnectionManager } from "../services/ssh-connection-manager.js";
2
+ import { BUILT_IN_COMMAND_BLACKLIST, BUILT_IN_DESTRUCTIVE_GUARDS, SSHConnectionManager, } from "../services/ssh-connection-manager.js";
3
3
  import { Logger } from "../utils/logger.js";
4
4
  import { ToolError, formatToolErrorResponse, toToolError } from "../utils/tool-error.js";
5
+ export function formatCommandPolicy(config) {
6
+ const whitelist = config.commandWhitelist || [];
7
+ const blacklist = config.commandBlacklist || [];
8
+ const commandMode = config.commandMode
9
+ ?? (whitelist.length > 0 ? "whitelist" : "blacklist");
10
+ let output = `## Command Policy\n\n`;
11
+ output += `Mode: \`${commandMode}\``;
12
+ if (!config.commandMode && whitelist.length > 0) {
13
+ output += ` _(legacy whitelist config)_`;
14
+ }
15
+ output += `\n\n`;
16
+ output += `Built-in destructive command guards:\n\n`;
17
+ for (const { regex, reason } of BUILT_IN_DESTRUCTIVE_GUARDS) {
18
+ output += `- \`${regex.source}\` -> ${reason}\n`;
19
+ }
20
+ output += `\n`;
21
+ output += `Built-in dangerous-command blacklist:\n\n`;
22
+ for (const { regex, reason } of BUILT_IN_COMMAND_BLACKLIST) {
23
+ output += `- \`${regex.source}\` -> ${reason}\n`;
24
+ }
25
+ output += `\n`;
26
+ output += `## Allowed Commands (Whitelist)\n\n`;
27
+ if (commandMode !== "whitelist") {
28
+ output += `Whitelist is inactive in blacklist mode.\n\n`;
29
+ }
30
+ else if (whitelist.length === 0) {
31
+ output += `WARNING: Whitelist mode is active but no whitelist patterns are configured, so all commands are blocked after blacklist checks.\n\n`;
32
+ }
33
+ else {
34
+ output += `${whitelist.length} patterns:\n\n`;
35
+ for (const pattern of whitelist) {
36
+ const readable = patternToReadable(pattern);
37
+ output += `- \`${pattern}\``;
38
+ if (readable !== pattern) {
39
+ output += ` -> ${readable}`;
40
+ }
41
+ output += `\n`;
42
+ }
43
+ output += `\n`;
44
+ }
45
+ if (blacklist.length > 0) {
46
+ output += `## Blocked Commands (Blacklist)\n\n`;
47
+ output += `${blacklist.length} patterns:\n\n`;
48
+ for (const pattern of blacklist) {
49
+ const readable = patternToReadable(pattern);
50
+ output += `- \`${pattern}\``;
51
+ if (readable !== pattern) {
52
+ output += ` -> ${readable}`;
53
+ }
54
+ output += `\n`;
55
+ }
56
+ output += `\n`;
57
+ }
58
+ if (commandMode === "whitelist" && whitelist.length > 0) {
59
+ output += `## Example Commands\n\n`;
60
+ output += `Based on the active whitelist, here are some commands you can likely use:\n\n`;
61
+ const examples = generateExamples(whitelist);
62
+ for (const ex of examples.slice(0, 10)) {
63
+ output += `- \`${ex}\`\n`;
64
+ }
65
+ output += `\n`;
66
+ }
67
+ return output;
68
+ }
5
69
  /**
6
70
  * Register show-whitelist tool
7
71
  *
@@ -9,7 +73,7 @@ import { ToolError, formatToolErrorResponse, toToolError } from "../utils/tool-e
9
73
  */
10
74
  export function registerShowWhitelistTool(server) {
11
75
  const sshManager = SSHConnectionManager.getInstance();
12
- server.tool("show-whitelist", "Show the configured whitelist, blacklist, and SFTP path policy (allowedRemoteDirectories / allowedLocalDirectories) for a server. Use this before execute-command when you need to understand which commands are allowed, why a command may be rejected, or what command patterns are safe to try next. Also use it before upload / download / transfer to see which paths SFTP is permitted to touch.", {
76
+ server.tool("show-whitelist", "Show the configured command policy, blacklist/whitelist patterns, and SFTP path policy (allowedRemoteDirectories / allowedLocalDirectories) for a server. Use this before execute-command when you need to understand which commands are allowed, why a command may be rejected, or what command patterns are safe to try next. Also use it before upload / download / transfer to see which paths SFTP is permitted to touch.", {
13
77
  connectionName: z
14
78
  .string()
15
79
  .optional()
@@ -28,41 +92,9 @@ export function registerShowWhitelistTool(server) {
28
92
  isError: true,
29
93
  };
30
94
  }
31
- const whitelist = config.commandWhitelist || [];
32
- const blacklist = config.commandBlacklist || [];
33
95
  let output = `# Command Permissions for: ${config.name}\n\n`;
34
96
  output += `Host: ${config.username}@${config.host}:${config.port}\n\n`;
35
- // Whitelist
36
- output += `## ✅ Allowed Commands (Whitelist)\n\n`;
37
- if (whitelist.length === 0) {
38
- output += `⚠️ No whitelist configured - using default safe commands.\n\n`;
39
- }
40
- else {
41
- output += `${whitelist.length} patterns:\n\n`;
42
- for (const pattern of whitelist) {
43
- const readable = patternToReadable(pattern);
44
- output += `- \`${pattern}\``;
45
- if (readable !== pattern) {
46
- output += ` → ${readable}`;
47
- }
48
- output += `\n`;
49
- }
50
- output += `\n`;
51
- }
52
- // Blacklist
53
- if (blacklist.length > 0) {
54
- output += `## ❌ Blocked Commands (Blacklist)\n\n`;
55
- output += `${blacklist.length} patterns:\n\n`;
56
- for (const pattern of blacklist) {
57
- const readable = patternToReadable(pattern);
58
- output += `- \`${pattern}\``;
59
- if (readable !== pattern) {
60
- output += ` → ${readable}`;
61
- }
62
- output += `\n`;
63
- }
64
- output += `\n`;
65
- }
97
+ output += formatCommandPolicy(config);
66
98
  // SFTP path policy (upload / download / transfer tools only)
67
99
  const allowedRemoteDirs = config.allowedRemoteDirectories ?? [];
68
100
  const allowedLocalDirs = config.allowedLocalDirectories ?? [];
@@ -112,13 +144,6 @@ export function registerShowWhitelistTool(server) {
112
144
  output += `- This server's logs land under: \`${serverLogDir}/\`\n`;
113
145
  output += `- File name: \`<timestamp>-<pid>-<rand>.log\` with \`=== META / STDOUT / STDERR / END ===\` markers.\n`;
114
146
  output += `- When output is truncated, the response includes an \`[OUTPUT TRUNCATED]\` header with the on-disk log path.\n\n`;
115
- // Quick examples
116
- output += `## 💡 Example Commands\n\n`;
117
- output += `Based on the whitelist, here are some commands you can likely use:\n\n`;
118
- const examples = generateExamples(whitelist);
119
- for (const ex of examples.slice(0, 10)) {
120
- output += `- \`${ex}\`\n`;
121
- }
122
147
  return {
123
148
  content: [{ type: "text", text: output }],
124
149
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aaarc/handfree-ssh-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Handfree SSH MCP Server - A hands-free SSH automation tool via MCP protocol",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
@@ -15,7 +15,10 @@
15
15
  "url": "https://github.com/Ironboxplus/handfree-ssh-mcp/issues"
16
16
  },
17
17
  "homepage": "https://github.com/Ironboxplus/handfree-ssh-mcp#readme",
18
- "scripts": {
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "scripts": {
19
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",
20
23
  "test:config": "npm run build && node --test build/tests/config.test.js",
21
24
  "test:cmd": "npm run build && node --test build/tests/command-validation.test.js",
@@ -46,7 +49,13 @@
46
49
  "@types/ssh2": "^1.15.5",
47
50
  "typescript": "^5.8.2"
48
51
  },
49
- "files": [
50
- "build/**/*"
51
- ]
52
- }
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
+ }