ssh-mcp-pro 1.1.4 → 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.
- package/CHANGELOG.md +29 -0
- package/README.md +58 -10
- package/REGISTRY_SUBMISSION.md +3 -3
- package/SECURITY.md +4 -0
- package/SECURITY_DECISIONS.md +32 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -0
- package/dist/config.js.map +1 -1
- package/dist/connector-profile.d.ts +3 -0
- package/dist/connector-profile.d.ts.map +1 -1
- package/dist/connector-profile.js +36 -14
- package/dist/connector-profile.js.map +1 -1
- package/dist/http-security.d.ts.map +1 -1
- package/dist/http-security.js +19 -4
- package/dist/http-security.js.map +1 -1
- package/dist/http-server-lifecycle.d.ts +17 -0
- package/dist/http-server-lifecycle.d.ts.map +1 -0
- package/dist/http-server-lifecycle.js +122 -0
- package/dist/http-server-lifecycle.js.map +1 -0
- package/dist/http-server-router.d.ts +28 -0
- package/dist/http-server-router.d.ts.map +1 -0
- package/dist/http-server-router.js +108 -0
- package/dist/http-server-router.js.map +1 -0
- package/dist/http-server-runtime.d.ts +21 -0
- package/dist/http-server-runtime.d.ts.map +1 -0
- package/dist/http-server-runtime.js +415 -0
- package/dist/http-server-runtime.js.map +1 -0
- package/dist/http-session-registry.d.ts +35 -0
- package/dist/http-session-registry.d.ts.map +1 -0
- package/dist/http-session-registry.js +96 -0
- package/dist/http-session-registry.js.map +1 -0
- package/dist/index.js +12 -5
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +8 -1
- package/dist/prompts.js.map +1 -1
- package/dist/remote/agent-cli.d.ts.map +1 -1
- package/dist/remote/agent-cli.js +20 -1
- package/dist/remote/agent-cli.js.map +1 -1
- package/dist/remote/agent-connection-lifecycle.d.ts +47 -0
- package/dist/remote/agent-connection-lifecycle.d.ts.map +1 -0
- package/dist/remote/agent-connection-lifecycle.js +304 -0
- package/dist/remote/agent-connection-lifecycle.js.map +1 -0
- package/dist/remote/agent-executor.d.ts.map +1 -1
- package/dist/remote/agent-executor.js +93 -68
- package/dist/remote/agent-executor.js.map +1 -1
- package/dist/remote/agent-handler.d.ts +22 -8
- package/dist/remote/agent-handler.d.ts.map +1 -1
- package/dist/remote/agent-handler.js +151 -98
- package/dist/remote/agent-handler.js.map +1 -1
- package/dist/remote/config.d.ts.map +1 -1
- package/dist/remote/config.js +14 -0
- package/dist/remote/config.js.map +1 -1
- package/dist/remote/control-plane.d.ts.map +1 -1
- package/dist/remote/control-plane.js +28 -3
- package/dist/remote/control-plane.js.map +1 -1
- package/dist/remote/oauth-handler.d.ts +2 -0
- package/dist/remote/oauth-handler.d.ts.map +1 -1
- package/dist/remote/oauth-handler.js +62 -1
- package/dist/remote/oauth-handler.js.map +1 -1
- package/dist/remote/policy-update-guard.d.ts +14 -0
- package/dist/remote/policy-update-guard.d.ts.map +1 -0
- package/dist/remote/policy-update-guard.js +27 -0
- package/dist/remote/policy-update-guard.js.map +1 -0
- package/dist/remote/policy.d.ts.map +1 -1
- package/dist/remote/policy.js +13 -2
- package/dist/remote/policy.js.map +1 -1
- package/dist/remote/safe-file.d.ts +5 -0
- package/dist/remote/safe-file.d.ts.map +1 -0
- package/dist/remote/safe-file.js +194 -0
- package/dist/remote/safe-file.js.map +1 -0
- package/dist/remote/store.d.ts +1 -0
- package/dist/remote/store.d.ts.map +1 -1
- package/dist/remote/store.js +5 -0
- package/dist/remote/store.js.map +1 -1
- package/dist/remote/types.d.ts +5 -0
- package/dist/remote/types.d.ts.map +1 -1
- package/dist/remote/websocket.d.ts +38 -13
- package/dist/remote/websocket.d.ts.map +1 -1
- package/dist/remote/websocket.js +90 -126
- package/dist/remote/websocket.js.map +1 -1
- package/dist/render-http.js +2 -1
- package/dist/render-http.js.map +1 -1
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +4 -2
- package/dist/retry.js.map +1 -1
- package/dist/server-http.d.ts +1 -1
- package/dist/server-http.d.ts.map +1 -1
- package/dist/server-http.js +1 -431
- package/dist/server-http.js.map +1 -1
- package/dist/tools/connector.provider.d.ts.map +1 -1
- package/dist/tools/connector.provider.js +5 -3
- package/dist/tools/connector.provider.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/output-schemas.d.ts.map +1 -1
- package/dist/tools/output-schemas.js +1 -0
- package/dist/tools/output-schemas.js.map +1 -1
- package/dist/tools/process.provider.d.ts +2 -0
- package/dist/tools/process.provider.d.ts.map +1 -1
- package/dist/tools/process.provider.js +60 -1
- package/dist/tools/process.provider.js.map +1 -1
- package/dist/tools/system.provider.d.ts +13 -0
- package/dist/tools/system.provider.d.ts.map +1 -1
- package/dist/tools/system.provider.js +304 -2
- package/dist/tools/system.provider.js.map +1 -1
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +34 -0
- package/dist/types.js.map +1 -1
- package/docs/adding-a-device.md +127 -0
- package/docs/agent-runtime-config.md +34 -0
- package/docs/development/api-stability.md +44 -0
- package/docs/development/coding-standards.md +35 -0
- package/docs/development/commit-conventions.md +35 -0
- package/docs/development/dependency-management.md +60 -0
- package/docs/development/deprecation-policy.md +29 -0
- package/docs/development/local-setup.md +46 -0
- package/docs/development/release-process.md +57 -0
- package/docs/development/testing-policy.md +47 -0
- package/docs/explanation/architecture.md +44 -0
- package/docs/how-to/README.md +14 -0
- package/docs/how-to/configure-ssh-policy.md +50 -0
- package/docs/openssf-evidence.md +75 -0
- package/docs/openssf-gap-analysis.md +63 -0
- package/docs/openssf-proposal-links.md +37 -0
- package/docs/professionalization-plan.md +81 -0
- package/docs/reference/README.md +21 -0
- package/docs/reference/cli.md +41 -0
- package/docs/reference/compatibility.md +46 -0
- package/docs/reference/configuration.md +72 -0
- package/docs/remote-mcp-hardening.md +59 -0
- package/docs/repo-maturity-report.md +565 -0
- package/docs/security/assurance-case.md +58 -0
- package/docs/security/dependency-audit-exceptions.json +4 -0
- package/docs/security/dependency-audit-exceptions.md +15 -0
- package/docs/security/input-validation.md +54 -0
- package/docs/security/release-integrity.md +28 -0
- package/docs/security/scanning-policy.md +50 -0
- package/docs/security/secrets-management.md +49 -0
- package/docs/security/threat-model.md +65 -0
- package/docs/testing.md +24 -0
- package/docs/troubleshooting.md +56 -0
- package/docs/tutorials/getting-started.md +84 -0
- package/mcp.json +1 -1
- package/package.json +37 -18
- package/registry/ssh-mcp-pro/mcp.json +1 -1
- package/server.json +3 -3
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Explanation: Architecture
|
|
2
|
+
|
|
3
|
+
This page is explanation-oriented (the "why"), per
|
|
4
|
+
[Diátaxis](https://diataxis.fr/explanation/). For the authoritative system diagram,
|
|
5
|
+
component breakdown, and ADRs, see [ARCHITECTURE.md](../../ARCHITECTURE.md) — this page
|
|
6
|
+
doesn't duplicate that content, it frames it.
|
|
7
|
+
|
|
8
|
+
## Why an MCP server for SSH, specifically
|
|
9
|
+
|
|
10
|
+
Model Context Protocol gives an LLM client a structured, typed set of tools instead of
|
|
11
|
+
free-form shell access. ssh-mcp-pro's core design bet is that "give the model SSH" is
|
|
12
|
+
unsafe as a primitive, but "give the model a curated set of SSH-backed tools, each with
|
|
13
|
+
its own policy check" is a workable middle ground between "no remote access" and
|
|
14
|
+
"unrestricted remote shell." That bet is why the codebase has a dedicated `policy`/
|
|
15
|
+
`safety` layer sitting between tool dispatch and the SSH session, rather than tools
|
|
16
|
+
calling `node-ssh` directly.
|
|
17
|
+
|
|
18
|
+
## Why defaults are deny-by-default
|
|
19
|
+
|
|
20
|
+
Every default documented in [SECURITY_DECISIONS.md](../../SECURITY_DECISIONS.md) —
|
|
21
|
+
strict host-key verification, no root login, no raw `sudo`, no destructive commands or
|
|
22
|
+
filesystem operations — exists because the honest threat model for this project is "an
|
|
23
|
+
LLM-driven client with SSH credentials, potentially acting on ambiguous or adversarial
|
|
24
|
+
instructions." See [docs/security/threat-model.md](../security/threat-model.md) for the
|
|
25
|
+
full reasoning. Architecturally, this means the safety checks live close to the tool
|
|
26
|
+
registry (so no tool can bypass them by construction), not scattered per-tool.
|
|
27
|
+
|
|
28
|
+
## Why there's a separate "remote control plane"
|
|
29
|
+
|
|
30
|
+
`src/remote/*` and the `ssh-mcp-pro-agent` binary exist because not every deployment
|
|
31
|
+
wants the MCP server itself reachable from the public internet. The control plane lets
|
|
32
|
+
an operator run a broker that mediates access, so the SSH-capable server can stay on a
|
|
33
|
+
private network while remote clients (ChatGPT connectors, Claude connectors) talk to the
|
|
34
|
+
broker instead. See [docs/remote-mcp-hardening.md](../remote-mcp-hardening.md) for the
|
|
35
|
+
operational guide and [ARCHITECTURE.md](../../ARCHITECTURE.md) for where this sits in
|
|
36
|
+
the request flow.
|
|
37
|
+
|
|
38
|
+
## Why tool profiles exist
|
|
39
|
+
|
|
40
|
+
`SSH_MCP_TOOL_PROFILE` (`full`, `remote-safe`, `chatgpt`, `claude`,
|
|
41
|
+
`remote-readonly`, `remote-broker`) exists because "expose every tool" is the right
|
|
42
|
+
default for a trusted local stdio client, but the wrong default for a remote connector
|
|
43
|
+
serving a broader audience. Profiles are an allowlist, not a denylist, so adding a new
|
|
44
|
+
profile can never accidentally widen an existing one's exposure.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# How-to Guides
|
|
2
|
+
|
|
3
|
+
How-to guides are goal-oriented: they assume you already know the basics (see the
|
|
4
|
+
[tutorial](../tutorials/getting-started.md) if not) and get you to a specific outcome.
|
|
5
|
+
Organized per the [Diátaxis](https://diataxis.fr/how-to-guides/) model.
|
|
6
|
+
|
|
7
|
+
- [How to run ssh-mcp-pro with Docker](../docker.md)
|
|
8
|
+
- [How to onboard a remote device / agent](../adding-a-device.md)
|
|
9
|
+
- [How to harden a public/remote MCP deployment](../remote-mcp-hardening.md)
|
|
10
|
+
- [How to set the SSH host-key and policy defaults](configure-ssh-policy.md)
|
|
11
|
+
|
|
12
|
+
If something's actually broken rather than a task you're trying to accomplish, see
|
|
13
|
+
[Troubleshooting](../troubleshooting.md) instead. If you're looking for exhaustive
|
|
14
|
+
option listings instead of a task, see [Reference](../reference/README.md).
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# How to configure the SSH policy
|
|
2
|
+
|
|
3
|
+
This guide walks through the most common policy adjustments. Every setting here is one
|
|
4
|
+
row in the [environment variable reference](../../README.md#configuration) — this guide
|
|
5
|
+
just groups them by task.
|
|
6
|
+
|
|
7
|
+
## Loosen host-key checking for a lab environment
|
|
8
|
+
|
|
9
|
+
By default `SSH_MCP_HOST_KEY_POLICY=strict`, which requires every host to already be in
|
|
10
|
+
`SSH_MCP_KNOWN_HOSTS_PATH`. For a throwaway lab environment where you accept the risk:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
export SSH_MCP_HOST_KEY_POLICY=accept-new
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Do not set this to `insecure` outside of a fully isolated test environment — see
|
|
17
|
+
[SECURITY_DECISIONS.md](../../SECURITY_DECISIONS.md) for why `strict` is the default.
|
|
18
|
+
|
|
19
|
+
## Allow a specific destructive operation
|
|
20
|
+
|
|
21
|
+
Destructive commands and filesystem operations (like `fs_rmrf`) are denied by default.
|
|
22
|
+
To allow them for a specific deployment:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
export SSH_MCP_ALLOW_DESTRUCTIVE_COMMANDS=true
|
|
26
|
+
export SSH_MCP_ALLOW_DESTRUCTIVE_FS=true
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Prefer narrowing scope with `SSH_MCP_COMMAND_ALLOW`/`SSH_MCP_COMMAND_DENY` and
|
|
30
|
+
`SSH_MCP_PATH_ALLOW_PREFIXES`/`SSH_MCP_PATH_DENY_PREFIXES` instead of a blanket allow,
|
|
31
|
+
where possible.
|
|
32
|
+
|
|
33
|
+
## Restrict which hosts can be targeted at all
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
export SSH_MCP_ALLOWED_HOSTS="bastion.example.com,10.0.0.0/8"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Use a policy file instead of many environment variables
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
export SSH_MCP_POLICY_FILE=/etc/ssh-mcp-pro/policy.json
|
|
43
|
+
export SSH_MCP_POLICY_MODE=enforce
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Set `SSH_MCP_POLICY_MODE=explain` first in a non-production environment to see what the
|
|
47
|
+
policy *would* block without actually enforcing it, before flipping to `enforce`.
|
|
48
|
+
|
|
49
|
+
See [docs/security/input-validation.md](../security/input-validation.md) for how these
|
|
50
|
+
allow/deny lists are evaluated.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# OpenSSF Best Practices — Evidence
|
|
2
|
+
|
|
3
|
+
Self-assessment against the [OpenSSF Best Practices Passing criteria](https://www.bestpractices.dev/en/criteria/0),
|
|
4
|
+
current as of 2026-07-03. This is a repo-side evidence log, not an official OpenSSF
|
|
5
|
+
artifact — the actual badge is issued by submitting these answers at
|
|
6
|
+
[bestpractices.dev](https://www.bestpractices.dev/). See `.bestpractices.json` for the
|
|
7
|
+
same answers in a structured tracking form used to prepare that submission.
|
|
8
|
+
|
|
9
|
+
Status legend: `Met` / `Partially met` / `Unmet` / `N/A`, matching the badge site's own
|
|
10
|
+
vocabulary.
|
|
11
|
+
|
|
12
|
+
## Basics
|
|
13
|
+
|
|
14
|
+
| Criterion | Status | Evidence |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| Project has a public repository with a version-controlled history | Met | github.com/oaslananka/ssh-mcp-pro |
|
|
17
|
+
| Project uses a common distributed version control system (Git) | Met | Git |
|
|
18
|
+
| Project has FLOSS license | Met | `LICENSE` (MIT), `REUSE.toml` |
|
|
19
|
+
| License is OSI-approved | Met | MIT |
|
|
20
|
+
| Documentation: basic description of purpose | Met | README.md intro |
|
|
21
|
+
| Documentation: how to obtain/provide feedback and contribute | Met | CONTRIBUTING.md, SUPPORT.md |
|
|
22
|
+
| Interim versions/releases are identifiable | Met | Conventional Commits + release-please tagging (pending first tag — see gap analysis) |
|
|
23
|
+
|
|
24
|
+
## Change control
|
|
25
|
+
|
|
26
|
+
| Criterion | Status | Evidence |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| Public version-controlled source repository | Met | GitHub |
|
|
29
|
+
| Repository tracks changes with commit-level granularity | Met | Git history |
|
|
30
|
+
| Releases have version numbers following a documented convention | Met | SemVer via release-please |
|
|
31
|
+
| Project has release notes for each new major/minor release | Partially met | `CHANGELOG.md` follows Keep a Changelog; no release has actually been cut yet to verify end-to-end |
|
|
32
|
+
|
|
33
|
+
## Reporting
|
|
34
|
+
|
|
35
|
+
| Criterion | Status | Evidence |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| Project has a process for reporting bugs | Met | Issue templates, `.github/ISSUE_TEMPLATE/config.yml` |
|
|
38
|
+
| Project has a documented vulnerability reporting process | Met | `SECURITY.md` — private advisories, 7-day SLA |
|
|
39
|
+
| Project acknowledges vulnerability reports within a reasonable time | Unmet (no data) | No vulnerability report has been received yet to measure against the documented SLA |
|
|
40
|
+
|
|
41
|
+
## Quality
|
|
42
|
+
|
|
43
|
+
| Criterion | Status | Evidence |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| Project uses at least one automated test suite | Met | Vitest: unit, integration, e2e, perf projects |
|
|
46
|
+
| Project has FLOSS automated test suite invoked on all commits/PRs | Met | `ci.yml` |
|
|
47
|
+
| New functionality has accompanying tests (documented expectation) | Met | `CONTRIBUTING.md` PR checklist requires it |
|
|
48
|
+
| Project has a general policy for test coverage | Met | `vitest.config.ts` enforces 85–90% thresholds |
|
|
49
|
+
| Project uses continuous integration | Met | `ci.yml` on push/PR/schedule |
|
|
50
|
+
| Project has at least one static analysis tool applied | Met | CodeQL, ESLint, mutation testing (Stryker) |
|
|
51
|
+
| All medium/high-severity static analysis findings are addressed | Met (2026-07-03) | All 8 CodeQL alerts closed: 1 fixed in code, 7 dismissed with rationale re-verified against current code — see `SECURITY_DECISIONS.md`. (Separately, Trivy container-image scanning added the same day found 50+ new findings against the built Docker image, mostly in the base image's bundled tooling rather than this project's source — tracked as an open item in `docs/repo-maturity-report.md`, not part of this specific criterion, which concerns source static analysis) |
|
|
52
|
+
|
|
53
|
+
## Security
|
|
54
|
+
|
|
55
|
+
| Criterion | Status | Evidence |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| Project has a documented security architecture / defaults | Met | SECURITY_DECISIONS.md, README "Security Defaults" |
|
|
58
|
+
| Cryptographic functions use public, well-reviewed protocols | Met | `jose` for JWT/OAuth verification (per `package.json` deps); no bespoke crypto identified |
|
|
59
|
+
| Secrets are not stored in the repository | Met (best-effort) | GitHub secret scanning + push protection enabled; `gitleaks.yml` added by this PR for defense-in-depth history scanning |
|
|
60
|
+
| Delivery mechanisms protect against MITM | Met | npm publish over registry.npmjs.org HTTPS with npm provenance; container images pinned by digest per `docs/docker.md` |
|
|
61
|
+
|
|
62
|
+
## Analysis
|
|
63
|
+
|
|
64
|
+
| Criterion | Status | Evidence |
|
|
65
|
+
| --- | --- | --- |
|
|
66
|
+
| Static analysis applied to any language used | Met | CodeQL (JS/TS query pack) |
|
|
67
|
+
| Dynamic analysis applied | Partially met | Mutation testing (Stryker) exercises runtime behavior of security-critical code paths; no fuzzing harness exists, and none is currently believed necessary (see repo-maturity-report.md, "Fuzzing: Not applicable") |
|
|
68
|
+
|
|
69
|
+
## Bottom line
|
|
70
|
+
|
|
71
|
+
As of 2026-07-03, the CodeQL blocker has been resolved. The remaining blocker is
|
|
72
|
+
"cut a first release," already tracked in [ROADMAP.md](../ROADMAP.md), plus triaging
|
|
73
|
+
the new Trivy container-image findings noted above. Nothing in this evidence log claims
|
|
74
|
+
Silver or Gold; see [docs/openssf-gap-analysis.md](openssf-gap-analysis.md) for why
|
|
75
|
+
those are out of reach right now.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# OpenSSF Gap Analysis
|
|
2
|
+
|
|
3
|
+
This document separates what's achievable now (Passing) from what genuinely requires
|
|
4
|
+
organizational change (Silver, Gold), so that neither this audit nor future readers
|
|
5
|
+
overclaim.
|
|
6
|
+
|
|
7
|
+
## Passing — achievable now
|
|
8
|
+
|
|
9
|
+
See [docs/openssf-evidence.md](openssf-evidence.md) for the full criterion table.
|
|
10
|
+
|
|
11
|
+
~~Seven open CodeQL alerts (one High) with no fully-current documented disposition.~~
|
|
12
|
+
**Resolved 2026-07-03** — see `SECURITY_DECISIONS.md`.
|
|
13
|
+
|
|
14
|
+
The remaining concrete blocker: no release has been cut yet, so "release notes exist
|
|
15
|
+
for each release" is unverified in practice even though the mechanism (release-please +
|
|
16
|
+
CHANGELOG) is implemented. Tracked in [ROADMAP.md](../ROADMAP.md) and as a recommended
|
|
17
|
+
issue in [docs/repo-maturity-report.md](repo-maturity-report.md).
|
|
18
|
+
|
|
19
|
+
A new, unrelated item surfaced the same day: Trivy container-image scanning (added
|
|
20
|
+
after this document was first written) found 50+ open findings against the built
|
|
21
|
+
Docker image — see "Package publishing maturity" in
|
|
22
|
+
[docs/repo-maturity-report.md](repo-maturity-report.md). This doesn't block Passing
|
|
23
|
+
(that criterion concerns source static analysis, not container base-image CVEs) but is
|
|
24
|
+
worth tracking as its own item.
|
|
25
|
+
|
|
26
|
+
## Silver — not yet achievable
|
|
27
|
+
|
|
28
|
+
OpenSSF Best Practices Silver adds requirements this project cannot honestly claim yet,
|
|
29
|
+
specifically:
|
|
30
|
+
|
|
31
|
+
- **Two or more unassociated significant contributors.** Current bus factor is 1
|
|
32
|
+
(single collaborator, `oaslananka`). Silver requires evidence of contribution from
|
|
33
|
+
people not all affiliated with the same entity.
|
|
34
|
+
- **A documented process for reviewing and responding to bug reports within a
|
|
35
|
+
well-defined window, evidenced by actual history.** The process is documented
|
|
36
|
+
(`SECURITY.md`, `SUPPORT.md`), but there is no report history yet to evidence it.
|
|
37
|
+
- **Basic floor for cryptographic practices is fully documented** (partially met —
|
|
38
|
+
`SECURITY_DECISIONS.md` covers several defaults but doesn't exhaustively enumerate
|
|
39
|
+
every cryptographic operation in the codebase).
|
|
40
|
+
|
|
41
|
+
**What would need to change:** a second active contributor with merged, substantive
|
|
42
|
+
PRs; at least one real vulnerability report handled end-to-end; and a pass over
|
|
43
|
+
`SECURITY_DECISIONS.md` to make the cryptographic inventory exhaustive.
|
|
44
|
+
|
|
45
|
+
## Gold — explicitly out of scope for a claim today
|
|
46
|
+
|
|
47
|
+
Per the audit's own instructions, Gold is only evaluated as a gap list when its
|
|
48
|
+
preconditions are absent — and they are absent here:
|
|
49
|
+
|
|
50
|
+
| Gold precondition | Current state |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| Multiple active maintainers | 1 (see MAINTAINERS.md) |
|
|
53
|
+
| Independent contributor/reviewer | 0 non-bot PRs have ever been opened |
|
|
54
|
+
| Regular human PR review | 0 human-reviewed PRs to date |
|
|
55
|
+
| Branch protection | Not applied on GitHub (ruleset JSON exists but unapplied) |
|
|
56
|
+
| Sustainable governance | Solo-maintainer model, honestly documented in GOVERNANCE.md, not yet a multi-party governance structure |
|
|
57
|
+
| High test coverage sustained over time | Coverage thresholds exist (85–90%) but "sustained over time" needs release/version history that doesn't exist yet (repo is 5 days old) |
|
|
58
|
+
| Repeatable/reproducible release process | Implemented (SBOM, checksums, provenance) but never executed — zero releases published |
|
|
59
|
+
|
|
60
|
+
**This is a gap list, not a target with a date.** None of these should be worked toward
|
|
61
|
+
by editing files; they require either time (release/contribution history accruing
|
|
62
|
+
naturally) or a maintainer decision (branch protection, recruiting a co-maintainer).
|
|
63
|
+
Revisit this analysis after the "Silver" preconditions above are met.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# OpenSSF Proposal Links
|
|
2
|
+
|
|
3
|
+
Reference links for acting on the findings in
|
|
4
|
+
[docs/repo-maturity-report.md](repo-maturity-report.md) and
|
|
5
|
+
[docs/openssf-gap-analysis.md](openssf-gap-analysis.md). These are pointers to where a
|
|
6
|
+
maintainer takes the next action, not automated submissions — no badge or scorecard
|
|
7
|
+
submission has been made on the project's behalf by this audit.
|
|
8
|
+
|
|
9
|
+
## OpenSSF Best Practices badge
|
|
10
|
+
|
|
11
|
+
- Self-assessment form (submit once the CodeQL/first-release gaps in
|
|
12
|
+
`docs/openssf-gap-analysis.md` close): <https://www.bestpractices.dev/en/projects/new>
|
|
13
|
+
- Full criteria reference used for this audit: <https://www.bestpractices.dev/en/criteria/0>
|
|
14
|
+
- `.bestpractices.json` in the repo root tracks current self-assessment answers to make
|
|
15
|
+
filling out the form above mechanical.
|
|
16
|
+
|
|
17
|
+
## OpenSSF Scorecard
|
|
18
|
+
|
|
19
|
+
- Public checks reference: <https://github.com/ossf/scorecard/blob/main/docs/checks.md>
|
|
20
|
+
- Scorecard viewer for this repo (once `publish_results` is re-enabled or a manual run
|
|
21
|
+
is published): <https://securityscorecards.dev/viewer/?uri=github.com/oaslananka/ssh-mcp-pro>
|
|
22
|
+
- `ossf/scorecard-action` documentation for the `publish_results` requirements that are
|
|
23
|
+
currently blocking automatic publication: <https://github.com/ossf/scorecard-action#authenticated-workflow>
|
|
24
|
+
|
|
25
|
+
## GitHub repository configuration referenced by this audit
|
|
26
|
+
|
|
27
|
+
- Branch protection settings: `https://github.com/oaslananka/ssh-mcp-pro/settings/branches`
|
|
28
|
+
- Rulesets (to import `.github/rulesets/main-protection.json`):
|
|
29
|
+
`https://github.com/oaslananka/ssh-mcp-pro/settings/rules`
|
|
30
|
+
- Security & analysis settings (private vulnerability reporting, Dependabot security
|
|
31
|
+
updates): `https://github.com/oaslananka/ssh-mcp-pro/settings/security_analysis`
|
|
32
|
+
- Code scanning alerts: `https://github.com/oaslananka/ssh-mcp-pro/security/code-scanning`
|
|
33
|
+
|
|
34
|
+
## CHAOSS
|
|
35
|
+
|
|
36
|
+
- Metrics reference used for the Community maturity section:
|
|
37
|
+
<https://chaoss.community/metrics/>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Professionalization Plan
|
|
2
|
+
|
|
3
|
+
A phased plan synthesizing [docs/repo-maturity-report.md](repo-maturity-report.md)'s
|
|
4
|
+
"Safe refactor opportunities," "High-risk refactor opportunities," and "Recommended
|
|
5
|
+
issues" into an ordered sequence. This is a plan, not a commitment with dates — see
|
|
6
|
+
[ROADMAP.md](../ROADMAP.md) for the roadmap framing of the same underlying gaps.
|
|
7
|
+
|
|
8
|
+
## Phase 0 — Done (this audit)
|
|
9
|
+
|
|
10
|
+
Documentation, governance, and evidence gaps that required no maintainer decision and
|
|
11
|
+
no behavior change: `CODE_OF_CONDUCT.md`, `GOVERNANCE.md`, `MAINTAINERS.md`,
|
|
12
|
+
`ROADMAP.md`, OpenSSF evidence/gap-analysis docs, Diátaxis documentation structure,
|
|
13
|
+
development-process docs, security docs, and the `gitleaks.yml` workflow.
|
|
14
|
+
|
|
15
|
+
## Phase 1 — Maintainer decisions, no code change
|
|
16
|
+
|
|
17
|
+
Status as of 2026-07-03:
|
|
18
|
+
|
|
19
|
+
1. Apply branch protection on `main` (import the existing ruleset or configure
|
|
20
|
+
equivalently). **Still open.**
|
|
21
|
+
2. ~~Confirm private vulnerability reporting is enabled.~~ **Done** — confirmed
|
|
22
|
+
already enabled via API.
|
|
23
|
+
3. ~~Decide on the Dependabot security-updates toggle.~~ **Done** — enabled, along
|
|
24
|
+
with Dependabot vulnerability alerts.
|
|
25
|
+
4. Merge or re-trigger release-please PR #1 to ship the first release. **Still open**
|
|
26
|
+
— deliberately left for the maintainer; triggers a real publish action.
|
|
27
|
+
5. Register the repo/workflow as an npm trusted publisher on npmjs.com (the workflow
|
|
28
|
+
side is already implemented; this is the registry-side counterpart). **Still open**
|
|
29
|
+
— an npmjs.com-side action outside the repo.
|
|
30
|
+
|
|
31
|
+
**Why this order:** branch protection and the first release are the two remaining
|
|
32
|
+
changes that would flip the most `Missing`/`Partial` rows in the maturity report to
|
|
33
|
+
`Passed` — they have outsized leverage relative to effort.
|
|
34
|
+
|
|
35
|
+
## Phase 2 — Low-risk CI additions
|
|
36
|
+
|
|
37
|
+
Status as of 2026-07-03:
|
|
38
|
+
|
|
39
|
+
1. ~~Container image vulnerability scanning (Trivy or Grype) in `docker.yml`.~~
|
|
40
|
+
**Done** — Trivy added, advisory-only. It found real findings: see the new Phase 3
|
|
41
|
+
item below.
|
|
42
|
+
2. Hadolint for Dockerfile linting. **Still open.**
|
|
43
|
+
3. ~~`publint` as an advisory (non-blocking) npm packaging check.~~ **Done** — added,
|
|
44
|
+
passes cleanly against the built package.
|
|
45
|
+
|
|
46
|
+
## Phase 3 — Security follow-through
|
|
47
|
+
|
|
48
|
+
Status as of 2026-07-03:
|
|
49
|
+
|
|
50
|
+
1. ~~Fix CodeQL alert #1 (High: clear-text logging in `scripts/start-chatgpt-http.mjs`).~~
|
|
51
|
+
**Done.**
|
|
52
|
+
2. ~~Review and record disposition for the remaining 7 CodeQL alerts.~~ **Done** — all
|
|
53
|
+
dismissed via the code-scanning API with rationale re-verified against current code;
|
|
54
|
+
written up in `SECURITY_DECISIONS.md`.
|
|
55
|
+
3. Re-evaluate Scorecard `publish_results: false` — either fix the blocking
|
|
56
|
+
`env:`/`defaults:` patterns across workflows, or adjust the README badge. **Still
|
|
57
|
+
open** — investigated; relocating those blocks across all 7 workflows risks
|
|
58
|
+
silently breaking the Windows Integration job's shell default, which can't be
|
|
59
|
+
verified without a live Windows Actions run.
|
|
60
|
+
4. **New: triage the Trivy container-image backlog.** Adding Trivy in item 1 above
|
|
61
|
+
surfaced 50+ open findings against the built image — mostly the Alpine base image's
|
|
62
|
+
OpenSSL packages and npm/corepack's own bundled dependencies, not this project's own
|
|
63
|
+
code. Options: rebuild on a newer/slimmer base image, prune build tooling from the
|
|
64
|
+
final layer, or accept and document the current baseline explicitly.
|
|
65
|
+
|
|
66
|
+
## Phase 4 — Community growth (not schedulable)
|
|
67
|
+
|
|
68
|
+
1. Land a first externally-authored, human-reviewed pull request.
|
|
69
|
+
2. Reconsider a second maintainer once Phase 4.1 has happened more than once — see
|
|
70
|
+
[GOVERNANCE.md](../GOVERNANCE.md#path-to-adding-maintainers).
|
|
71
|
+
3. Revisit OpenSSF Silver readiness once Phase 4.1–4.2 give real evidence to point to.
|
|
72
|
+
|
|
73
|
+
## What's explicitly not in this plan
|
|
74
|
+
|
|
75
|
+
- No plan to pursue OpenSSF Gold/foundation-grade on any timeline — see
|
|
76
|
+
[docs/openssf-gap-analysis.md](openssf-gap-analysis.md) for why that's an
|
|
77
|
+
organizational precondition problem, not a checklist.
|
|
78
|
+
- No plan to add PyPI, Go, or Rust packaging — none of those ecosystems are used by
|
|
79
|
+
this project.
|
|
80
|
+
- No plan to add a `.devcontainer` — assessed as optional given the toolchain is
|
|
81
|
+
already a single `pnpm install` away from working.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Reference
|
|
2
|
+
|
|
3
|
+
Reference material is information-oriented: precise, exhaustive where practical, and
|
|
4
|
+
organized for lookup rather than reading start-to-finish. This index follows the
|
|
5
|
+
[Diátaxis](https://diataxis.fr/reference/) definition of "reference."
|
|
6
|
+
|
|
7
|
+
- [Environment variables](../../README.md#configuration) — the canonical
|
|
8
|
+
`SSH_MCP_*` configuration table lives in the README so it stays next to the
|
|
9
|
+
install/quickstart instructions; this reference doesn't duplicate it.
|
|
10
|
+
- [Tool profiles](../../README.md#tool-profiles) — which tools/resources/prompts each
|
|
11
|
+
connector profile (`full`, `remote-safe`, `chatgpt`, `claude`, `remote-readonly`,
|
|
12
|
+
`remote-broker`) exposes.
|
|
13
|
+
- [CLI reference](cli.md) — `ssh-mcp-pro` and `ssh-mcp-pro-agent` command-line flags.
|
|
14
|
+
- [Configuration reference](configuration.md) — the `SSH_MCP_*` table grouped by
|
|
15
|
+
concern, pointing back to the canonical table in the README.
|
|
16
|
+
- [Compatibility reference](compatibility.md) — supported Node.js/pnpm/OS/container
|
|
17
|
+
platforms and MCP transport/client compatibility.
|
|
18
|
+
- [MCP metadata](../../mcp.json) and [server metadata](../../server.json) — machine-readable
|
|
19
|
+
capability and registry declarations.
|
|
20
|
+
- [Security defaults](../../SECURITY_DECISIONS.md) — the exact default-deny behaviors and
|
|
21
|
+
why each exists.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# CLI Reference
|
|
2
|
+
|
|
3
|
+
ssh-mcp-pro installs two binaries (see `bin` in [package.json](../../package.json)):
|
|
4
|
+
|
|
5
|
+
- `ssh-mcp-pro` — the MCP server itself.
|
|
6
|
+
- `ssh-mcp-pro-agent` — the remote control-plane agent (see
|
|
7
|
+
[docs/remote-mcp-hardening.md](../remote-mcp-hardening.md)).
|
|
8
|
+
|
|
9
|
+
## `ssh-mcp-pro`
|
|
10
|
+
|
|
11
|
+
Source of truth: [src/cli.ts](../../src/cli.ts).
|
|
12
|
+
|
|
13
|
+
| Flag / subcommand | Effect |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| (no args) | Starts the server over stdio transport (default) |
|
|
16
|
+
| `stdio` | Explicitly selects stdio transport |
|
|
17
|
+
| `http` | Selects the Streamable HTTP transport |
|
|
18
|
+
| `agent [...]` | Delegates all remaining arguments to the agent CLI (see below); consumes the rest of `argv` |
|
|
19
|
+
| `--help`, `-h` | Print help and exit |
|
|
20
|
+
| `--version`, `-v` | Print version and exit |
|
|
21
|
+
| `--stdio` | Forces stdio transport |
|
|
22
|
+
| `--transport=stdio` \| `--transport=http` | Explicit transport selection |
|
|
23
|
+
| `--host <value>` | HTTP bind host (overrides `SSH_MCP_HTTP_HOST`) |
|
|
24
|
+
| `--port <value>` | HTTP bind port (overrides `SSH_MCP_HTTP_PORT`) |
|
|
25
|
+
| `--bearer-token-file <path>` | Bearer token file for HTTP auth (overrides `SSH_MCP_HTTP_BEARER_TOKEN_FILE`) |
|
|
26
|
+
| `--enable-legacy-sse` | Enables legacy SSE compatibility (overrides `SSH_MCP_ENABLE_LEGACY_SSE`) |
|
|
27
|
+
| `--tool-profile <name>` | Selects a tool exposure profile (overrides `SSH_MCP_TOOL_PROFILE`) — see [Tool Profiles](../../README.md#tool-profiles) |
|
|
28
|
+
| `--connector-credential-provider <name>` | Selects a connector credential provider (overrides `SSH_MCP_CONNECTOR_CREDENTIAL_PROVIDER`) |
|
|
29
|
+
| `--no-stdio` | Unsupported/internal flag; do not rely on it |
|
|
30
|
+
|
|
31
|
+
Unknown flags are ignored rather than rejected, so that MCP clients invoking the binary
|
|
32
|
+
with extra arguments don't break the server.
|
|
33
|
+
|
|
34
|
+
## `ssh-mcp-pro-agent`
|
|
35
|
+
|
|
36
|
+
Delegates directly to `src/remote/agent-cli.ts` via `argv` passthrough. See
|
|
37
|
+
[docs/remote-mcp-hardening.md](../remote-mcp-hardening.md) and
|
|
38
|
+
[docs/adding-a-device.md](../adding-a-device.md) for the documented agent workflows —
|
|
39
|
+
this reference intentionally doesn't restate flags that aren't yet enumerated in one
|
|
40
|
+
place in the source, to avoid documenting something that could drift silently from the
|
|
41
|
+
implementation.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Compatibility Reference
|
|
2
|
+
|
|
3
|
+
## Node.js
|
|
4
|
+
|
|
5
|
+
| Requirement | Value | Source |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| Minimum supported range | `>=22.22.2 \|\| >=24.15.0 \|\| >=26.3.0` | `package.json` `engines.node` |
|
|
8
|
+
| CI matrix | Node 22, 24, 26 (`Unit Tests (Node 22/24/26)`) | `.github/workflows/ci.yml` |
|
|
9
|
+
| Local development / release builds | Node 24.15.0 / 26.3.0 | `CONTRIBUTING.md`, `release.yml` |
|
|
10
|
+
|
|
11
|
+
Note the gap between 22.22.2 and 24.15.0: Node 23.x is not supported (it was never an
|
|
12
|
+
LTS line). This is expected, not an oversight.
|
|
13
|
+
|
|
14
|
+
## Package manager
|
|
15
|
+
|
|
16
|
+
pnpm `11.28.5`, enforced via `packageManager` in `package.json` and `engine-strict=true`.
|
|
17
|
+
npm/yarn are not supported installation paths for *developing* this repo (consumers
|
|
18
|
+
installing the published package via npm/npx are unaffected — see
|
|
19
|
+
[README.md#installation](../../README.md#installation)).
|
|
20
|
+
|
|
21
|
+
## Operating systems
|
|
22
|
+
|
|
23
|
+
| OS | Status | Evidence |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| Linux | Passed | Primary CI target; SSH fixture-based integration/E2E tests |
|
|
26
|
+
| Windows command paths | Passed | Dedicated `test:integration:windows` project on `windows-2022`; the `Windows Command-Path Integration` job validates local PowerShell and package-manager dispatch, not a real Windows OpenSSH server |
|
|
27
|
+
| macOS | Needs human confirmation | Listed as a supported platform in `mcp.json` (`platforms: [linux, macos, windows]`) but no macOS-specific CI job was found — likely covered incidentally by the cross-platform Node.js/SSH code paths, not by a dedicated test |
|
|
28
|
+
|
|
29
|
+
## Container platforms
|
|
30
|
+
|
|
31
|
+
`linux/amd64`, `linux/arm64` — built and verified in `docker.yml` via Buildx + QEMU.
|
|
32
|
+
|
|
33
|
+
## MCP transport compatibility
|
|
34
|
+
|
|
35
|
+
| Transport | Status |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| stdio | Passed — default, used by all documented client configs |
|
|
38
|
+
| Streamable HTTP | Passed — `http`/`--transport=http`, with the non-loopback safety gate described in [SECURITY_DECISIONS.md](../../SECURITY_DECISIONS.md) |
|
|
39
|
+
| Legacy SSE | Partial — available behind `SSH_MCP_ENABLE_LEGACY_SSE` for compatibility, not the recommended path |
|
|
40
|
+
|
|
41
|
+
## MCP client compatibility
|
|
42
|
+
|
|
43
|
+
Documented configs exist for a generic stdio client, VS Code, and Claude Desktop (see
|
|
44
|
+
[README.md#quickstart](../../README.md#quickstart)) plus connector-shaped profiles for
|
|
45
|
+
ChatGPT and Claude remote connectors (`chatgpt`, `claude` tool profiles). See
|
|
46
|
+
[Tool Profiles](../../README.md#tool-profiles) for exactly what each profile exposes.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Configuration Reference
|
|
2
|
+
|
|
3
|
+
The canonical, exhaustive `SSH_MCP_*` table lives in
|
|
4
|
+
[README.md#configuration](../../README.md#configuration) and isn't duplicated here — a
|
|
5
|
+
second copy of a 40+ row table would drift out of sync with the first. This page groups
|
|
6
|
+
that table by concern, for when you know *what kind* of setting you need but not its
|
|
7
|
+
exact name.
|
|
8
|
+
|
|
9
|
+
## Session and resource limits
|
|
10
|
+
|
|
11
|
+
`SSH_MCP_MAX_SESSIONS`, `SSH_MCP_SESSION_TTL`, `SSH_MCP_COMMAND_TIMEOUT`,
|
|
12
|
+
`SSH_MCP_MAX_COMMAND_OUTPUT_BYTES`, `SSH_MCP_MAX_STREAM_CHUNKS`, `SSH_MCP_MAX_FILE_SIZE`,
|
|
13
|
+
`SSH_MCP_MAX_FILE_WRITE_BYTES`, `SSH_MCP_MAX_TRANSFER_BYTES`.
|
|
14
|
+
|
|
15
|
+
## SSH security policy
|
|
16
|
+
|
|
17
|
+
`SSH_MCP_HOST_KEY_POLICY`, `SSH_MCP_KNOWN_HOSTS_PATH`, `SSH_MCP_ALLOW_ROOT_LOGIN`,
|
|
18
|
+
`SSH_MCP_ALLOWED_CIPHERS`, `SSH_MCP_ALLOW_RAW_SUDO`, `SSH_MCP_ALLOW_DESTRUCTIVE_COMMANDS`,
|
|
19
|
+
`SSH_MCP_ALLOW_DESTRUCTIVE_FS`, `SSH_MCP_ALLOWED_HOSTS`, `SSH_MCP_COMMAND_ALLOW`,
|
|
20
|
+
`SSH_MCP_COMMAND_DENY`, `SSH_MCP_PATH_ALLOW_PREFIXES`, `SSH_MCP_PATH_DENY_PREFIXES`. See
|
|
21
|
+
[docs/how-to/configure-ssh-policy.md](../how-to/configure-ssh-policy.md) for task-based
|
|
22
|
+
guidance and [docs/security/threat-model.md](../security/threat-model.md) for why these
|
|
23
|
+
defaults exist.
|
|
24
|
+
|
|
25
|
+
## Local transfer and tunnel policy
|
|
26
|
+
|
|
27
|
+
`SSH_MCP_LOCAL_PATH_ALLOW_PREFIXES`, `SSH_MCP_LOCAL_PATH_DENY_PREFIXES`,
|
|
28
|
+
`SSH_MCP_TUNNEL_ALLOW_BIND_HOSTS`, `SSH_MCP_TUNNEL_DENY_BIND_HOSTS`,
|
|
29
|
+
`SSH_MCP_TUNNEL_ALLOW_REMOTE_HOSTS`, `SSH_MCP_TUNNEL_DENY_REMOTE_HOSTS`,
|
|
30
|
+
`SSH_MCP_TUNNEL_ALLOW_PORTS`, `SSH_MCP_TUNNEL_DENY_PORTS`.
|
|
31
|
+
|
|
32
|
+
## Policy engine
|
|
33
|
+
|
|
34
|
+
`SSH_MCP_POLICY_FILE`, `SSH_MCP_POLICY_MODE` — a JSON policy file plus enforce/explain
|
|
35
|
+
mode, for centralizing the settings above instead of many separate environment
|
|
36
|
+
variables. No config-schema version field currently exists for the policy file (see
|
|
37
|
+
[docs/development/api-stability.md](../development/api-stability.md)).
|
|
38
|
+
|
|
39
|
+
## HTTP transport
|
|
40
|
+
|
|
41
|
+
`SSH_MCP_HTTP_HOST`, `SSH_MCP_HTTP_PORT`, `SSH_MCP_HTTP_ALLOWED_ORIGINS`,
|
|
42
|
+
`SSH_MCP_HTTP_BEARER_TOKEN_FILE`, `SSH_MCP_ENABLE_LEGACY_SSE`,
|
|
43
|
+
`SSH_MCP_HTTP_MAX_REQUEST_BODY_BYTES`, `SSH_MCP_HTTP_MAX_SESSIONS`,
|
|
44
|
+
`SSH_MCP_HTTP_SESSION_IDLE_TTL_MS`, `SSH_MCP_HTTP_PUBLIC_URL`,
|
|
45
|
+
`SSH_MCP_HTTP_TRUST_PROXY`. See [docs/remote-mcp-hardening.md](../remote-mcp-hardening.md).
|
|
46
|
+
|
|
47
|
+
## OAuth (HTTP auth mode)
|
|
48
|
+
|
|
49
|
+
`SSH_MCP_HTTP_AUTH_MODE`, `SSH_MCP_OAUTH_ISSUER`, `SSH_MCP_OAUTH_AUDIENCE`,
|
|
50
|
+
`SSH_MCP_OAUTH_JWKS_URL`, `SSH_MCP_OAUTH_RESOURCE`, `SSH_MCP_OAUTH_REQUIRED_SCOPES`,
|
|
51
|
+
`SSH_MCP_OAUTH_ALLOWED_ALGORITHMS`.
|
|
52
|
+
|
|
53
|
+
## Remote agent WebSocket boundary
|
|
54
|
+
|
|
55
|
+
`AGENT_WS_PATH`, `AGENT_WS_HELLO_TIMEOUT_MS`, `AGENT_WS_HEARTBEAT_INTERVAL_MS`,
|
|
56
|
+
`AGENT_WS_IDLE_TIMEOUT_MS`, `AGENT_WS_MAX_CONNECTIONS`,
|
|
57
|
+
`AGENT_WS_MAX_CONNECTIONS_PER_AGENT`, `MAX_ACTION_TIMEOUT_SECONDS`,
|
|
58
|
+
`MAX_OUTPUT_BYTES`. The idle timeout is normalized to at least two heartbeat intervals.
|
|
59
|
+
See [docs/remote-mcp-hardening.md](../remote-mcp-hardening.md) for operational guidance.
|
|
60
|
+
|
|
61
|
+
## Tool exposure and connectors
|
|
62
|
+
|
|
63
|
+
`SSH_MCP_TOOL_PROFILE` / `SSH_MCP_CONNECTOR_PROFILE`,
|
|
64
|
+
`SSH_MCP_CONNECTOR_CREDENTIAL_PROVIDER`, `SSH_MCP_CONNECTOR_CREDENTIAL_COMMAND`,
|
|
65
|
+
`SSH_MCP_CONNECTOR_CREDENTIAL_COMMAND_ARGS`,
|
|
66
|
+
`SSH_MCP_CONNECTOR_CREDENTIAL_COMMAND_TIMEOUT_MS`,
|
|
67
|
+
`SSH_MCP_CONNECTOR_DEFAULT_USERNAME`, `SSH_MCP_REMOTE_AGENT_MCP_PASSTHROUGH`. See
|
|
68
|
+
[Tool Profiles](../../README.md#tool-profiles) for what each profile exposes.
|
|
69
|
+
|
|
70
|
+
## Misc
|
|
71
|
+
|
|
72
|
+
`SSH_MCP_DEBUG`, `SSH_MCP_RATE_LIMIT*` (global and per-session rate limiting).
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Remote MCP hardening
|
|
2
|
+
|
|
3
|
+
Use this checklist before exposing `ssh-mcp-pro` through a public Streamable HTTP MCP endpoint.
|
|
4
|
+
|
|
5
|
+
## Streamable HTTP session resilience
|
|
6
|
+
|
|
7
|
+
- Keep `SSH_MCP_HTTP_SESSION_IDLE_TTL_MS` low enough for connector workloads that may abandon sessions without sending HTTP `DELETE`. A practical production value for ChatGPT/Cloudflare deployments is `300000`.
|
|
8
|
+
- Raise `SSH_MCP_HTTP_MAX_SESSIONS` above the expected burst of connector initializations. A practical production value for ChatGPT/Cloudflare deployments is `100`.
|
|
9
|
+
- The HTTP session registry cleans expired sessions before opening a new session. If capacity is still full, it evicts the oldest idle session with reason `capacity-evict-oldest` instead of returning a persistent 503 that can surface as an upstream 502.
|
|
10
|
+
- Watch logs for `HTTP MCP session removed`, `idle-timeout`, and `capacity-evict-oldest` to confirm cleanup is happening.
|
|
11
|
+
|
|
12
|
+
## Remote agent WebSocket lifecycle
|
|
13
|
+
|
|
14
|
+
- Keep `AGENT_WS_HELLO_TIMEOUT_MS` short enough that unauthenticated sockets cannot remain open indefinitely. The default is `10000` milliseconds.
|
|
15
|
+
- `AGENT_WS_MAX_CONNECTIONS` bounds all accepted agent WebSockets, including connections still waiting for `agent.hello`. The default is `128`.
|
|
16
|
+
- `AGENT_WS_MAX_CONNECTIONS_PER_AGENT` bounds active and replacement candidates for one enrolled agent. The default is `2`, which permits one live connection and one authenticated replacement attempt.
|
|
17
|
+
- `AGENT_WS_HEARTBEAT_INTERVAL_MS` defaults to `30000`. The server sends protocol pings and treats pong or text traffic as activity.
|
|
18
|
+
- `AGENT_WS_IDLE_TIMEOUT_MS` defaults to `90000` and is normalized to at least two heartbeat intervals. Idle connections close cleanly and are forcibly terminated if the close handshake does not complete.
|
|
19
|
+
- The `/readyz` response reports `agent_connections_open` and `agent_connections_unauthenticated`. Audit events record global/per-agent capacity rejection, hello timeout, idle timeout, replacement, and safe numeric close codes.
|
|
20
|
+
- Keep WebSocket compression disabled at the public boundary. Agent messages are text-only and capped at 1 MiB.
|
|
21
|
+
|
|
22
|
+
## OAuth and token verification
|
|
23
|
+
|
|
24
|
+
- Prefer OAuth mode for public endpoints.
|
|
25
|
+
- Configure `SSH_MCP_OAUTH_ISSUER`, `SSH_MCP_OAUTH_JWKS_URL`, and either `SSH_MCP_OAUTH_AUDIENCE` or `SSH_MCP_OAUTH_RESOURCE` explicitly.
|
|
26
|
+
- Use `SSH_MCP_OAUTH_ALLOWED_ALGORITHMS` to pin accepted JWT algorithms when your authorization server has a stable signing policy.
|
|
27
|
+
- Keep `SSH_MCP_OAUTH_REQUIRED_SCOPES` narrow and environment-specific.
|
|
28
|
+
- Confirm unauthorized `/mcp` requests return `401` with `WWW-Authenticate` metadata discovery headers.
|
|
29
|
+
|
|
30
|
+
## Connector routing
|
|
31
|
+
|
|
32
|
+
- Keep `SSH_MCP_REMOTE_AGENT_MCP_PASSTHROUGH` disabled by default.
|
|
33
|
+
- Enable it only during a controlled remote-agent routing migration where `/mcp` must bypass the remote control plane and reach the Streamable HTTP MCP handler.
|
|
34
|
+
- Remove the switch again after the routing migration is complete.
|
|
35
|
+
|
|
36
|
+
## Tool exposure
|
|
37
|
+
|
|
38
|
+
- Prefer `remote-safe`, `remote-readonly`, or `remote-broker` profiles for public connectors.
|
|
39
|
+
- Require a host allowlist for public endpoints.
|
|
40
|
+
- Keep credential entry in chat disabled; use the credential broker or SSH agent instead.
|
|
41
|
+
- Keep destructive filesystem and command policies disabled unless the environment has an explicit change-control process.
|
|
42
|
+
|
|
43
|
+
## Packaging and repository hygiene
|
|
44
|
+
|
|
45
|
+
- Never commit local bearer tokens, private keys, or generated credential files.
|
|
46
|
+
- Keep local credential directories ignored by Git.
|
|
47
|
+
- Run the full push gate before merging changes that affect remote connector behavior:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pnpm run format:check
|
|
51
|
+
pnpm run lint
|
|
52
|
+
pnpm run typecheck
|
|
53
|
+
pnpm test
|
|
54
|
+
pnpm run build
|
|
55
|
+
pnpm run audit
|
|
56
|
+
pnpm run validate:mcp-metadata
|
|
57
|
+
pnpm run validate:chatgpt-app
|
|
58
|
+
pnpm run validate:claude-connector
|
|
59
|
+
```
|