ssh-mcp-pro 1.2.1 → 1.4.0
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 +72 -0
- package/README.md +12 -2
- package/dist/agent-bin.js +5 -2
- package/dist/agent-bin.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +38 -47
- package/dist/cli.js.map +1 -1
- package/dist/config-security-env.d.ts +4 -0
- package/dist/config-security-env.d.ts.map +1 -0
- package/dist/config-security-env.js +64 -0
- package/dist/config-security-env.js.map +1 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -67
- package/dist/config.js.map +1 -1
- package/dist/connector-profile.d.ts +17 -3
- package/dist/connector-profile.d.ts.map +1 -1
- package/dist/connector-profile.js.map +1 -1
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js +226 -192
- package/dist/detect.js.map +1 -1
- package/dist/ensure-package-service.d.ts +9 -0
- package/dist/ensure-package-service.d.ts.map +1 -0
- package/dist/ensure-package-service.js +146 -0
- package/dist/ensure-package-service.js.map +1 -0
- package/dist/ensure-pkg.js +4 -4
- package/dist/ensure-pkg.js.map +1 -1
- package/dist/ensure.d.ts.map +1 -1
- package/dist/ensure.js +15 -144
- package/dist/ensure.js.map +1 -1
- package/dist/fs-sftp.js +1 -1
- package/dist/fs-sftp.js.map +1 -1
- package/dist/fs-stat.d.ts +10 -0
- package/dist/fs-stat.d.ts.map +1 -0
- package/dist/fs-stat.js +62 -0
- package/dist/fs-stat.js.map +1 -0
- package/dist/fs-tools.d.ts.map +1 -1
- package/dist/fs-tools.js +6 -58
- package/dist/fs-tools.js.map +1 -1
- package/dist/http-security.d.ts.map +1 -1
- package/dist/http-security.js +6 -6
- package/dist/http-security.js.map +1 -1
- package/dist/http-server-runtime.d.ts.map +1 -1
- package/dist/http-server-runtime.js +41 -7
- package/dist/http-server-runtime.js.map +1 -1
- package/dist/index.js +6 -23
- package/dist/index.js.map +1 -1
- package/dist/mcp-http-web.d.ts +8 -0
- package/dist/mcp-http-web.d.ts.map +1 -0
- package/dist/mcp-http-web.js +35 -0
- package/dist/mcp-http-web.js.map +1 -0
- package/dist/mcp.d.ts +6 -2
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +22 -14
- package/dist/mcp.js.map +1 -1
- package/dist/policy.d.ts +8 -0
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +147 -105
- package/dist/policy.js.map +1 -1
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +11 -7
- package/dist/process.js.map +1 -1
- package/dist/prompts.d.ts +2 -11
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js.map +1 -1
- package/dist/remote/agent-activity-log.d.ts +26 -0
- package/dist/remote/agent-activity-log.d.ts.map +1 -0
- package/dist/remote/agent-activity-log.js +211 -0
- package/dist/remote/agent-activity-log.js.map +1 -0
- package/dist/remote/agent-cli.d.ts.map +1 -1
- package/dist/remote/agent-cli.js +92 -25
- package/dist/remote/agent-cli.js.map +1 -1
- package/dist/remote/agent-executor.d.ts.map +1 -1
- package/dist/remote/agent-executor.js +25 -16
- package/dist/remote/agent-executor.js.map +1 -1
- package/dist/remote/agent-handler.d.ts +2 -0
- package/dist/remote/agent-handler.d.ts.map +1 -1
- package/dist/remote/agent-handler.js +4 -0
- package/dist/remote/agent-handler.js.map +1 -1
- package/dist/remote/config.d.ts.map +1 -1
- package/dist/remote/config.js +12 -1
- package/dist/remote/config.js.map +1 -1
- package/dist/remote/control-plane.d.ts +12 -0
- package/dist/remote/control-plane.d.ts.map +1 -1
- package/dist/remote/control-plane.js +174 -53
- package/dist/remote/control-plane.js.map +1 -1
- package/dist/remote/crypto.d.ts +9 -4
- package/dist/remote/crypto.d.ts.map +1 -1
- package/dist/remote/crypto.js +41 -9
- package/dist/remote/crypto.js.map +1 -1
- package/dist/remote/http-util.d.ts.map +1 -1
- package/dist/remote/http-util.js +14 -7
- package/dist/remote/http-util.js.map +1 -1
- package/dist/remote/mcp-tools.d.ts +7 -0
- package/dist/remote/mcp-tools.d.ts.map +1 -1
- package/dist/remote/mcp-tools.js +28 -0
- package/dist/remote/mcp-tools.js.map +1 -1
- package/dist/remote/oauth-handler.d.ts +36 -3
- package/dist/remote/oauth-handler.d.ts.map +1 -1
- package/dist/remote/oauth-handler.js +287 -13
- package/dist/remote/oauth-handler.js.map +1 -1
- package/dist/remote/policy.js +2 -2
- package/dist/remote/policy.js.map +1 -1
- package/dist/remote/safe-file.d.ts +2 -1
- package/dist/remote/safe-file.d.ts.map +1 -1
- package/dist/remote/safe-file.js +51 -53
- package/dist/remote/safe-file.js.map +1 -1
- package/dist/remote/schemas.d.ts +9 -9
- package/dist/remote/schemas.js +6 -6
- package/dist/remote/schemas.js.map +1 -1
- package/dist/remote/store.d.ts +38 -1
- package/dist/remote/store.d.ts.map +1 -1
- package/dist/remote/store.js +149 -11
- package/dist/remote/store.js.map +1 -1
- package/dist/remote/types.d.ts +14 -0
- package/dist/remote/types.d.ts.map +1 -1
- package/dist/remote/types.js.map +1 -1
- package/dist/remote/web-dashboard-client-config.d.ts +77 -0
- package/dist/remote/web-dashboard-client-config.d.ts.map +1 -0
- package/dist/remote/web-dashboard-client-config.js +81 -0
- package/dist/remote/web-dashboard-client-config.js.map +1 -0
- package/dist/remote/web-dashboard-html.d.ts +2 -0
- package/dist/remote/web-dashboard-html.d.ts.map +1 -0
- package/dist/remote/web-dashboard-html.js +433 -0
- package/dist/remote/web-dashboard-html.js.map +1 -0
- package/dist/remote/web-dashboard-oauth-users.d.ts +4 -0
- package/dist/remote/web-dashboard-oauth-users.d.ts.map +1 -0
- package/dist/remote/web-dashboard-oauth-users.js +78 -0
- package/dist/remote/web-dashboard-oauth-users.js.map +1 -0
- package/dist/remote/web-dashboard-static.d.ts +4 -0
- package/dist/remote/web-dashboard-static.d.ts.map +1 -0
- package/dist/remote/web-dashboard-static.js +19 -0
- package/dist/remote/web-dashboard-static.js.map +1 -0
- package/dist/remote/web-dashboard-styles.d.ts +2 -0
- package/dist/remote/web-dashboard-styles.d.ts.map +1 -0
- package/dist/remote/web-dashboard-styles.js +187 -0
- package/dist/remote/web-dashboard-styles.js.map +1 -0
- package/dist/remote/web-dashboard.d.ts +62 -0
- package/dist/remote/web-dashboard.d.ts.map +1 -0
- package/dist/remote/web-dashboard.js +467 -0
- package/dist/remote/web-dashboard.js.map +1 -0
- package/dist/resources.d.ts +2 -7
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js.map +1 -1
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +6 -3
- package/dist/retry.js.map +1 -1
- package/dist/safety.d.ts.map +1 -1
- package/dist/safety.js +56 -6
- package/dist/safety.js.map +1 -1
- package/dist/session-auth.js +7 -7
- package/dist/session-auth.js.map +1 -1
- package/dist/session.d.ts +5 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +81 -54
- package/dist/session.js.map +1 -1
- package/dist/shell.d.ts.map +1 -1
- package/dist/shell.js +5 -4
- package/dist/shell.js.map +1 -1
- package/dist/ssh-config.d.ts +2 -2
- package/dist/ssh-config.d.ts.map +1 -1
- package/dist/ssh-config.js +24 -20
- package/dist/ssh-config.js.map +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +9 -2
- package/dist/telemetry.js.map +1 -1
- package/dist/tools/connector.provider.d.ts +1 -1
- package/dist/tools/connector.provider.d.ts.map +1 -1
- package/dist/tools/ensure.provider.d.ts +1 -1
- package/dist/tools/ensure.provider.d.ts.map +1 -1
- package/dist/tools/fs.provider.d.ts +1 -1
- package/dist/tools/fs.provider.d.ts.map +1 -1
- package/dist/tools/metadata.d.ts +1 -1
- package/dist/tools/metadata.d.ts.map +1 -1
- package/dist/tools/output-schemas.d.ts +27 -135
- package/dist/tools/output-schemas.d.ts.map +1 -1
- package/dist/tools/process.provider.d.ts +1 -1
- package/dist/tools/process.provider.d.ts.map +1 -1
- package/dist/tools/registry.d.ts +1 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +16 -13
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/session.provider.d.ts +1 -1
- package/dist/tools/session.provider.d.ts.map +1 -1
- package/dist/tools/system.provider.d.ts +1 -1
- package/dist/tools/system.provider.d.ts.map +1 -1
- package/dist/tools/transfer.provider.d.ts +1 -1
- package/dist/tools/transfer.provider.d.ts.map +1 -1
- package/dist/tools/tunnel.provider.d.ts +1 -1
- package/dist/tools/tunnel.provider.d.ts.map +1 -1
- package/dist/tools/types.d.ts +1 -1
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/transfer.js +2 -2
- package/dist/transfer.js.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/docs/adding-a-device.md +39 -17
- package/docs/agent-activity-monitor.md +34 -0
- package/docs/coolify.md +29 -0
- package/docs/development/coding-standards.md +8 -2
- package/docs/development/dependency-management.md +1 -1
- package/docs/development/mcp-protocol-2026.md +33 -0
- package/docs/development/release-process.md +29 -55
- package/docs/doppler-guide.md +34 -0
- package/docs/reference/compatibility.md +1 -1
- package/docs/remote-mcp-hardening.md +17 -0
- package/docs/security/dependency-audit-exceptions.md +3 -7
- package/docs/security/hosted-data-inventory.md +81 -0
- package/docs/sonarcloud-analysis.md +9 -0
- package/mcp.json +1 -1
- package/package.json +31 -24
- package/registry/ssh-mcp-pro/mcp.json +1 -1
- package/server.json +3 -3
package/dist/remote/http-util.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createPublicKey } from "node:crypto";
|
|
2
2
|
import { URL } from "node:url";
|
|
3
|
-
import { LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS
|
|
3
|
+
import { LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS } from "@modelcontextprotocol/server";
|
|
4
4
|
import { sha256Base64Url } from "./crypto.js";
|
|
5
5
|
import { parseScopes } from "./scopes.js";
|
|
6
6
|
import { REMOTE_SCOPES } from "./types.js";
|
|
@@ -20,10 +20,11 @@ export function negotiateMcpProtocolVersion(params) {
|
|
|
20
20
|
: LATEST_PROTOCOL_VERSION;
|
|
21
21
|
}
|
|
22
22
|
export function quotePosixArg(value) {
|
|
23
|
-
|
|
23
|
+
const escaped = value.replaceAll("'", "'\"'\"'");
|
|
24
|
+
return `'${escaped}'`;
|
|
24
25
|
}
|
|
25
26
|
export function quotePowerShellArg(value) {
|
|
26
|
-
return `'${value.
|
|
27
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
27
28
|
}
|
|
28
29
|
export function asStringArray(value) {
|
|
29
30
|
if (!Array.isArray(value)) {
|
|
@@ -99,6 +100,15 @@ export async function readJson(req) {
|
|
|
99
100
|
}
|
|
100
101
|
return parsed;
|
|
101
102
|
}
|
|
103
|
+
function formFieldString(value) {
|
|
104
|
+
if (value === null || value === undefined) {
|
|
105
|
+
return "";
|
|
106
|
+
}
|
|
107
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
108
|
+
return String(value);
|
|
109
|
+
}
|
|
110
|
+
throw safeError("INVALID_CLIENT", "Expected a scalar JSON form value", 400);
|
|
111
|
+
}
|
|
102
112
|
export async function readJsonOrForm(req) {
|
|
103
113
|
const raw = await readBody(req);
|
|
104
114
|
const contentType = req.headers["content-type"] ?? "";
|
|
@@ -107,10 +117,7 @@ export async function readJsonOrForm(req) {
|
|
|
107
117
|
if (!isRecord(parsed)) {
|
|
108
118
|
throw safeError("INTERNAL_ERROR", "Expected JSON object");
|
|
109
119
|
}
|
|
110
|
-
return Object.fromEntries(Object.entries(parsed).map(([key, value]) => [
|
|
111
|
-
key,
|
|
112
|
-
typeof value === "string" ? value : String(value ?? ""),
|
|
113
|
-
]));
|
|
120
|
+
return Object.fromEntries(Object.entries(parsed).map(([key, value]) => [key, formFieldString(value)]));
|
|
114
121
|
}
|
|
115
122
|
try {
|
|
116
123
|
return formDecode(raw);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-util.js","sourceRoot":"","sources":["../../src/remote/http-util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,
|
|
1
|
+
{"version":3,"file":"http-util.js","sourceRoot":"","sources":["../../src/remote/http-util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AACpG,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAC1C,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,CAAC;AAEhD,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,MAAe;IACzD,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACzF,OAAO,gBAAgB,IAAI,2BAA2B,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QAC/E,CAAC,CAAC,gBAAgB;QAClB,CAAC,CAAC,uBAAuB,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACjD,OAAO,IAAI,OAAO,GAAG,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAO,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAA2B,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IAC5E,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;QAClD,IAAI,SAAS,IAAI,GAAG,EAAE,CAAC;YACrB,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,MAA2B,EAC3B,KAAa,EACb,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IAEhB,gBAAgB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9B,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAA2B,EAAE,KAAa,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IACxF,gBAAgB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9B,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,GAAG,kBAAkB,CAAC,CAAC;IAC5C,OAAO,MAAM,CAAC,IAAI,GAAG,2BAA2B,EAAE,CAAC;QACjD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;QAC1C,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC/B,MAAM;QACR,CAAC;QACD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,UAAkC,EAAE;IAC7D,OAAO;QACL,eAAe,EAAE,UAAU;QAC3B,MAAM,EAAE,UAAU;QAClB,GAAG,OAAO;KACX,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,GAAmB,EAAE,QAAgB;IAC5D,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC,CAAC;IACxE,GAAG,CAAC,GAAG,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,IAAqB,EACrB,OAAe,EACf,MAAM,GAAG,GAAG;IAEZ,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,YAAoB;IAC1D,IAAI,CAAC;QACH,OAAO,eAAe,CAAC,YAAY,CAAC,CAAC,iBAAiB,KAAK,SAAS,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,GAAoB,EAAE,QAAQ,GAAG,SAAS;IACvE,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnE,IAAI,IAAI,MAAM,CAAC,UAAU,CAAC;QAC1B,IAAI,IAAI,GAAG,QAAQ,EAAE,CAAC;YACpB,MAAM,SAAS,CAAC,WAAW,EAAE,2BAA2B,EAAE,GAAG,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,GAAoB;IACjD,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;IAChC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;QAChB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAC;IAC1C,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACtB,MAAM,SAAS,CAAC,gBAAgB,EAAE,sBAAsB,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;QACzF,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IACD,MAAM,SAAS,CAAC,gBAAgB,EAAE,mCAAmC,EAAE,GAAG,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAoB;IACvD,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IACtD,IAAI,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACrD,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACtB,MAAM,SAAS,CAAC,gBAAgB,EAAE,sBAAsB,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAC5E,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,SAAS,CAAC,gBAAgB,EAAE,0BAA0B,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACjC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,CACL,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAC3F,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,OAAO,eAAe,CAAC,QAAQ,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAS,aAAa,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACtD,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QACjD,MAAM,SAAS,CAAC,eAAe,EAAE,kCAAkC,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAClC,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC;AAChG,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAwB;IACpD,OAAO;QACL,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,cAAc,EAAE,KAAK,CAAC,aAAa;QACnC,aAAa,EAAE,KAAK,CAAC,YAAY;QACjC,YAAY,EAAE,KAAK,CAAC,UAAU;QAC9B,UAAU,EAAE,KAAK,CAAC,SAAS;QAC3B,UAAU,EAAE,KAAK,CAAC,SAAS;KAC5B,CAAC;AACJ,CAAC"}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import type { RemoteCapability, RemoteToolName } from "./types.js";
|
|
2
|
+
export interface ToolAnnotations {
|
|
3
|
+
readOnlyHint?: boolean;
|
|
4
|
+
destructiveHint?: boolean;
|
|
5
|
+
openWorldHint?: boolean;
|
|
6
|
+
}
|
|
2
7
|
interface RemoteToolDescriptor {
|
|
3
8
|
name: RemoteToolName;
|
|
4
9
|
description: string;
|
|
5
10
|
capability: RemoteCapability;
|
|
6
11
|
inputSchema: Record<string, unknown>;
|
|
12
|
+
annotations?: ToolAnnotations;
|
|
7
13
|
}
|
|
14
|
+
export declare function remoteToolAnnotations(name: RemoteToolName): ToolAnnotations;
|
|
8
15
|
export declare function listRemoteToolDescriptors(capabilities: readonly RemoteCapability[]): RemoteToolDescriptor[];
|
|
9
16
|
export {};
|
|
10
17
|
//# sourceMappingURL=mcp-tools.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-tools.d.ts","sourceRoot":"","sources":["../../src/remote/mcp-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGnE,UAAU,oBAAoB;IAC5B,IAAI,EAAE,cAAc,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"mcp-tools.d.ts","sourceRoot":"","sources":["../../src/remote/mcp-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGnE,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,UAAU,oBAAoB;IAC5B,IAAI,EAAE,cAAc,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B;AAyND,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,cAAc,GAAG,eAAe,CAQ3E;AAED,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,SAAS,gBAAgB,EAAE,GACxC,oBAAoB,EAAE,CAQxB"}
|
package/dist/remote/mcp-tools.js
CHANGED
|
@@ -190,11 +190,39 @@ const descriptors = {
|
|
|
190
190
|
},
|
|
191
191
|
},
|
|
192
192
|
};
|
|
193
|
+
const READ_ONLY_REMOTE_TOOLS = new Set([
|
|
194
|
+
"list_hosts",
|
|
195
|
+
"list_agents",
|
|
196
|
+
"get_agent_install_command",
|
|
197
|
+
"get_system_status",
|
|
198
|
+
"tail_logs",
|
|
199
|
+
"docker_ps",
|
|
200
|
+
"docker_logs",
|
|
201
|
+
"file_read",
|
|
202
|
+
"get_audit_events",
|
|
203
|
+
]);
|
|
204
|
+
const DESTRUCTIVE_REMOTE_TOOLS = new Set([
|
|
205
|
+
"run_shell_as_root",
|
|
206
|
+
"revoke_agent",
|
|
207
|
+
"file_write",
|
|
208
|
+
"restart_service",
|
|
209
|
+
"docker_restart",
|
|
210
|
+
]);
|
|
211
|
+
export function remoteToolAnnotations(name) {
|
|
212
|
+
const isReadOnly = READ_ONLY_REMOTE_TOOLS.has(name);
|
|
213
|
+
const isDestructive = DESTRUCTIVE_REMOTE_TOOLS.has(name);
|
|
214
|
+
return {
|
|
215
|
+
readOnlyHint: isReadOnly,
|
|
216
|
+
destructiveHint: isDestructive,
|
|
217
|
+
openWorldHint: !isReadOnly,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
193
220
|
export function listRemoteToolDescriptors(capabilities) {
|
|
194
221
|
const allowed = new Set(capabilities);
|
|
195
222
|
return REMOTE_TOOLS.filter((name) => allowed.has(TOOL_CAPABILITY_MAP[name])).map((name) => ({
|
|
196
223
|
name,
|
|
197
224
|
capability: TOOL_CAPABILITY_MAP[name],
|
|
225
|
+
annotations: remoteToolAnnotations(name),
|
|
198
226
|
...descriptors[name],
|
|
199
227
|
}));
|
|
200
228
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-tools.js","sourceRoot":"","sources":["../../src/remote/mcp-tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"mcp-tools.js","sourceRoot":"","sources":["../../src/remote/mcp-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import { type JwtKeyPair } from "./crypto.js";
|
|
3
3
|
import { RemoteStore } from "./store.js";
|
|
4
|
-
import type { AuditEvent, RemoteConfig } from "./types.js";
|
|
4
|
+
import type { AuditEvent, GitHubUser, GoogleUser, RemoteConfig } from "./types.js";
|
|
5
5
|
/** Pending OAuth authorization transaction. */
|
|
6
6
|
export interface PendingAuthorize {
|
|
7
7
|
clientId: string;
|
|
@@ -11,6 +11,7 @@ export interface PendingAuthorize {
|
|
|
11
11
|
scope: string;
|
|
12
12
|
state: string;
|
|
13
13
|
expiresAt: number;
|
|
14
|
+
approved?: boolean;
|
|
14
15
|
}
|
|
15
16
|
/** Handles OAuth 2.0 authorization code flow with PKCE and GitHub identity. */
|
|
16
17
|
export declare class OAuthHandler {
|
|
@@ -19,6 +20,9 @@ export declare class OAuthHandler {
|
|
|
19
20
|
private readonly authorizeTransactions;
|
|
20
21
|
private readonly getJwtKeyPair;
|
|
21
22
|
private readonly audit;
|
|
23
|
+
private readonly registrationAttempts;
|
|
24
|
+
private transactionCookie;
|
|
25
|
+
private matchingBrowser;
|
|
22
26
|
constructor(config: RemoteConfig, store: RemoteStore, authorizeTransactions: Map<string, PendingAuthorize>, getJwtKeyPair: () => JwtKeyPair, audit: (event: Omit<AuditEvent, "id" | "createdAt">) => void);
|
|
23
27
|
/** GET /.well-known/oauth-protected-resource */
|
|
24
28
|
protectedResourceMetadata(): Record<string, unknown>;
|
|
@@ -28,14 +32,43 @@ export declare class OAuthHandler {
|
|
|
28
32
|
handleRegister(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
29
33
|
/** GET /oauth/authorize */
|
|
30
34
|
handleAuthorize(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Avoid chaining browser form submissions through third-party OAuth redirects
|
|
37
|
+
* and then back to the MCP client. Chromium applies form-action to redirects;
|
|
38
|
+
* the final client callback is deliberately not a permitted form destination.
|
|
39
|
+
* A new document starts an ordinary navigation instead.
|
|
40
|
+
*/
|
|
41
|
+
private continueToGitHub;
|
|
42
|
+
/** Explicit browser approval binds this client to the current login transaction. */
|
|
43
|
+
handleApprove(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
31
44
|
private validateAuthorizeParams;
|
|
32
45
|
private restoreChatGptClient;
|
|
33
46
|
private addChatGptRedirectUri;
|
|
47
|
+
/** Consume one-use OAuth transaction and preserve the associated PKCE request. */
|
|
48
|
+
private consumeOAuthCallback;
|
|
49
|
+
private completeOAuthCallback;
|
|
34
50
|
/** GET /oauth/callback/github */
|
|
35
51
|
handleGitHubCallback(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
52
|
+
/** GET /oauth/callback/google */
|
|
53
|
+
handleGoogleCallback(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
54
|
+
fetchGoogleUser(code: string): Promise<GoogleUser>;
|
|
55
|
+
/** Fixture identities must never bypass provider login on public-facing hosts. */
|
|
56
|
+
private allowFixtureIdentity;
|
|
57
|
+
testGoogleUser(): GoogleUser | undefined;
|
|
58
|
+
upsertGoogleUser(googleUser: GoogleUser): {
|
|
59
|
+
id: string;
|
|
60
|
+
googleId: string;
|
|
61
|
+
email: string;
|
|
62
|
+
displayName: string;
|
|
63
|
+
};
|
|
64
|
+
isGoogleUserAllowed(user: GoogleUser): boolean;
|
|
36
65
|
private fetchGitHubUser;
|
|
37
|
-
|
|
38
|
-
|
|
66
|
+
testGitHubUser(): GitHubUser | undefined;
|
|
67
|
+
upsertGitHubUser(githubUser: GitHubUser): {
|
|
68
|
+
id: string;
|
|
69
|
+
githubId: string;
|
|
70
|
+
githubLogin: string;
|
|
71
|
+
};
|
|
39
72
|
private isGitHubUserAllowed;
|
|
40
73
|
private issueAuthorizationCode;
|
|
41
74
|
/** POST /oauth/token */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-handler.d.ts","sourceRoot":"","sources":["../../src/remote/oauth-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"oauth-handler.d.ts","sourceRoot":"","sources":["../../src/remote/oauth-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { escape as escapeHtml } from "html-escaper";
|
|
1
2
|
import { URL } from "node:url";
|
|
2
3
|
import { hashSecret, id, issueAccessToken, nowIso, publicJwkFromPem, randomToken, } from "./crypto.js";
|
|
3
4
|
import { asString, asStringArray, addNoStore, isSafeRedirectUri, pkceChallenge, readJson, readJsonOrForm, redirect, safeError, scopeList, } from "./http-util.js";
|
|
@@ -10,6 +11,16 @@ export class OAuthHandler {
|
|
|
10
11
|
authorizeTransactions;
|
|
11
12
|
getJwtKeyPair;
|
|
12
13
|
audit;
|
|
14
|
+
registrationAttempts = new Map();
|
|
15
|
+
transactionCookie(value) {
|
|
16
|
+
const secure = this.config.publicBaseUrl.startsWith("https://") ? "; Secure" : "";
|
|
17
|
+
return `ssh_mcp_oauth_tx=${value}; Path=/oauth; HttpOnly; SameSite=Lax; Max-Age=300${secure}`;
|
|
18
|
+
}
|
|
19
|
+
matchingBrowser(req, transactionId) {
|
|
20
|
+
return (req.headers.cookie ?? "")
|
|
21
|
+
.split(";")
|
|
22
|
+
.some((part) => part.trim() === `ssh_mcp_oauth_tx=${transactionId}`);
|
|
23
|
+
}
|
|
13
24
|
constructor(config, store, authorizeTransactions, getJwtKeyPair, audit) {
|
|
14
25
|
this.config = config;
|
|
15
26
|
this.store = store;
|
|
@@ -31,6 +42,7 @@ export class OAuthHandler {
|
|
|
31
42
|
authorizationServerMetadata() {
|
|
32
43
|
return {
|
|
33
44
|
issuer: this.config.publicBaseUrl,
|
|
45
|
+
authorization_response_iss_parameter_supported: true,
|
|
34
46
|
authorization_endpoint: `${this.config.publicBaseUrl}/oauth/authorize`,
|
|
35
47
|
token_endpoint: `${this.config.publicBaseUrl}/oauth/token`,
|
|
36
48
|
registration_endpoint: `${this.config.publicBaseUrl}/oauth/register`,
|
|
@@ -49,6 +61,16 @@ export class OAuthHandler {
|
|
|
49
61
|
if (redirectUris.length === 0 || redirectUris.some((uri) => !isSafeRedirectUri(uri))) {
|
|
50
62
|
throw safeError("INVALID_REDIRECT_URI", "redirect_uris must contain HTTPS URLs or localhost HTTP URLs");
|
|
51
63
|
}
|
|
64
|
+
// Bound unauthenticated DCR registration by the TCP peer, not spoofable proxy headers.
|
|
65
|
+
const peer = req.socket?.remoteAddress ?? "local-test";
|
|
66
|
+
const nowMs = Date.now();
|
|
67
|
+
const previous = this.registrationAttempts.get(peer);
|
|
68
|
+
const window = previous && previous.expiresAt > nowMs ? previous : { count: 0, expiresAt: nowMs + 300_000 };
|
|
69
|
+
if (window.count >= 20) {
|
|
70
|
+
throw safeError("FORBIDDEN", "OAuth client registration rate limit reached", 429);
|
|
71
|
+
}
|
|
72
|
+
window.count++;
|
|
73
|
+
this.registrationAttempts.set(peer, window);
|
|
52
74
|
if (this.store.countOAuthClients() >= this.config.maxOAuthClients) {
|
|
53
75
|
throw safeError("FORBIDDEN", "OAuth client registration limit reached", 429);
|
|
54
76
|
}
|
|
@@ -105,23 +127,149 @@ export class OAuthHandler {
|
|
|
105
127
|
const code = this.issueAuthorizationCode(pending, user.id);
|
|
106
128
|
const destination = new URL(redirectUri);
|
|
107
129
|
destination.searchParams.set("code", code);
|
|
130
|
+
destination.searchParams.set("iss", this.config.publicBaseUrl);
|
|
108
131
|
if (state) {
|
|
109
132
|
destination.searchParams.set("state", state);
|
|
110
133
|
}
|
|
111
134
|
redirect(res, destination.toString());
|
|
112
135
|
return;
|
|
113
136
|
}
|
|
114
|
-
|
|
115
|
-
|
|
137
|
+
const testGoogle = this.testGoogleUser();
|
|
138
|
+
if (testGoogle) {
|
|
139
|
+
const user = this.upsertGoogleUser(testGoogle);
|
|
140
|
+
const code = this.issueAuthorizationCode(pending, user.id);
|
|
141
|
+
const destination = new URL(redirectUri);
|
|
142
|
+
destination.searchParams.set("code", code);
|
|
143
|
+
destination.searchParams.set("iss", this.config.publicBaseUrl);
|
|
144
|
+
if (state) {
|
|
145
|
+
destination.searchParams.set("state", state);
|
|
146
|
+
}
|
|
147
|
+
redirect(res, destination.toString());
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const hasGitHub = Boolean(this.config.githubClientId && this.config.githubClientSecret);
|
|
151
|
+
const hasGoogle = Boolean(this.config.googleClientId && this.config.googleClientSecret);
|
|
152
|
+
if (!hasGitHub && !hasGoogle) {
|
|
153
|
+
throw safeError("FORBIDDEN", "Neither GitHub nor Google OAuth is configured", 503);
|
|
116
154
|
}
|
|
117
155
|
const transactionId = id("code");
|
|
118
156
|
this.authorizeTransactions.set(transactionId, pending);
|
|
157
|
+
const hostname = escapeHtml(new URL(redirectUri).hostname);
|
|
158
|
+
// Chromium also checks form-action on the redirect after POST /oauth/approve.
|
|
159
|
+
// Permit only the configured identity providers, not arbitrary OAuth clients.
|
|
160
|
+
const formActions = [
|
|
161
|
+
"'self'",
|
|
162
|
+
...(hasGoogle ? ["https://accounts.google.com"] : []),
|
|
163
|
+
...(hasGitHub ? ["https://github.com"] : []),
|
|
164
|
+
].join(" ");
|
|
165
|
+
res.writeHead(200, {
|
|
166
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
167
|
+
"Cache-Control": "no-store",
|
|
168
|
+
"Content-Security-Policy": `default-src 'none'; style-src 'unsafe-inline'; form-action ${formActions}; frame-ancestors 'none'; base-uri 'none'`,
|
|
169
|
+
"X-Content-Type-Options": "nosniff",
|
|
170
|
+
"Set-Cookie": this.transactionCookie(transactionId),
|
|
171
|
+
});
|
|
172
|
+
const buttons = [];
|
|
173
|
+
if (hasGoogle) {
|
|
174
|
+
buttons.push(`<button type="submit" name="provider" value="google" style="display:flex;align-items:center;justify-content:center;gap:10px;width:100%;padding:12px;margin-bottom:10px;background:#ffffff;color:#1f2937;border:1px solid #d1d5db;border-radius:8px;font-weight:600;font-size:15px;cursor:pointer;">
|
|
175
|
+
<svg width="18" height="18" viewBox="0 0 24 24"><path fill="#EA4335" d="M12 5c1.7 0 3 .7 3.9 1.5l2.9-2.9C17 2 14.7 1 12 1 7.5 1 3.7 3.6 1.9 7.3l3.7 2.9C6.5 7.4 9 5 12 5z"/><path fill="#4285F4" d="M23.5 12.3c0-.8-.1-1.6-.2-2.3H12v4.5h6.5c-.3 1.5-1.1 2.8-2.4 3.7l3.7 2.9c2.2-2 3.7-5 3.7-8.8z"/><path fill="#FBBC05" d="M5.6 14.8c-.2-.7-.4-1.5-.4-2.3s.2-1.6.4-2.3L1.9 7.3C.7 9.7 0 12.3 0 15.2c0 2.8.7 5.5 1.9 7.8l3.7-2.9z"/><path fill="#34A853" d="M12 23.5c3.2 0 6-1.1 8-3l-3.7-2.9c-1.1.7-2.5 1.2-4.3 1.2-3 0-5.5-2.4-6.4-5.2L1.9 16.5C3.7 20.3 7.5 23.5 12 23.5z"/></svg>
|
|
176
|
+
Continue with Google
|
|
177
|
+
</button>`);
|
|
178
|
+
}
|
|
179
|
+
if (hasGitHub) {
|
|
180
|
+
buttons.push(`<button type="submit" name="provider" value="github" style="display:flex;align-items:center;justify-content:center;gap:10px;width:100%;padding:12px;margin-bottom:10px;background:#24292f;color:#ffffff;border:none;border-radius:8px;font-weight:600;font-size:15px;cursor:pointer;">
|
|
181
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.53 1.032 1.53 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>
|
|
182
|
+
Continue with GitHub
|
|
183
|
+
</button>`);
|
|
184
|
+
}
|
|
185
|
+
res.end([
|
|
186
|
+
'<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Authorize SSH MCP Pro</title>',
|
|
187
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
188
|
+
'<style>body{background:#0d1117;color:#e6edf3;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:16px;}',
|
|
189
|
+
".card{background:#161b22;border:1px solid #30363d;border-radius:12px;padding:32px;max-width:440px;width:100%;box-shadow:0 10px 30px rgba(0,0,0,0.5);text-align:center;}",
|
|
190
|
+
"h1{font-size:22px;margin:0 0 12px;color:#f0f6fc;}p{font-size:14px;color:#8b949e;line-height:1.5;margin:0 0 24px;}",
|
|
191
|
+
".badge{background:#21262d;border:1px solid #30363d;border-radius:6px;padding:4px 8px;font-family:monospace;font-size:12px;color:#58a6ff;}",
|
|
192
|
+
"</style></head><body>",
|
|
193
|
+
'<div class="card">',
|
|
194
|
+
'<div style="margin-bottom:16px;"><span style="font-size:32px;">🔐</span></div>',
|
|
195
|
+
"<h1>Authorize SSH MCP Pro</h1>",
|
|
196
|
+
'<p>Allow client at <strong style="color:#f0f6fc;">',
|
|
197
|
+
hostname,
|
|
198
|
+
"</strong> to request access to:</p>",
|
|
199
|
+
'<p><span class="badge">',
|
|
200
|
+
escapeHtml(scope),
|
|
201
|
+
"</span></p>",
|
|
202
|
+
'<form method="post" action="/oauth/approve"><input type="hidden" name="transaction" value="',
|
|
203
|
+
escapeHtml(transactionId),
|
|
204
|
+
'">',
|
|
205
|
+
buttons.join(""),
|
|
206
|
+
"</form>",
|
|
207
|
+
'<div style="font-size:12px;color:#484f58;margin-top:20px;">Zero-Trust SSH Model Context Protocol Gateway</div>',
|
|
208
|
+
"</div></body></html>",
|
|
209
|
+
].join(""));
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Avoid chaining browser form submissions through third-party OAuth redirects
|
|
213
|
+
* and then back to the MCP client. Chromium applies form-action to redirects;
|
|
214
|
+
* the final client callback is deliberately not a permitted form destination.
|
|
215
|
+
* A new document starts an ordinary navigation instead.
|
|
216
|
+
*/
|
|
217
|
+
continueToGitHub(res, githubUrl) {
|
|
218
|
+
const href = escapeHtml(githubUrl.toString());
|
|
219
|
+
res.writeHead(200, {
|
|
220
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
221
|
+
"Cache-Control": "no-store",
|
|
222
|
+
"Referrer-Policy": "no-referrer",
|
|
223
|
+
"X-Content-Type-Options": "nosniff",
|
|
224
|
+
"Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; form-action 'none'; frame-ancestors 'none'; base-uri 'none'",
|
|
225
|
+
});
|
|
226
|
+
res.end([
|
|
227
|
+
'<!doctype html><html lang="en"><head><meta charset="utf-8">',
|
|
228
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
229
|
+
"<title>Continue with GitHub — SSH MCP Pro</title>",
|
|
230
|
+
'<meta http-equiv="refresh" content="0;url=',
|
|
231
|
+
href,
|
|
232
|
+
'">',
|
|
233
|
+
"</head><body>",
|
|
234
|
+
'<p>Continue to GitHub to sign in.</p><a href="',
|
|
235
|
+
href,
|
|
236
|
+
'">Continue with GitHub</a>',
|
|
237
|
+
"</body></html>",
|
|
238
|
+
].join(""));
|
|
239
|
+
}
|
|
240
|
+
/** Explicit browser approval binds this client to the current login transaction. */
|
|
241
|
+
async handleApprove(req, res) {
|
|
242
|
+
const form = await readJsonOrForm(req);
|
|
243
|
+
const transactionId = form.transaction ?? "";
|
|
244
|
+
const provider = asString(form.provider) ?? "github";
|
|
245
|
+
const pending = this.authorizeTransactions.get(transactionId);
|
|
246
|
+
if (!pending || pending.expiresAt < Date.now() || !this.matchingBrowser(req, transactionId)) {
|
|
247
|
+
throw safeError("INVALID_TOKEN", "OAuth transaction is missing or expired", 403);
|
|
248
|
+
}
|
|
249
|
+
if (provider === "google") {
|
|
250
|
+
if (!this.config.googleClientId || !this.config.googleClientSecret) {
|
|
251
|
+
throw safeError("FORBIDDEN", "Google OAuth is not configured", 503);
|
|
252
|
+
}
|
|
253
|
+
pending.approved = true;
|
|
254
|
+
const googleUrl = new URL("https://accounts.google.com/o/oauth2/v2/auth");
|
|
255
|
+
googleUrl.searchParams.set("client_id", this.config.googleClientId);
|
|
256
|
+
googleUrl.searchParams.set("redirect_uri", this.config.googleCallbackUrl);
|
|
257
|
+
googleUrl.searchParams.set("response_type", "code");
|
|
258
|
+
googleUrl.searchParams.set("scope", "openid email profile");
|
|
259
|
+
googleUrl.searchParams.set("state", transactionId);
|
|
260
|
+
redirect(res, googleUrl.toString());
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (!this.config.githubClientId || !this.config.githubClientSecret) {
|
|
264
|
+
throw safeError("FORBIDDEN", "GitHub OAuth is not configured", 503);
|
|
265
|
+
}
|
|
266
|
+
pending.approved = true;
|
|
119
267
|
const githubUrl = new URL("https://github.com/login/oauth/authorize");
|
|
120
268
|
githubUrl.searchParams.set("client_id", this.config.githubClientId);
|
|
121
269
|
githubUrl.searchParams.set("redirect_uri", this.config.githubCallbackUrl);
|
|
122
270
|
githubUrl.searchParams.set("scope", "read:user");
|
|
123
271
|
githubUrl.searchParams.set("state", transactionId);
|
|
124
|
-
|
|
272
|
+
this.continueToGitHub(res, githubUrl);
|
|
125
273
|
}
|
|
126
274
|
validateAuthorizeParams(clientId, redirectUri, responseType, codeChallenge, codeChallengeMethod, resource, scope) {
|
|
127
275
|
let client = this.store.getClient(clientId);
|
|
@@ -203,26 +351,126 @@ export class OAuthHandler {
|
|
|
203
351
|
});
|
|
204
352
|
return { ...client, redirectUris: updatedUris };
|
|
205
353
|
}
|
|
206
|
-
/**
|
|
207
|
-
|
|
208
|
-
const url = new URL(req.url ??
|
|
354
|
+
/** Consume one-use OAuth transaction and preserve the associated PKCE request. */
|
|
355
|
+
consumeOAuthCallback(req, provider) {
|
|
356
|
+
const url = new URL(req.url ?? `/oauth/callback/${provider}`, this.config.publicBaseUrl);
|
|
209
357
|
const code = url.searchParams.get("code") ?? "";
|
|
210
358
|
const state = url.searchParams.get("state") ?? "";
|
|
211
359
|
const pending = this.authorizeTransactions.get(state);
|
|
212
360
|
this.authorizeTransactions.delete(state);
|
|
213
|
-
if (!code ||
|
|
361
|
+
if (!code ||
|
|
362
|
+
!pending?.approved ||
|
|
363
|
+
pending.expiresAt < Date.now() ||
|
|
364
|
+
!this.matchingBrowser(req, state)) {
|
|
214
365
|
throw safeError("INVALID_TOKEN", "OAuth transaction is missing or expired");
|
|
215
366
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
367
|
+
return { code, pending };
|
|
368
|
+
}
|
|
369
|
+
completeOAuthCallback(res, pending, userId) {
|
|
370
|
+
const authCode = this.issueAuthorizationCode(pending, userId);
|
|
219
371
|
const destination = new URL(pending.redirectUri);
|
|
220
372
|
destination.searchParams.set("code", authCode);
|
|
221
|
-
|
|
373
|
+
destination.searchParams.set("iss", this.config.publicBaseUrl);
|
|
374
|
+
if (pending.state)
|
|
222
375
|
destination.searchParams.set("state", pending.state);
|
|
223
|
-
|
|
376
|
+
res.setHeader("Set-Cookie", this.transactionCookie("").replace("Max-Age=300", "Max-Age=0"));
|
|
224
377
|
redirect(res, destination.toString());
|
|
225
378
|
}
|
|
379
|
+
/** GET /oauth/callback/github */
|
|
380
|
+
async handleGitHubCallback(req, res) {
|
|
381
|
+
const { code, pending } = this.consumeOAuthCallback(req, "github");
|
|
382
|
+
const user = this.upsertGitHubUser(await this.fetchGitHubUser(code));
|
|
383
|
+
this.completeOAuthCallback(res, pending, user.id);
|
|
384
|
+
}
|
|
385
|
+
/** GET /oauth/callback/google */
|
|
386
|
+
async handleGoogleCallback(req, res) {
|
|
387
|
+
const { code, pending } = this.consumeOAuthCallback(req, "google");
|
|
388
|
+
const user = this.upsertGoogleUser(await this.fetchGoogleUser(code));
|
|
389
|
+
this.completeOAuthCallback(res, pending, user.id);
|
|
390
|
+
}
|
|
391
|
+
async fetchGoogleUser(code) {
|
|
392
|
+
const testUser = this.testGoogleUser();
|
|
393
|
+
if (testUser) {
|
|
394
|
+
return testUser;
|
|
395
|
+
}
|
|
396
|
+
if (!this.config.googleClientId || !this.config.googleClientSecret) {
|
|
397
|
+
throw safeError("FORBIDDEN", "Google OAuth is not configured", 503);
|
|
398
|
+
}
|
|
399
|
+
const tokenResponse = await fetch("https://oauth2.googleapis.com/token", {
|
|
400
|
+
method: "POST",
|
|
401
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
402
|
+
body: new URLSearchParams({
|
|
403
|
+
code,
|
|
404
|
+
client_id: this.config.googleClientId,
|
|
405
|
+
client_secret: this.config.googleClientSecret,
|
|
406
|
+
redirect_uri: this.config.googleCallbackUrl,
|
|
407
|
+
grant_type: "authorization_code",
|
|
408
|
+
}).toString(),
|
|
409
|
+
});
|
|
410
|
+
const tokenPayload = (await tokenResponse.json());
|
|
411
|
+
const accessToken = asString(tokenPayload.access_token);
|
|
412
|
+
if (!accessToken) {
|
|
413
|
+
throw safeError("INVALID_TOKEN", "Google OAuth token exchange failed", 502);
|
|
414
|
+
}
|
|
415
|
+
const userResponse = await fetch("https://openidconnect.googleapis.com/v1/userinfo", {
|
|
416
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
417
|
+
});
|
|
418
|
+
const userPayload = (await userResponse.json());
|
|
419
|
+
const googleSub = asString(userPayload.sub);
|
|
420
|
+
const email = asString(userPayload.email);
|
|
421
|
+
const name = asString(userPayload.name) ?? email ?? "Google User";
|
|
422
|
+
const picture = asString(userPayload.picture);
|
|
423
|
+
if (!googleSub || !email) {
|
|
424
|
+
throw safeError("INVALID_TOKEN", "Google OAuth user response missing valid identity", 502);
|
|
425
|
+
}
|
|
426
|
+
return { id: googleSub, email, name, picture };
|
|
427
|
+
}
|
|
428
|
+
/** Fixture identities must never bypass provider login on public-facing hosts. */
|
|
429
|
+
allowFixtureIdentity() {
|
|
430
|
+
if (process.env.NODE_ENV !== "test")
|
|
431
|
+
return false;
|
|
432
|
+
try {
|
|
433
|
+
const baseUrl = new URL(this.config.publicBaseUrl);
|
|
434
|
+
return (baseUrl.protocol === "http:" &&
|
|
435
|
+
["localhost", "127.0.0.1", "[::1]"].includes(baseUrl.hostname));
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
testGoogleUser() {
|
|
442
|
+
if (!this.allowFixtureIdentity())
|
|
443
|
+
return undefined;
|
|
444
|
+
const idValue = process.env.SSHAUTOMATOR_TEST_GOOGLE_ID;
|
|
445
|
+
const email = process.env.SSHAUTOMATOR_TEST_GOOGLE_EMAIL;
|
|
446
|
+
const name = process.env.SSHAUTOMATOR_TEST_GOOGLE_NAME ?? "Test Google User";
|
|
447
|
+
return idValue && email ? { id: idValue, email, name } : undefined;
|
|
448
|
+
}
|
|
449
|
+
upsertGoogleUser(googleUser) {
|
|
450
|
+
if (!this.isGoogleUserAllowed(googleUser)) {
|
|
451
|
+
throw safeError("FORBIDDEN", "Google user is not allowed");
|
|
452
|
+
}
|
|
453
|
+
const existing = this.store.getUserByGoogleId(googleUser.id);
|
|
454
|
+
const internalId = existing?.id ?? `google:${googleUser.id}`;
|
|
455
|
+
this.store.upsertGoogleUser({ ...googleUser, internalId, now: nowIso() });
|
|
456
|
+
this.audit({
|
|
457
|
+
userId: internalId,
|
|
458
|
+
eventType: "user_login",
|
|
459
|
+
severity: "info",
|
|
460
|
+
metadata: { google_id: googleUser.id, email: googleUser.email, provider: "google" },
|
|
461
|
+
});
|
|
462
|
+
return {
|
|
463
|
+
id: internalId,
|
|
464
|
+
googleId: googleUser.id,
|
|
465
|
+
email: googleUser.email,
|
|
466
|
+
displayName: googleUser.name,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
isGoogleUserAllowed(user) {
|
|
470
|
+
return (this.config.allowAllUsers ||
|
|
471
|
+
this.config.allowedGoogleSubIds.includes(user.id) ||
|
|
472
|
+
this.config.allowedGoogleEmails.includes(user.email));
|
|
473
|
+
}
|
|
226
474
|
async fetchGitHubUser(code) {
|
|
227
475
|
const tokenResponse = await fetch("https://github.com/login/oauth/access_token", {
|
|
228
476
|
method: "POST",
|
|
@@ -243,9 +491,20 @@ export class OAuthHandler {
|
|
|
243
491
|
headers: { Authorization: `Bearer ${accessToken}`, Accept: "application/vnd.github+json" },
|
|
244
492
|
});
|
|
245
493
|
const userPayload = (await userResponse.json());
|
|
246
|
-
|
|
494
|
+
const githubId = userPayload.id;
|
|
495
|
+
const githubLogin = userPayload.login;
|
|
496
|
+
if (typeof githubId !== "number" ||
|
|
497
|
+
!Number.isSafeInteger(githubId) ||
|
|
498
|
+
githubId <= 0 ||
|
|
499
|
+
typeof githubLogin !== "string" ||
|
|
500
|
+
!githubLogin) {
|
|
501
|
+
throw safeError("INVALID_TOKEN", "GitHub OAuth user response missing valid identity", 502);
|
|
502
|
+
}
|
|
503
|
+
return { id: String(githubId), login: githubLogin };
|
|
247
504
|
}
|
|
248
505
|
testGitHubUser() {
|
|
506
|
+
if (!this.allowFixtureIdentity())
|
|
507
|
+
return undefined;
|
|
249
508
|
const idValue = process.env.SSHAUTOMATOR_TEST_GITHUB_ID;
|
|
250
509
|
const login = process.env.SSHAUTOMATOR_TEST_GITHUB_LOGIN;
|
|
251
510
|
return idValue && login ? { id: idValue, login } : undefined;
|
|
@@ -338,10 +597,25 @@ export class OAuthHandler {
|
|
|
338
597
|
return user;
|
|
339
598
|
}
|
|
340
599
|
}
|
|
600
|
+
if (userId.startsWith("google:")) {
|
|
601
|
+
const googleId = userId.slice("google:".length);
|
|
602
|
+
const user = this.store.getUserByGoogleId(googleId);
|
|
603
|
+
if (user) {
|
|
604
|
+
return user;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
const user = this.store.getUserById(userId);
|
|
608
|
+
if (user) {
|
|
609
|
+
return user;
|
|
610
|
+
}
|
|
341
611
|
throw safeError("UNAUTHORIZED", "User no longer exists", 401);
|
|
342
612
|
}
|
|
343
613
|
/** Remove expired pending authorization transactions. */
|
|
344
614
|
cleanupExpired(now = Date.now()) {
|
|
615
|
+
for (const [peer, entry] of this.registrationAttempts) {
|
|
616
|
+
if (entry.expiresAt <= now)
|
|
617
|
+
this.registrationAttempts.delete(peer);
|
|
618
|
+
}
|
|
345
619
|
for (const [transactionId, transaction] of this.authorizeTransactions.entries()) {
|
|
346
620
|
if (transaction.expiresAt <= now) {
|
|
347
621
|
this.authorizeTransactions.delete(transactionId);
|