@coinpaprika/mcp 1.4.0 → 1.4.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 CHANGED
@@ -4,6 +4,12 @@ All notable changes to the CoinPaprika MCP Server will be documented in this fil
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.4.1] - 2026-10-21
8
+
9
+ ### Security
10
+
11
+ - **An id could change which API path a tool called.** Ids go into the request path through `encodeURIComponent`, which leaves `.` and `..` as they are, so an id of `..` moved the request, with your API key on it, to a different path. Every request now has its path checked before it is sent: a path that changes when parsed, or a segment that decodes to `.` or `..` or holds `/`, `\` or `%`, is refused with "Invalid identifier". No real coin, exchange, tag, person, platform or contract id is affected.
12
+
7
13
  ## [1.4.0] - 2026-08-19
8
14
 
9
15
  ### Fixed
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { createRequire } from 'module';
4
4
  import fetch from 'node-fetch';
5
+ import { assertPathUnchanged } from './safe-path.js';
5
6
  import { z } from 'zod';
6
7
 
7
8
  // Server version, read from package.json so the banner, capabilities
@@ -195,7 +196,9 @@ function clamp(n, min, max) {
195
196
  // Helper function to fetch data from CoinPaprika API with structured error handling
196
197
  async function fetchFromAPI(endpoint) {
197
198
  const startTime = Date.now();
198
- const response = await fetch(`${API_BASE_URL}${endpoint}`, { headers: buildHeaders() });
199
+ const url = `${API_BASE_URL}${endpoint}`;
200
+ assertPathUnchanged(url); // before the key leaves the process
201
+ const response = await fetch(url, { headers: buildHeaders() });
199
202
 
200
203
  if (!response.ok) {
201
204
  const structuredError = parseAPIError(response.status, response.statusText, endpoint);
@@ -0,0 +1,28 @@
1
+ // Ids reach the upstream path through encodeURIComponent, which encodes "/", "?" and "#" but
2
+ // leaves "." and "..". An id of ".." would make the request, with your API key on it, go to a
3
+ // different path than the tool intended, because URL parsing resolves dot segments. And an id
4
+ // of "../key/info" encodes to "..%2Fkey%2Finfo", which URL parsing leaves alone but a server
5
+ // that decodes before routing might not. So refuse any URL whose path changes when parsed, and
6
+ // any path segment that, decoded once, is "." or ".." or holds "/", "\" or "%". No real coin,
7
+ // exchange, tag, person, platform or contract id contains any of these.
8
+ export function assertPathUnchanged(url) {
9
+ const afterScheme = url.indexOf('://');
10
+ const pathStart = afterScheme === -1 ? -1 : url.indexOf('/', afterScheme + 3);
11
+ if (pathStart === -1) return;
12
+ const end = url.slice(pathStart).search(/[?#]/);
13
+ const rawPath = end === -1 ? url.slice(pathStart) : url.slice(pathStart, pathStart + end);
14
+ if (new URL(url).pathname !== rawPath) {
15
+ throw new Error('Invalid identifier: an id may not be "." or "..".');
16
+ }
17
+ for (const seg of rawPath.split('/').slice(1)) {
18
+ let decoded;
19
+ try {
20
+ decoded = decodeURIComponent(seg);
21
+ } catch {
22
+ throw new Error('Invalid identifier.');
23
+ }
24
+ if (decoded === '.' || decoded === '..' || /[\\/%]/.test(decoded)) {
25
+ throw new Error('Invalid identifier.');
26
+ }
27
+ }
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coinpaprika/mcp",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "A Model Context Protocol server for CoinPaprika cryptocurrency market data: prices, tickers, exchanges, and more",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -17,7 +17,7 @@
17
17
  "build": "mkdir -p dist && cp -r src/* dist/ && chmod +x dist/bin.js",
18
18
  "prepublishOnly": "npm run build",
19
19
  "start": "node src/index.js",
20
- "test": "node test/smoke.js"
20
+ "test": "node test/path_safety.js && node test/smoke.js"
21
21
  },
22
22
  "keywords": [
23
23
  "mcp",