🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@getnote/mcp

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@getnote/mcp - npm Package Compare versions

Comparing version
1.5.0
to
1.5.1
+53
-3
dist/index.js

@@ -52,2 +52,51 @@ #!/usr/bin/env node

const SERVER_VERSION = pkg.version;
// ─── Security ────────────────────────────────────────────────────────────────
/**
* 校验并解析 upload_image 的 image_path,防止路径穿越(安全漏洞 #10)。
*
* 为什么要校验:upload_image 会把 image_path 指向的本地文件读出来并上传到公网
* CDN。如果不校验,攻击者只要能发起 MCP 调用,就能传入任意路径
* (如 "/etc/passwd"、"../../.ssh/id_rsa"),把宿主机上的任意敏感文件读出来
* 并外泄到 CDN —— 等同于任意文件读取漏洞。
*
* 校验策略:
* - 拒绝 null byte:底层 syscall 会在 \0 处截断字符串,可绕过后续校验。
* - 拒绝绝对路径(以 / 或 \ 开头,或平台判定为绝对路径)。
* - 拒绝包含 ".." 的路径段:避免跳出工作目录 / 允许目录。
* - 若设置了 GETNOTE_MCP_ALLOWED_ROOT:用 realpathSync + resolve 解开符号链接
* 后,确保最终真实路径严格位于该目录内(防止软链逃逸)。
* - 若未设置:默认只允许相对路径(上面已拒绝绝对路径与 ".."),相对当前
* 工作目录读取。
*
* @returns 经校验、可安全读取的最终路径。
*/
function resolveSafeImagePath(imagePath) {
if (typeof imagePath !== "string" || imagePath.length === 0) {
throw new Error("image_path must be a non-empty string");
}
// null byte 会截断底层路径字符串,从而绕过后续的字符串校验
if (imagePath.includes("\0")) {
throw new Error("image_path must not contain null bytes");
}
// 绝对路径直接拒绝(覆盖 POSIX 的 "/"、Windows 的 "\" 与盘符)
if ((0, node_path_1.isAbsolute)(imagePath) || /^[/\\]/.test(imagePath)) {
throw new Error("image_path must be a relative path; absolute paths are not allowed");
}
// 任意 ".." 路径段都可能用来跳出允许目录
if (imagePath.split(/[/\\]/).includes("..")) {
throw new Error('image_path must not contain ".." path segments');
}
const allowedRoot = process.env.GETNOTE_MCP_ALLOWED_ROOT;
if (allowedRoot) {
// realpathSync 解开符号链接,防止用软链把路径指向允许目录之外的文件
const rootReal = (0, node_fs_1.realpathSync)((0, node_path_1.resolve)(allowedRoot));
const target = (0, node_fs_1.realpathSync)((0, node_path_1.resolve)(rootReal, imagePath));
if (target !== rootReal && !target.startsWith(rootReal + node_path_1.sep)) {
throw new Error("image_path resolves outside of GETNOTE_MCP_ALLOWED_ROOT");
}
return target;
}
// 未配置允许目录:仅允许相对路径(已校验),相对 cwd 解析
return (0, node_path_1.resolve)(process.cwd(), imagePath);
}
// ─── Config ──────────────────────────────────────────────────────────────────

@@ -384,3 +433,3 @@ function getApiKey() {

type: "string",
description: "本地图片文件路径",
description: "本地图片文件路径。出于安全考虑仅接受相对路径:不允许绝对路径(以 / 或 \\ 开头)、不允许包含 \"..\" 的路径段、不允许 null byte。默认相对当前工作目录解析;若设置了环境变量 GETNOTE_MCP_ALLOWED_ROOT,则路径必须落在该目录内(含符号链接解析后)。",
},

@@ -700,4 +749,5 @@ image_base64: {

if (input.image_path) {
// 从文件路径读取
imageData = fs.readFileSync(input.image_path);
// 从文件路径读取 —— 先做路径安全校验,防止任意文件读取(漏洞 #10)
const safePath = resolveSafeImagePath(input.image_path);
imageData = fs.readFileSync(safePath);
}

@@ -704,0 +754,0 @@ else if (input.image_base64) {

+5
-4
{
"name": "@getnote/mcp",
"version": "1.5.0",
"version": "1.5.1",
"description": "MCP server for 得到大脑(Get笔记) — manage notes and knowledge bases from AI assistants",

@@ -27,3 +27,3 @@ "main": "dist/index.js",

"axios": "^1.16.1",
"form-data": "^4.0.0",
"form-data": "^4.0.6",
"zod": "^3.22.0"

@@ -40,8 +40,9 @@ },

"express-rate-limit": "^8.2.2",
"hono": "^4.12.18",
"hono": "^4.12.21",
"path-to-regexp": "^8.4.1",
"fast-uri": "^3.1.2",
"ip-address": "^10.2.0",
"axios": "^1.16.1"
"axios": "^1.16.1",
"qs": "^6.15.2"
}
}

@@ -19,4 +19,4 @@ #!/usr/bin/env node

import { GetNoteClient, GetNoteAPIError, SaveNoteReq, UpdateNoteReq } from "./client.js";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { readFileSync, realpathSync } from "node:fs";
import { join, resolve, isAbsolute, sep } from "node:path";

@@ -28,2 +28,59 @@ const pkg = JSON.parse(

// ─── Security ────────────────────────────────────────────────────────────────
/**
* 校验并解析 upload_image 的 image_path,防止路径穿越(安全漏洞 #10)。
*
* 为什么要校验:upload_image 会把 image_path 指向的本地文件读出来并上传到公网
* CDN。如果不校验,攻击者只要能发起 MCP 调用,就能传入任意路径
* (如 "/etc/passwd"、"../../.ssh/id_rsa"),把宿主机上的任意敏感文件读出来
* 并外泄到 CDN —— 等同于任意文件读取漏洞。
*
* 校验策略:
* - 拒绝 null byte:底层 syscall 会在 \0 处截断字符串,可绕过后续校验。
* - 拒绝绝对路径(以 / 或 \ 开头,或平台判定为绝对路径)。
* - 拒绝包含 ".." 的路径段:避免跳出工作目录 / 允许目录。
* - 若设置了 GETNOTE_MCP_ALLOWED_ROOT:用 realpathSync + resolve 解开符号链接
* 后,确保最终真实路径严格位于该目录内(防止软链逃逸)。
* - 若未设置:默认只允许相对路径(上面已拒绝绝对路径与 ".."),相对当前
* 工作目录读取。
*
* @returns 经校验、可安全读取的最终路径。
*/
function resolveSafeImagePath(imagePath: string): string {
if (typeof imagePath !== "string" || imagePath.length === 0) {
throw new Error("image_path must be a non-empty string");
}
// null byte 会截断底层路径字符串,从而绕过后续的字符串校验
if (imagePath.includes("\0")) {
throw new Error("image_path must not contain null bytes");
}
// 绝对路径直接拒绝(覆盖 POSIX 的 "/"、Windows 的 "\" 与盘符)
if (isAbsolute(imagePath) || /^[/\\]/.test(imagePath)) {
throw new Error(
"image_path must be a relative path; absolute paths are not allowed"
);
}
// 任意 ".." 路径段都可能用来跳出允许目录
if (imagePath.split(/[/\\]/).includes("..")) {
throw new Error('image_path must not contain ".." path segments');
}
const allowedRoot = process.env.GETNOTE_MCP_ALLOWED_ROOT;
if (allowedRoot) {
// realpathSync 解开符号链接,防止用软链把路径指向允许目录之外的文件
const rootReal = realpathSync(resolve(allowedRoot));
const target = realpathSync(resolve(rootReal, imagePath));
if (target !== rootReal && !target.startsWith(rootReal + sep)) {
throw new Error(
"image_path resolves outside of GETNOTE_MCP_ALLOWED_ROOT"
);
}
return target;
}
// 未配置允许目录:仅允许相对路径(已校验),相对 cwd 解析
return resolve(process.cwd(), imagePath);
}
// ─── Config ──────────────────────────────────────────────────────────────────

@@ -378,3 +435,4 @@

type: "string",
description: "本地图片文件路径",
description:
"本地图片文件路径。出于安全考虑仅接受相对路径:不允许绝对路径(以 / 或 \\ 开头)、不允许包含 \"..\" 的路径段、不允许 null byte。默认相对当前工作目录解析;若设置了环境变量 GETNOTE_MCP_ALLOWED_ROOT,则路径必须落在该目录内(含符号链接解析后)。",
},

@@ -724,4 +782,5 @@ image_base64: {

if (input.image_path) {
// 从文件路径读取
imageData = fs.readFileSync(input.image_path as string);
// 从文件路径读取 —— 先做路径安全校验,防止任意文件读取(漏洞 #10)
const safePath = resolveSafeImagePath(input.image_path as string);
imageData = fs.readFileSync(safePath);
} else if (input.image_base64) {

@@ -728,0 +787,0 @@ // 从 Base64 解码