ssh-mcp-pro 1.1.5 → 1.2.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.
Files changed (123) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +47 -7
  3. package/SECURITY.md +4 -0
  4. package/SECURITY_DECISIONS.md +32 -1
  5. package/dist/agent-bin.js +0 -0
  6. package/dist/connector-profile.d.ts +3 -0
  7. package/dist/connector-profile.d.ts.map +1 -1
  8. package/dist/connector-profile.js +36 -14
  9. package/dist/connector-profile.js.map +1 -1
  10. package/dist/http-security.d.ts.map +1 -1
  11. package/dist/http-security.js +19 -4
  12. package/dist/http-security.js.map +1 -1
  13. package/dist/http-server-lifecycle.d.ts +17 -0
  14. package/dist/http-server-lifecycle.d.ts.map +1 -0
  15. package/dist/http-server-lifecycle.js +122 -0
  16. package/dist/http-server-lifecycle.js.map +1 -0
  17. package/dist/http-server-router.d.ts +28 -0
  18. package/dist/http-server-router.d.ts.map +1 -0
  19. package/dist/http-server-router.js +108 -0
  20. package/dist/http-server-router.js.map +1 -0
  21. package/dist/http-server-runtime.d.ts +21 -0
  22. package/dist/http-server-runtime.d.ts.map +1 -0
  23. package/dist/http-server-runtime.js +415 -0
  24. package/dist/http-server-runtime.js.map +1 -0
  25. package/dist/http-session-registry.d.ts.map +1 -1
  26. package/dist/http-session-registry.js.map +1 -1
  27. package/dist/index.js +12 -5
  28. package/dist/index.js.map +1 -1
  29. package/dist/mcp.d.ts +1 -1
  30. package/dist/mcp.js +1 -1
  31. package/dist/prompts.d.ts.map +1 -1
  32. package/dist/prompts.js +8 -1
  33. package/dist/prompts.js.map +1 -1
  34. package/dist/remote/agent-cli.d.ts.map +1 -1
  35. package/dist/remote/agent-cli.js +20 -1
  36. package/dist/remote/agent-cli.js.map +1 -1
  37. package/dist/remote/agent-connection-lifecycle.d.ts +47 -0
  38. package/dist/remote/agent-connection-lifecycle.d.ts.map +1 -0
  39. package/dist/remote/agent-connection-lifecycle.js +304 -0
  40. package/dist/remote/agent-connection-lifecycle.js.map +1 -0
  41. package/dist/remote/agent-executor.d.ts.map +1 -1
  42. package/dist/remote/agent-executor.js +93 -68
  43. package/dist/remote/agent-executor.js.map +1 -1
  44. package/dist/remote/agent-handler.d.ts +22 -8
  45. package/dist/remote/agent-handler.d.ts.map +1 -1
  46. package/dist/remote/agent-handler.js +151 -98
  47. package/dist/remote/agent-handler.js.map +1 -1
  48. package/dist/remote/config.d.ts.map +1 -1
  49. package/dist/remote/config.js +14 -0
  50. package/dist/remote/config.js.map +1 -1
  51. package/dist/remote/control-plane.d.ts.map +1 -1
  52. package/dist/remote/control-plane.js +3 -0
  53. package/dist/remote/control-plane.js.map +1 -1
  54. package/dist/remote/policy-update-guard.d.ts +14 -0
  55. package/dist/remote/policy-update-guard.d.ts.map +1 -0
  56. package/dist/remote/policy-update-guard.js +27 -0
  57. package/dist/remote/policy-update-guard.js.map +1 -0
  58. package/dist/remote/policy.d.ts.map +1 -1
  59. package/dist/remote/policy.js +13 -2
  60. package/dist/remote/policy.js.map +1 -1
  61. package/dist/remote/safe-file.d.ts +5 -0
  62. package/dist/remote/safe-file.d.ts.map +1 -0
  63. package/dist/remote/safe-file.js +194 -0
  64. package/dist/remote/safe-file.js.map +1 -0
  65. package/dist/remote/store.d.ts.map +1 -1
  66. package/dist/remote/store.js.map +1 -1
  67. package/dist/remote/types.d.ts +5 -0
  68. package/dist/remote/types.d.ts.map +1 -1
  69. package/dist/remote/websocket.d.ts +38 -13
  70. package/dist/remote/websocket.d.ts.map +1 -1
  71. package/dist/remote/websocket.js +90 -126
  72. package/dist/remote/websocket.js.map +1 -1
  73. package/dist/render-http.js +2 -1
  74. package/dist/render-http.js.map +1 -1
  75. package/dist/retry.d.ts.map +1 -1
  76. package/dist/retry.js +4 -2
  77. package/dist/retry.js.map +1 -1
  78. package/dist/server-http.d.ts +1 -1
  79. package/dist/server-http.d.ts.map +1 -1
  80. package/dist/server-http.js +1 -421
  81. package/dist/server-http.js.map +1 -1
  82. package/dist/tools/connector.provider.d.ts.map +1 -1
  83. package/dist/tools/connector.provider.js +2 -1
  84. package/dist/tools/connector.provider.js.map +1 -1
  85. package/dist/types.d.ts.map +1 -1
  86. package/dist/types.js.map +1 -1
  87. package/docs/agent-runtime-config.md +34 -0
  88. package/docs/development/api-stability.md +44 -0
  89. package/docs/development/coding-standards.md +35 -0
  90. package/docs/development/commit-conventions.md +35 -0
  91. package/docs/development/dependency-management.md +60 -0
  92. package/docs/development/deprecation-policy.md +29 -0
  93. package/docs/development/local-setup.md +46 -0
  94. package/docs/development/release-process.md +57 -0
  95. package/docs/development/testing-policy.md +47 -0
  96. package/docs/explanation/architecture.md +44 -0
  97. package/docs/how-to/README.md +14 -0
  98. package/docs/how-to/configure-ssh-policy.md +50 -0
  99. package/docs/openssf-evidence.md +75 -0
  100. package/docs/openssf-gap-analysis.md +63 -0
  101. package/docs/openssf-proposal-links.md +37 -0
  102. package/docs/professionalization-plan.md +81 -0
  103. package/docs/reference/README.md +21 -0
  104. package/docs/reference/cli.md +41 -0
  105. package/docs/reference/compatibility.md +46 -0
  106. package/docs/reference/configuration.md +72 -0
  107. package/docs/remote-mcp-hardening.md +10 -0
  108. package/docs/repo-maturity-report.md +565 -0
  109. package/docs/security/assurance-case.md +58 -0
  110. package/docs/security/dependency-audit-exceptions.json +4 -0
  111. package/docs/security/dependency-audit-exceptions.md +15 -0
  112. package/docs/security/input-validation.md +54 -0
  113. package/docs/security/release-integrity.md +28 -0
  114. package/docs/security/scanning-policy.md +50 -0
  115. package/docs/security/secrets-management.md +49 -0
  116. package/docs/security/threat-model.md +65 -0
  117. package/docs/testing.md +24 -0
  118. package/docs/troubleshooting.md +56 -0
  119. package/docs/tutorials/getting-started.md +84 -0
  120. package/mcp.json +1 -1
  121. package/package.json +82 -68
  122. package/registry/ssh-mcp-pro/mcp.json +1 -1
  123. package/server.json +3 -3
@@ -0,0 +1,4 @@
1
+ {
2
+ "version": 1,
3
+ "exceptions": []
4
+ }
@@ -0,0 +1,15 @@
1
+ # Dependency Audit Exceptions
2
+
3
+ Dependency audit exceptions are a last resort. They must be scoped to a specific distribution surface, include reachability evidence, name an owner, define a removal condition, and expire automatically. The machine-readable source of truth is [`dependency-audit-exceptions.json`](./dependency-audit-exceptions.json).
4
+
5
+ The canonical workspace, Docker image, and release lockfile must remain free of known moderate-or-higher advisories. `pnpm run audit:packed` separately installs the generated npm tarball as an external consumer and rejects every advisory that is not listed in the exception file. It also rejects expired or stale exceptions.
6
+
7
+ ## Active exception
8
+
9
+ | Advisory | Package path | Scope | Reachability assessment | Owner | Expires |
10
+ | --- | --- | --- | --- | --- | --- |
11
+ | `GHSA-frvp-7c67-39w9` | `ssh-mcp-pro > @modelcontextprotocol/sdk > @hono/node-server` | Published npm consumer graph | The affected `serve-static` implementation is not imported or invoked by ssh-mcp-pro. HTTP entrypoints use the project's own Node HTTP implementation. | `@oaslananka` | 2026-08-23 |
12
+
13
+ The latest `@modelcontextprotocol/sdk` release still constrains `@hono/node-server` to the vulnerable 1.x major. Local, CI, and container installs use the patched 2.x major through the root workspace override. npm consumers do not inherit dependency-package overrides, so the exception remains visible only for the independently installed tarball.
14
+
15
+ Remove the exception immediately when the MCP SDK accepts `@hono/node-server >=2.0.5`, or before introducing any use of the Hono Node adapter or `serve-static`.
@@ -0,0 +1,54 @@
1
+ # Input Validation
2
+
3
+ This summarizes where and how untrusted input is constrained, based on the environment
4
+ variables and policy behavior already documented in the
5
+ [README configuration table](../../README.md#configuration) and
6
+ [SECURITY_DECISIONS.md](../../SECURITY_DECISIONS.md). It intentionally stays at the
7
+ policy level rather than asserting internal implementation details this audit didn't
8
+ verify line-by-line.
9
+
10
+ ## Schema-level validation
11
+
12
+ Tool inputs are validated with [zod](https://www.npmjs.com/package/zod) schemas (a
13
+ runtime dependency in `package.json`); policy file contents are checked against JSON
14
+ Schema tooling (`ajv` is a devDependency used in the validation scripts under
15
+ `scripts/`). Malformed input is expected to be rejected before it reaches SSH/filesystem
16
+ operations, rather than sanitized and passed through.
17
+
18
+ ## Allow/deny lists, not blanket flags
19
+
20
+ Most of the security-relevant configuration surface is an allow/deny pair rather than a
21
+ single boolean, specifically so operators can scope access narrowly instead of an
22
+ all-or-nothing switch:
23
+
24
+ - `SSH_MCP_ALLOWED_HOSTS` / implicit deny of everything else.
25
+ - `SSH_MCP_COMMAND_ALLOW` / `SSH_MCP_COMMAND_DENY`.
26
+ - `SSH_MCP_PATH_ALLOW_PREFIXES` / `SSH_MCP_PATH_DENY_PREFIXES` (remote filesystem).
27
+ - `SSH_MCP_LOCAL_PATH_ALLOW_PREFIXES` / `SSH_MCP_LOCAL_PATH_DENY_PREFIXES` (local
28
+ transfer endpoints).
29
+ - `SSH_MCP_TUNNEL_ALLOW_BIND_HOSTS` / `SSH_MCP_TUNNEL_DENY_BIND_HOSTS` and the
30
+ equivalent remote-host/port allow/deny pairs for tunnels.
31
+
32
+ Deny lists are checked in addition to allow lists — see
33
+ [docs/how-to/configure-ssh-policy.md](../how-to/configure-ssh-policy.md) for how to use
34
+ both together.
35
+
36
+ ## Size/resource bounds
37
+
38
+ Every buffer- or transfer-related setting has an explicit byte ceiling:
39
+ `SSH_MCP_MAX_COMMAND_OUTPUT_BYTES`, `SSH_MCP_MAX_FILE_SIZE`,
40
+ `SSH_MCP_MAX_FILE_WRITE_BYTES`, `SSH_MCP_MAX_TRANSFER_BYTES`,
41
+ `SSH_MCP_HTTP_MAX_REQUEST_BODY_BYTES`, and stream/session ceilings
42
+ (`SSH_MCP_MAX_STREAM_CHUNKS`, `SSH_MCP_MAX_SESSIONS`, `SSH_MCP_HTTP_MAX_SESSIONS`). These
43
+ exist to bound both memory use and the blast radius of any single tool call.
44
+
45
+ ## Resolved gap (was: known gap)
46
+
47
+ Several CodeQL alerts (`js/file-access-to-http` / `js/http-to-file-access` in
48
+ `src/remote/agent-cli.ts` and `scripts/check-dependency-freshness.mjs`) were exactly
49
+ input-validation-shaped findings — untrusted file data influencing a network request, or
50
+ untrusted network data influencing a file write. As of 2026-07-03 all were dismissed
51
+ after independently re-verifying the rationale in `SECURITY_DECISIONS.md` against the
52
+ current code (not just re-asserting stale documentation) — see
53
+ [docs/repo-maturity-report.md](../repo-maturity-report.md) for the audit trail. 0 open
54
+ CodeQL alerts remain.
@@ -0,0 +1,28 @@
1
+ # Release Integrity
2
+
3
+ How a release artifact can be verified, and what mechanism backs each guarantee. See
4
+ [docs/development/release-process.md](../development/release-process.md) for the full
5
+ release flow this is extracted from.
6
+
7
+ | Guarantee | Mechanism | Verify with |
8
+ | --- | --- | --- |
9
+ | The tarball wasn't corrupted/tampered in transit | SHA-256 checksum generated in `release-assets` (`sha256sum "artifacts/${PACKAGE_FILE}" > ...sha256`) | `sha256sum -c ssh-mcp-pro-<version>.tgz.sha256` |
10
+ | The tarball was actually built by this repo's release workflow, from the commit it claims | `actions/attest-build-provenance` on the package | `gh attestation verify ssh-mcp-pro-<version>.tgz --owner oaslananka` |
11
+ | The SBOM matches the released package and wasn't tampered with | Separate checksum + separate `actions/attest-build-provenance` run with `sbom-path` set | `sha256sum -c sbom.cdx.json.sha256`, `gh attestation verify sbom.cdx.json --owner oaslananka` |
12
+ | What's actually inside the package (dependency inventory) | CycloneDX SBOM (`pnpm run sbom`) | Inspect `sbom.cdx.json` directly, or feed it to any CycloneDX-compatible scanner |
13
+ | npm package provenance (when published) | `npm publish --provenance` (conditional on `AUTO_RELEASE_PUBLISH` repo variable) | `npm view ssh-mcp-pro provenance`, or the "Provenance" panel on the npmjs.com package page |
14
+ | Container image integrity | Digest-pinned references, no mutable `latest` tag | See [docs/docker.md](../docker.md) |
15
+
16
+ ## Current limitation
17
+
18
+ None of the above has been exercised against a real release yet — no release has been
19
+ published (see [docs/repo-maturity-report.md](../repo-maturity-report.md)). The
20
+ mechanisms are implemented and reviewed here on paper; they should be spot-checked
21
+ against the first actual release before being relied on operationally.
22
+
23
+ ## What this does *not* claim
24
+
25
+ This project does not claim a specific SLSA build level. `actions/attest-build-provenance`
26
+ produces build provenance attestations (in-toto/SLSA-compatible statements), which is a
27
+ meaningfully strong signal, but claiming a specific SLSA level requires meeting that
28
+ level's full requirement set — not attempted here to avoid overclaiming.
@@ -0,0 +1,50 @@
1
+ # Security scanning policy
2
+
3
+ This repository uses complementary scanners because no single tool covers source code,
4
+ dependencies, secrets, workflow configuration, and container images equally well.
5
+
6
+ ## Required controls
7
+
8
+ | Surface | Primary control | Enforcement |
9
+ | --- | --- | --- |
10
+ | TypeScript and workflows | CodeQL, Semgrep, and SonarQube Cloud | Required pull-request checks where configured |
11
+ | Dependency graph | `pnpm audit`, dependency review, Socket, and OSV verification | Moderate-or-higher audit findings fail the canonical quality gate unless a reviewed, expiring exception exists |
12
+ | Secrets | Gitleaks, SonarQube secrets, and Trivy filesystem scanning | New verified findings block merge |
13
+ | Container image | Trivy | Fixable High and Critical vulnerabilities fail the Docker workflow |
14
+ | Release artifacts | SBOM, package smoke tests, provenance, and registry metadata checks | Required before publishing |
15
+
16
+ ## Container policy
17
+
18
+ The Docker workflow performs two scans against the image built from the pull-request or
19
+ release commit:
20
+
21
+ 1. A SARIF scan reports all High and Critical findings to GitHub code scanning.
22
+ 2. A separate enforcement scan fails on **fixable** High or Critical vulnerabilities.
23
+
24
+ `ignore-unfixed: true` is intentionally limited to the enforcement scan. Unfixed findings
25
+ remain visible in SARIF and must be reconsidered when a fix becomes available. The project
26
+ does not maintain wildcard ignores or an indefinite `.trivyignore` baseline. A temporary
27
+ exception must identify the advisory, explain the exposure, name an owner, and include an
28
+ expiry date in a reviewed repository file or issue.
29
+
30
+ Short-lived test-runner images, such as `Dockerfile.test`, are not required to define a
31
+ `HEALTHCHECK`; they do not run a service and exit when the test command completes.
32
+
33
+ ## Local verification
34
+
35
+ ```bash
36
+ osv-scanner scan source --recursive .
37
+ semgrep ci --dry-run
38
+ trivy fs --skip-dirs node_modules --scanners vuln,misconfig,secret .
39
+ ```
40
+
41
+ Container enforcement requires Docker access:
42
+
43
+ ```bash
44
+ docker build -t ssh-mcp-pro:local .
45
+ trivy image --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 ssh-mcp-pro:local
46
+ ```
47
+
48
+ Scanner output is evidence, not an automatic code-change mandate. Protocol-required
49
+ cryptography, bounded configuration regexes, and short-lived build images must be reviewed
50
+ in context rather than changed solely to silence a generic rule.
@@ -0,0 +1,49 @@
1
+ # Secrets Management
2
+
3
+ Covers both how ssh-mcp-pro handles secrets at runtime, and how this repository's own
4
+ CI/CD handles secrets. No secrets, tokens, or credentials were added to the repository
5
+ by this audit — this document only records what already exists.
6
+
7
+ ## Runtime (the deployed server)
8
+
9
+ - **Bearer tokens are read from a file** (`SSH_MCP_HTTP_BEARER_TOKEN_FILE`), not passed
10
+ directly as an environment variable value, reducing exposure via process listings and
11
+ environment dumps.
12
+ - **Bearer token comparison uses `timingSafeEqual`** (documented in
13
+ `SECURITY_DECISIONS.md`), avoiding timing side-channels.
14
+ - **Connector credential providers** (`SSH_MCP_CONNECTOR_CREDENTIAL_PROVIDER=command`)
15
+ resolve credentials via an external command rather than storing them in configuration,
16
+ and reject command output containing forbidden secret-shaped fields (see
17
+ `test/unit/connector-credentials.test.ts`).
18
+ - **Audit logging redacts sensitive fields** per `SECURITY_DECISIONS.md`, within a
19
+ bounded 500-event buffer.
20
+ - **Resolved (was: known open gap):** CodeQL alert #1 (High) flagged clear-text logging
21
+ of sensitive environment data in `scripts/start-chatgpt-http.mjs` — a genuine secrets
22
+ handling defect. Fixed 2026-07-03 by deriving the logged values from already-validated
23
+ enum checks instead of interpolating environment-sourced strings directly; see
24
+ [docs/repo-maturity-report.md](../repo-maturity-report.md) for the audit trail.
25
+
26
+ ## CI/CD
27
+
28
+ - **No long-lived npm token.** The release workflow explicitly `unset`s
29
+ `NODE_AUTH_TOKEN`/`NPM_CONFIG_USERCONFIG` before publishing and relies on
30
+ `id-token: write` + `npm publish --provenance` — npm's OIDC trusted-publishing flow,
31
+ not a stored secret. See [docs/security/release-integrity.md](release-integrity.md).
32
+ - **No Docker registry password stored** — GHCR login uses `${{ github.token }}`
33
+ (the automatically-provisioned, workflow-scoped token), not a personal access token.
34
+ - **This audit's own `gitleaks.yml` addition uses `${{ secrets.GITHUB_TOKEN }}`** —
35
+ the same automatic token, required by `gitleaks-action` v3 to scan pull requests. No
36
+ new secret was created or requested.
37
+ - **GitHub secret scanning + push protection are enabled** at the repository level
38
+ (confirmed via API), catching accidental secret commits before they're even pushed.
39
+ - **`.env.example` exists** and should be the only `.env*`-shaped file ever committed;
40
+ `.gitignore` is expected to exclude real `.env` files (verify locally if adding new
41
+ env-derived tooling).
42
+
43
+ ## If you're adding a new CI step that needs credentials
44
+
45
+ Before adding any token: check whether OIDC/trusted-publishing is available for the
46
+ target platform first (npm and PyPI both support it; GHCR uses the automatic
47
+ `GITHUB_TOKEN`). Long-lived tokens should be a last resort, not a default — see the
48
+ constraints this audit operated under in
49
+ [docs/repo-maturity-report.md](../repo-maturity-report.md).
@@ -0,0 +1,65 @@
1
+ # Threat Model
2
+
3
+ This document states the threat model implied by the defaults already documented in
4
+ [SECURITY_DECISIONS.md](../../SECURITY_DECISIONS.md) and the README's
5
+ [Security Defaults](../../README.md#security-defaults) section, gathered into one place.
6
+ It does not introduce new claims about the implementation beyond what those documents
7
+ already assert.
8
+
9
+ ## What ssh-mcp-pro is
10
+
11
+ An MCP server that gives an LLM-driven client a curated set of tools for opening SSH
12
+ sessions and running commands, transferring files, managing services, and creating
13
+ tunnels against remote hosts, mediated by a policy layer.
14
+
15
+ ## Primary actors
16
+
17
+ | Actor | Trust level | Notes |
18
+ | --- | --- | --- |
19
+ | The MCP client (e.g. an LLM agent) | Partially trusted | Can issue any tool call the active [tool profile](../../README.md#tool-profiles) exposes; instructions reaching it may be ambiguous or, in the worst case, adversarial (prompt injection via tool output, a compromised upstream, etc.) |
20
+ | The operator/deployer | Trusted | Configures policy, environment variables, and which hosts are reachable |
21
+ | The remote SSH host | Partially trusted | Assumed to be a host the operator intends to manage, but output from it (command results, file contents) is untrusted data flowing back into the MCP session |
22
+ | A remote connector caller (HTTP transport) | Least trusted | Reaches the server over a network boundary; see the non-loopback HTTP restrictions below |
23
+
24
+ ## What the defaults are actually defending against
25
+
26
+ 1. **An LLM client instructed (by a user, a compromised prompt, or a malicious tool
27
+ response) to do something destructive.** This is why destructive commands and
28
+ filesystem operations (`fs_rmrf`, etc.) are denied by default
29
+ (`SSH_MCP_ALLOW_DESTRUCTIVE_COMMANDS`, `SSH_MCP_ALLOW_DESTRUCTIVE_FS`), and why root
30
+ login and raw `sudo` are denied by default.
31
+ 2. **Host impersonation / MITM against a managed host.** This is why
32
+ `SSH_MCP_HOST_KEY_POLICY` defaults to `strict` against a known-hosts file rather than
33
+ trusting on first use.
34
+ 3. **Accidental exposure of the server itself to the network.** This is why non-loopback
35
+ HTTP startup requires an explicit combination of bearer/OAuth auth, an allowed
36
+ origins list, a public HTTPS URL, strict host-key verification, a remote-safe tool
37
+ profile, and a host allowlist — the server refuses to start non-loopback without all
38
+ of these, rather than silently binding wide open.
39
+ 4. **Overexposure to untrusted/broad connector audiences.** This is why tool profiles
40
+ (`remote-safe`, `chatgpt`, `claude`, `remote-readonly`, `remote-broker`) are
41
+ allowlists of a small tool subset, not the full `full` profile, for anything meant to
42
+ be reachable by a broader audience than a trusted local stdio client.
43
+ 5. **Credential/secret leakage through logs or audit trails.** `SECURITY_DECISIONS.md`
44
+ documents audit redaction patterns and a bounded (500-event) audit buffer, and
45
+ constant-time (`timingSafeEqual`) comparison for bearer tokens.
46
+
47
+ ## Resolved gap (was: known open gap)
48
+
49
+ CodeQL previously had 7 open alerts, including one High severity finding
50
+ (`js/clear-text-logging` in `scripts/start-chatgpt-http.mjs`) that was exactly the kind
51
+ of secret-leakage-through-logs risk this threat model calls out. All 8 were resolved
52
+ 2026-07-03 (1 fixed in code, 7 dismissed with re-verified rationale) — see
53
+ [docs/repo-maturity-report.md](../repo-maturity-report.md) for the audit trail. Separately,
54
+ adding Trivy container-image scanning the same day surfaced 50+ new findings against the
55
+ built Docker image (mostly the base image's bundled tooling, not this project's own
56
+ code) — see "Package publishing maturity" in that same report. That backlog is
57
+ untriaged as of this update and is the current live example of "detection isn't the
58
+ same as resolution."
59
+
60
+ ## Out of scope (matches SECURITY.md)
61
+
62
+ - Vulnerabilities in dependencies that aren't reachable through this project's own
63
+ attack surface.
64
+ - Findings that require the operator to have already disabled a secure default.
65
+ - Denial-of-service against a fully trusted local development environment.
package/docs/testing.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Testing
2
2
 
3
+ ## HTTP boundary testing
4
+
5
+ `test/integration/http-server-boundary.integration.test.ts` starts the real HTTP
6
+ MCP runtime in-process on an ephemeral loopback port. It uses temporary bearer
7
+ files, an injected clock, and deterministic remote-control-plane doubles; it
8
+ does not require external OAuth, JWKS, SSH, or public-network services.
9
+
10
+ The suite exercises the production request boundary rather than only helper
11
+ functions. Coverage includes bearer and OAuth challenges, origin and CORS
12
+ handling, request-size limits, rate-limit headers, protected-resource metadata,
13
+ MCP session initialization, capacity eviction, idle expiry, legacy SSE,
14
+ remote HTTP and WebSocket delegation, startup refusal, and idempotent shutdown.
15
+ `test/unit/http-server-lifecycle.test.ts` separately locks lifecycle concurrency,
16
+ bind failures, signal cleanup, and one-shot close behavior.
17
+
18
+ Run the boundary suite with:
19
+
20
+ ```bash
21
+ pnpm run test:integration -- test/integration/http-server-boundary.integration.test.ts
22
+ ```
23
+
24
+ The HTTP runtime is included in the normal coverage denominator. Do not replace
25
+ these tests with a coverage exclusion when adding routes or lifecycle branches.
26
+
3
27
  ## Mutation testing
4
28
 
5
29
  Mutation testing is configured for policy-critical security surfaces only. The
@@ -0,0 +1,56 @@
1
+ # Troubleshooting
2
+
3
+ Common failure modes and where to look. For option-by-option config, see
4
+ [docs/reference/configuration.md](reference/configuration.md).
5
+
6
+ ## The server starts but no tools show up in my MCP client
7
+
8
+ Check `SSH_MCP_TOOL_PROFILE` (or `--tool-profile`). Every profile except `full` is an
9
+ allowlist — see [Tool Profiles](../README.md#tool-profiles). If you expected the full
10
+ tool set and see only a handful, you're likely on `remote-safe` or another restricted
11
+ profile.
12
+
13
+ ## `EHOSTKEY` / host key verification failures
14
+
15
+ This is `SSH_MCP_HOST_KEY_POLICY=strict` (the default) working as intended: the host
16
+ isn't in `SSH_MCP_KNOWN_HOSTS_PATH` yet, or its key changed. Add the host's key to your
17
+ known_hosts file first (e.g. `ssh-keyscan` then verify out-of-band), or see
18
+ [docs/how-to/configure-ssh-policy.md](how-to/configure-ssh-policy.md) for
19
+ lab-environment alternatives. Do not switch to `insecure` against a host you don't fully
20
+ trust.
21
+
22
+ ## A tool call is denied by policy
23
+
24
+ Run `ssh_policy_explain` (or ask your MCP client to "explain the active SSH policy")
25
+ before assuming something is broken — most denials are the deny-by-default security
26
+ posture described in [SECURITY_DECISIONS.md](../SECURITY_DECISIONS.md), not a bug. See
27
+ [docs/how-to/configure-ssh-policy.md](how-to/configure-ssh-policy.md) to loosen a
28
+ specific restriction deliberately.
29
+
30
+ ## Non-loopback HTTP transport won't start
31
+
32
+ By design — see [Security Defaults](../README.md#security-defaults). Non-loopback
33
+ startup requires bearer/OAuth auth, an allowed origins list, a public HTTPS URL, strict
34
+ host-key verification, a remote-safe tool profile, and a host allowlist, all configured
35
+ together. Check the startup error message; it names which of these is missing.
36
+
37
+ ## Debugging a session
38
+
39
+ Set `SSH_MCP_DEBUG=true` for more verbose configuration-time diagnostics. For
40
+ request/response-level detail, check whether OpenTelemetry export is configured (see the
41
+ `@opentelemetry/*` dependencies and the telemetry-related environment variables) — this
42
+ project ships OTLP trace export support, not just console logging.
43
+
44
+ ## Windows-specific SSH integration issues
45
+
46
+ There's a dedicated Windows SSH integration test project
47
+ (`pnpm run test:integration:windows`) — if something works on Linux but not Windows,
48
+ check that test file first; it's the closest thing to a regression harness for
49
+ Windows-specific SSH behavior.
50
+
51
+ ## Filing a bug
52
+
53
+ If none of the above resolves it, open an issue using the bug report template — see
54
+ [SUPPORT.md](../SUPPORT.md) for response expectations. Include the output of
55
+ `ssh_policy_explain` and the exact `SSH_MCP_*` environment variables you've set
56
+ (redacting any secrets).
@@ -0,0 +1,84 @@
1
+ # Tutorial: Getting Started
2
+
3
+ This is a learning-oriented walkthrough: follow it top to bottom and you'll end with a
4
+ working local ssh-mcp-pro connected to an MCP client and one successful remote command.
5
+ Per [Diátaxis](https://diataxis.fr/tutorials/), it favors one guaranteed-to-work path
6
+ over covering every option — see [How-to guides](../how-to/README.md) for other tasks
7
+ and [Reference](../reference/README.md) for the full option set.
8
+
9
+ ## 1. Prerequisites
10
+
11
+ You need:
12
+
13
+ - Node.js `>=22.22.2` (or `>=24.15.0` / `>=26.3.0`) and pnpm `>=11.0.9`.
14
+ - At least one host you can already reach with plain `ssh`, with its key already in
15
+ your `~/.ssh/known_hosts` (this tutorial uses the default `strict` host-key policy —
16
+ see [SECURITY_DECISIONS.md](../../SECURITY_DECISIONS.md) for why).
17
+
18
+ ## 2. Install
19
+
20
+ ```bash
21
+ pnpm add --global ssh-mcp-pro
22
+ ssh-mcp-pro --version
23
+ ```
24
+
25
+ If that prints a version number, installation worked.
26
+
27
+ ## 3. Register it with an MCP client
28
+
29
+ Pick the block matching your client and add it to that client's MCP config (see
30
+ [INSTALL.md](../../INSTALL.md) for exact file locations per client):
31
+
32
+ ```json
33
+ {
34
+ "mcpServers": {
35
+ "ssh-mcp-pro": {
36
+ "command": "ssh-mcp-pro",
37
+ "args": []
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ Restart or reload your MCP client so it picks up the new server.
44
+
45
+ ## 4. Confirm the server is visible
46
+
47
+ In your MCP client, ask it to list available tools, or run:
48
+
49
+ ```text
50
+ List configured SSH hosts.
51
+ ```
52
+
53
+ You should see a response describing zero or more configured hosts — an empty list is
54
+ expected if you haven't pointed `ssh-mcp-pro` at any host config yet; it means the
55
+ server started and responded, which is the thing this step is actually checking.
56
+
57
+ ## 5. Inspect the active policy
58
+
59
+ Before running anything against a real host, see what's allowed:
60
+
61
+ ```text
62
+ Explain the active SSH policy.
63
+ ```
64
+
65
+ This reflects the deny-by-default settings described in the
66
+ [Security Defaults](../../README.md#security-defaults) section of the README: strict
67
+ host-key checking, no root login, no raw `sudo`, no destructive operations.
68
+
69
+ ## 6. Connect to a host and run a safe, read-only command
70
+
71
+ ```text
72
+ Connect to <your-host> as <your-user> with hostKeyPolicy=strict, then run os_detect.
73
+ ```
74
+
75
+ If this succeeds, you have a working end-to-end setup: MCP client → ssh-mcp-pro →
76
+ your host.
77
+
78
+ ## Next steps
79
+
80
+ - [How to configure the SSH policy](../how-to/configure-ssh-policy.md) if you need to
81
+ change any default.
82
+ - [examples/README.md](../../examples/README.md) for more workflow examples.
83
+ - [docs/explanation/architecture.md](../explanation/architecture.md) to understand why
84
+ the server is structured the way it is.
package/mcp.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "ssh-mcp-pro",
3
3
  "display_name": "ssh-mcp-pro",
4
4
  "description": "Secure MCP SSH automation server with policy controls, resources, prompts, stdio, and HTTP.",
5
- "version": "1.1.5",
5
+ "version": "1.2.1",
6
6
  "author": "Osman Aslan",
7
7
  "license": "MIT",
8
8
  "runtime": "node",
package/package.json CHANGED
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "name": "ssh-mcp-pro",
3
- "version": "1.1.5",
3
+ "version": "1.2.1",
4
4
  "mcpName": "io.github.oaslananka/ssh-mcp-pro",
5
5
  "description": "Model Context Protocol (MCP) SSH client server for remote automation",
6
6
  "type": "module",
7
- "packageManager": "pnpm@11.9.0+sha512.bd682d5d03fe525ef7c9fd6780c6884d1e756ac4c9c9fe00c538782824310dcf90e3ddc4f53835f06dfaebd5085e41855e0bcbb3b60de2ac5bbab89e5036f03b",
8
7
  "main": "dist/index.js",
9
8
  "exports": "./dist/index.js",
10
9
  "types": "dist/index.d.ts",
@@ -32,6 +31,80 @@
32
31
  "ssh-mcp-pro": "dist/index.js",
33
32
  "ssh-mcp-pro-agent": "dist/agent-bin.js"
34
33
  },
34
+ "keywords": [
35
+ "mcp",
36
+ "ssh",
37
+ "copilot",
38
+ "automation",
39
+ "remote",
40
+ "model-context-protocol",
41
+ "github-copilot",
42
+ "ssh-client",
43
+ "devops",
44
+ "infrastructure"
45
+ ],
46
+ "author": "Osman Aslan <oaslananka>",
47
+ "license": "MIT",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/oaslananka/ssh-mcp-pro.git"
51
+ },
52
+ "homepage": "https://github.com/oaslananka/ssh-mcp-pro#readme",
53
+ "bugs": {
54
+ "url": "https://github.com/oaslananka/ssh-mcp-pro/issues"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "engines": {
60
+ "node": ">=22.22.2",
61
+ "pnpm": "^11.28.5"
62
+ },
63
+ "dependencies": {
64
+ "@modelcontextprotocol/sdk": "^1.32.0",
65
+ "@opentelemetry/api": "^1.9.1",
66
+ "@opentelemetry/exporter-trace-otlp-http": "^0.222.0",
67
+ "@opentelemetry/resources": "^2.11.0",
68
+ "@opentelemetry/sdk-node": "^0.222.0",
69
+ "@opentelemetry/semantic-conventions": "^1.43.0",
70
+ "jose": "6.2.3",
71
+ "node-ssh": "^13.2.1",
72
+ "ws": "^8.21.1",
73
+ "zod": "^4.4.3"
74
+ },
75
+ "devDependencies": {
76
+ "@stryker-mutator/core": "9.6.1",
77
+ "@stryker-mutator/typescript-checker": "9.6.1",
78
+ "@stryker-mutator/vitest-runner": "9.6.1",
79
+ "@types/node": "^24.13.1",
80
+ "@types/ssh2": "^1.15.6",
81
+ "@types/ws": "^8.18.2",
82
+ "@typescript-eslint/eslint-plugin": "^8.71.0",
83
+ "@typescript-eslint/parser": "^8.71.0",
84
+ "@vitest/coverage-v8": "^4.1.11",
85
+ "ajv": "^8.20.0",
86
+ "autocannon": "^7.15.0",
87
+ "dependency-cruiser": "^17.4.3",
88
+ "eslint": "^10.12.0",
89
+ "fast-check": "^4.10.2",
90
+ "knip": "^6.39.0",
91
+ "prettier": "^3.9.9",
92
+ "publint": "^0.3.25",
93
+ "typedoc": "0.28.20",
94
+ "typescript": "^6.0.3",
95
+ "vite": "^7.3.5",
96
+ "vitest": "^4.1.11"
97
+ },
98
+ "overrides": {
99
+ "brace-expansion": "^5.0.12",
100
+ "fast-uri": "^3.1.8",
101
+ "ip-address": "^10.7.1",
102
+ "nanoid": "^3.3.18",
103
+ "browserslist": "^4.28.7",
104
+ "@hono/node-server": "^2.0.5",
105
+ "markdown-it": "^14.3.2",
106
+ "vitest": "^4.1.11"
107
+ },
35
108
  "scripts": {
36
109
  "build": "tsc -p tsconfig.json",
37
110
  "dev": "tsc -p tsconfig.json -w",
@@ -51,11 +124,11 @@
51
124
  "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"scripts/**/*.{mjs,cjs}\" \"docs/**/*.md\" \".github/workflows/*.{yml,yaml}\" \"stryker.conf.mjs\" \"vitest.config.ts\" \"vitest.mutation.config.ts\"",
52
125
  "format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\" \"scripts/**/*.{mjs,cjs}\" \"docs/**/*.md\" \".github/workflows/*.{yml,yaml}\" \"stryker.conf.mjs\" \"vitest.config.ts\" \"vitest.mutation.config.ts\"",
53
126
  "format:staged": "node scripts/run-staged-checks.mjs",
54
- "prepare": "node scripts/setup-git-hooks.mjs",
55
- "prepack": "pnpm run build && pnpm run sync-version -- --check && pnpm run validate:mcp-metadata && pnpm run validate:chatgpt-app && pnpm run validate:claude-connector",
56
- "prepublishOnly": "pnpm run check",
57
127
  "docker:ssh-fixture:up": "node scripts/docker-ssh-fixture.mjs up",
128
+ "docker:ssh-fixture:verify": "node scripts/docker-ssh-fixture.mjs verify",
129
+ "docker:ssh-fixture:diagnostics": "node scripts/docker-ssh-fixture.mjs diagnostics",
58
130
  "docker:ssh-fixture:down": "node scripts/docker-ssh-fixture.mjs down",
131
+ "check:fixture-results": "node scripts/check-fixture-test-results.mjs",
59
132
  "e2e:docker": "node scripts/docker-ssh-fixture.mjs run e2e",
60
133
  "integration:docker": "node scripts/docker-ssh-fixture.mjs run integration",
61
134
  "docs": "pnpm exec typedoc --options typedoc.json",
@@ -80,82 +153,23 @@
80
153
  "release:state": "node scripts/release-state.mjs",
81
154
  "check:npm-name": "node scripts/check-npm-name.mjs ssh-mcp-pro",
82
155
  "audit": "pnpm audit --audit-level moderate",
156
+ "audit:packed": "node scripts/check-packed-consumer-audit.mjs",
83
157
  "licenses:check": "node scripts/check-licenses.mjs",
84
158
  "reuse:lint": "reuse lint",
85
159
  "pack:check": "node scripts/check-package-contents.mjs",
86
160
  "pack:install-smoke": "node scripts/check-package-install-smoke.mjs",
161
+ "pack:lint": "publint --strict=false",
87
162
  "check:knip": "knip -c knip.jsonc --reporter compact || true",
88
163
  "check:circular": "depcruise src --config .dependency-cruiser.mjs || true",
89
164
  "check:module-size": "node scripts/check-module-size.mjs",
90
165
  "check:maintainability": "pnpm run check:module-size && pnpm run check:circular && pnpm run check:knip",
91
166
  "check:quality": "pnpm run format:check && pnpm run check:doc-language && pnpm run check:package-scripts && pnpm run check:rulesets && pnpm run verify:actions-runtime && pnpm run lint && pnpm run typecheck && pnpm run audit && pnpm run licenses:check",
92
- "check:package": "pnpm run build && pnpm run sync-version -- --check && pnpm run validate:mcp-metadata && pnpm run validate:chatgpt-app && pnpm run validate:claude-connector && pnpm run docs:check && pnpm run pack:check && pnpm run pack:install-smoke",
167
+ "check:package": "pnpm run build && pnpm run sync-version -- --check && pnpm run validate:mcp-metadata && pnpm run validate:chatgpt-app && pnpm run validate:claude-connector && pnpm run docs:check && pnpm run pack:check && pnpm run pack:install-smoke && pnpm run audit:packed",
93
168
  "check:push": "pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm test",
94
169
  "check": "pnpm run check:quality && pnpm run test:coverage && pnpm run check:package",
95
170
  "hook:pre-commit": "pnpm run format:staged",
96
171
  "hook:pre-push": "pnpm run check:push",
97
172
  "release:dry-run": "node scripts/validate-release-please.mjs && node scripts/release-state.mjs --offline --json && pnpm run check:package",
98
173
  "sbom": "node scripts/generate-sbom.mjs"
99
- },
100
- "keywords": [
101
- "mcp",
102
- "ssh",
103
- "copilot",
104
- "automation",
105
- "remote",
106
- "model-context-protocol",
107
- "github-copilot",
108
- "ssh-client",
109
- "devops",
110
- "infrastructure"
111
- ],
112
- "author": "Osman Aslan <oaslananka>",
113
- "license": "MIT",
114
- "repository": {
115
- "type": "git",
116
- "url": "git+https://github.com/oaslananka/ssh-mcp-pro.git"
117
- },
118
- "homepage": "https://github.com/oaslananka/ssh-mcp-pro#readme",
119
- "bugs": {
120
- "url": "https://github.com/oaslananka/ssh-mcp-pro/issues"
121
- },
122
- "publishConfig": {
123
- "access": "public"
124
- },
125
- "engines": {
126
- "node": ">=22.22.2",
127
- "pnpm": "^11.5.1"
128
- },
129
- "dependencies": {
130
- "@modelcontextprotocol/sdk": "^1.29.0",
131
- "@opentelemetry/api": "^1.9.1",
132
- "@opentelemetry/exporter-trace-otlp-http": "^0.219.0",
133
- "@opentelemetry/resources": "^2.8.0",
134
- "@opentelemetry/sdk-node": "^0.219.0",
135
- "@opentelemetry/semantic-conventions": "^1.41.1",
136
- "jose": "6.2.3",
137
- "node-ssh": "^13.2.1",
138
- "zod": "^4.4.3"
139
- },
140
- "devDependencies": {
141
- "@stryker-mutator/core": "9.6.1",
142
- "@stryker-mutator/typescript-checker": "9.6.1",
143
- "@stryker-mutator/vitest-runner": "9.6.1",
144
- "@types/node": "^24.13.1",
145
- "@types/ssh2": "^1.15.5",
146
- "@typescript-eslint/eslint-plugin": "^8.62.0",
147
- "@typescript-eslint/parser": "^8.62.0",
148
- "@vitest/coverage-v8": "^4.1.9",
149
- "ajv": "^8.20.0",
150
- "autocannon": "^7.15.0",
151
- "dependency-cruiser": "^17.4.3",
152
- "eslint": "^10.5.0",
153
- "fast-check": "^4.8.0",
154
- "knip": "^6.18.0",
155
- "prettier": "^3.8.4",
156
- "typedoc": "0.28.19",
157
- "typescript": "^6.0.3",
158
- "vite": "^7.3.5",
159
- "vitest": "^4.1.9"
160
174
  }
161
- }
175
+ }
@@ -2,7 +2,7 @@
2
2
  "name": "ssh-mcp-pro",
3
3
  "display_name": "ssh-mcp-pro",
4
4
  "description": "Secure MCP SSH automation server with policy controls, resources, prompts, stdio, and HTTP.",
5
- "version": "1.1.5",
5
+ "version": "1.2.1",
6
6
  "author": "Osman Aslan",
7
7
  "license": "MIT",
8
8
  "runtime": "node",