@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 +6 -0
- package/dist/index.js +4 -1
- package/dist/safe-path.js +28 -0
- package/package.json +2 -2
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
|
|
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.
|
|
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",
|