pinelabs-mcp 1.0.5 → 1.0.6

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/README.md CHANGED
@@ -119,7 +119,7 @@ See the [complete tools reference](https://developer.pinelabsonline.com/docs/mcp
119
119
  |-------|-----|
120
120
  | Missing credentials | Run `npx pinelabs-mcp configure` or set `PINELABS_CLIENT_ID` / `PINELABS_CLIENT_SECRET` in your MCP client's `env` block |
121
121
  | Tools not appearing | Restart your AI assistant; verify Node.js 18+ (`node --version`); set `"DEBUG": "true"` in `env` |
122
- | Connection issues | Verify credentials; check network access to `mcp.pluralpay.in`; allow HTTPS if behind a proxy |
122
+ | Connection issues | Verify credentials; check network access to `mcp.pinelabs.com`; allow HTTPS if behind a proxy |
123
123
 
124
124
  ## License
125
125
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinelabs-mcp",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "MCP client for Pine Labs payment gateway — connect Claude Desktop, Cursor, and VS Code to Pine Labs payment APIs using your client credentials.",
5
5
  "main": "src/config.js",
6
6
  "bin": {
@@ -31,24 +31,51 @@ function startProxy() {
31
31
 
32
32
  const mcpRemoteBin = require.resolve("mcp-remote/dist/proxy.js");
33
33
 
34
- const args = [
35
- mcpRemoteBin,
34
+ // SECURITY: Do NOT place client secret in spawn argv — process arguments are
35
+ // visible to other local users/processes via `ps`, /proc, monitoring agents,
36
+ // and crash dumps. Instead we:
37
+ // 1. Pass the secret to the child via a dedicated env var.
38
+ // 2. Use `node -e <bootstrap>` so the OS-visible command line never
39
+ // contains the secret.
40
+ // 3. The bootstrap reads the secret from env, deletes it from
41
+ // `process.env`, rebuilds `process.argv`, then loads mcp-remote
42
+ // in-process via `require()` (no further child spawn).
43
+ const proxyArgs = [
36
44
  endpoint,
37
45
  "--header",
38
46
  `X-Client-Id:${clientId}`,
39
- "--header",
40
- `X-Client-Secret:${clientSecret}`,
41
47
  "--transport",
42
48
  "http-first",
43
49
  ];
44
50
 
45
51
  if (debug) {
46
- args.push("--debug");
52
+ proxyArgs.push("--debug");
47
53
  }
48
54
 
49
- const child = spawn(process.execPath, args, {
55
+ const SECRET_ENV_VAR = "PINELABS_MCP_CLIENT_SECRET_INTERNAL";
56
+ const bootstrap = `
57
+ const secret = process.env[${JSON.stringify(SECRET_ENV_VAR)}];
58
+ if (!secret) {
59
+ process.stderr.write("[pinelabs-mcp] Missing client secret in child process environment\\n");
60
+ process.exit(1);
61
+ }
62
+ delete process.env[${JSON.stringify(SECRET_ENV_VAR)}];
63
+ process.argv = [
64
+ process.execPath,
65
+ ${JSON.stringify(mcpRemoteBin)},
66
+ ...${JSON.stringify(proxyArgs)},
67
+ "--header",
68
+ "X-Client-Secret:" + secret,
69
+ ];
70
+ require(${JSON.stringify(mcpRemoteBin)});
71
+ `;
72
+
73
+ const child = spawn(process.execPath, ["-e", bootstrap], {
50
74
  stdio: ["pipe", "pipe", "pipe"],
51
- env: { ...process.env },
75
+ env: {
76
+ ...process.env,
77
+ [SECRET_ENV_VAR]: clientSecret,
78
+ },
52
79
  });
53
80
 
54
81
  process.stdin.pipe(child.stdin);
package/src/config.js CHANGED
@@ -9,10 +9,51 @@
9
9
  */
10
10
 
11
11
  const ENDPOINTS = {
12
- prod: "https://mcp.pluralpay.in/mcp",
12
+ prod: "https://mcp.pinelabs.com/mcp",
13
13
  uat: "https://pluralai.v2.pinepg.in/mcp",
14
14
  };
15
15
 
16
+ const LOCALHOST_HOSTNAMES = new Set([
17
+ "localhost",
18
+ "127.0.0.1",
19
+ "::1",
20
+ "0.0.0.0",
21
+ ]);
22
+
23
+ /**
24
+ * Validate that an endpoint URL is safe to send credentials to.
25
+ *
26
+ * Enforces HTTPS for any non-localhost host so that the X-Client-Id /
27
+ * X-Client-Secret headers are never transmitted in cleartext.
28
+ *
29
+ * @param {string} rawUrl - The endpoint URL to validate.
30
+ * @throws {Error} If the URL is malformed or uses http:// for a non-local host.
31
+ */
32
+ function validateEndpointUrl(rawUrl) {
33
+ let parsed;
34
+ try {
35
+ parsed = new URL(rawUrl);
36
+ } catch {
37
+ throw new Error(
38
+ `Invalid PINELABS_MCP_ENDPOINT: "${rawUrl}" is not a valid URL.`,
39
+ );
40
+ }
41
+
42
+ if (parsed.protocol === "https:") {
43
+ return;
44
+ }
45
+
46
+ if (parsed.protocol === "http:" && LOCALHOST_HOSTNAMES.has(parsed.hostname)) {
47
+ return;
48
+ }
49
+
50
+ throw new Error(
51
+ `Insecure PINELABS_MCP_ENDPOINT: "${rawUrl}". ` +
52
+ "Endpoint must use https:// (http:// is only allowed for localhost). " +
53
+ "Sending credentials over plain HTTP would expose your client secret.",
54
+ );
55
+ }
56
+
16
57
  /**
17
58
  * Parse and validate configuration from environment variables.
18
59
  *
@@ -84,7 +125,9 @@ function loadConfig() {
84
125
  endpoint = ENDPOINTS.uat;
85
126
  }
86
127
 
128
+ validateEndpointUrl(endpoint);
129
+
87
130
  return { clientId, clientSecret, endpoint, debug };
88
131
  }
89
132
 
90
- module.exports = { loadConfig, ENDPOINTS };
133
+ module.exports = { loadConfig, ENDPOINTS, validateEndpointUrl };