apple-notes-mcp 2.5.0 → 2.5.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.
|
@@ -2056,10 +2056,11 @@ export class AppleNotesManager {
|
|
|
2056
2056
|
*/
|
|
2057
2057
|
listAttachments(title, account) {
|
|
2058
2058
|
const targetAccount = this.resolveAccount(account);
|
|
2059
|
+
const safeAccount = escapePlainStringForAppleScript(targetAccount);
|
|
2059
2060
|
const safeTitle = escapePlainStringForAppleScript(title);
|
|
2060
2061
|
const script = `
|
|
2061
2062
|
tell application "Notes"
|
|
2062
|
-
tell account "${
|
|
2063
|
+
tell account "${safeAccount}"
|
|
2063
2064
|
set theNote to note "${safeTitle}"
|
|
2064
2065
|
set attachmentList to {}
|
|
2065
2066
|
repeat with a in attachments of theNote
|
|
@@ -315,6 +315,17 @@ describe("AppleNotesManager", () => {
|
|
|
315
315
|
manager = new AppleNotesManager();
|
|
316
316
|
vi.clearAllMocks();
|
|
317
317
|
});
|
|
318
|
+
describe("listAttachments — security", () => {
|
|
319
|
+
it("escapes the account name so it cannot break out of the AppleScript literal (injection regression)", () => {
|
|
320
|
+
mockExecuteAppleScript.mockReturnValue({ success: true, output: "" });
|
|
321
|
+
manager.listAttachments("My Note", 'evil" injected');
|
|
322
|
+
const script = String(mockExecuteAppleScript.mock.calls.at(-1)?.[0]);
|
|
323
|
+
// The account's double-quote must be escaped (\\") — a raw quote would
|
|
324
|
+
// terminate the tell-account string literal and allow `do shell script` injection.
|
|
325
|
+
expect(script).toContain('tell account "evil\\" injected"');
|
|
326
|
+
expect(script).not.toContain('tell account "evil" injected"');
|
|
327
|
+
});
|
|
328
|
+
});
|
|
318
329
|
// ---------------------------------------------------------------------------
|
|
319
330
|
// Note Creation
|
|
320
331
|
// ---------------------------------------------------------------------------
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* @module utils/checklistParser
|
|
25
25
|
* @see https://github.com/sweetrb/apple-notes-mcp/issues/2
|
|
26
26
|
*/
|
|
27
|
-
import {
|
|
27
|
+
import { execFileSync } from "child_process";
|
|
28
28
|
import * as zlib from "zlib";
|
|
29
29
|
import * as fs from "fs";
|
|
30
30
|
import * as path from "path";
|
|
@@ -43,7 +43,7 @@ export function hasFullDiskAccess() {
|
|
|
43
43
|
if (!fs.existsSync(NOTES_DB_PATH))
|
|
44
44
|
return false;
|
|
45
45
|
// Try to open the database with a simple query
|
|
46
|
-
|
|
46
|
+
execFileSync("sqlite3", ["-readonly", NOTES_DB_PATH, "SELECT 1;"], {
|
|
47
47
|
encoding: "utf8",
|
|
48
48
|
timeout: 3000,
|
|
49
49
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -75,7 +75,7 @@ function queryNoteData(noteId) {
|
|
|
75
75
|
// Query for the gzipped protobuf data, output as hex for safe transport
|
|
76
76
|
const query = `SELECT hex(nd.ZDATA) FROM ZICNOTEDATA nd JOIN ZICCLOUDSYNCINGOBJECT n ON nd.ZNOTE = n.Z_PK WHERE n.Z_PK = ${pk};`;
|
|
77
77
|
try {
|
|
78
|
-
const result =
|
|
78
|
+
const result = execFileSync("sqlite3", ["-readonly", NOTES_DB_PATH, query], {
|
|
79
79
|
encoding: "utf8",
|
|
80
80
|
timeout: 5000,
|
|
81
81
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -9,7 +9,7 @@ import * as zlib from "zlib";
|
|
|
9
9
|
import { getChecklistItems, hasFullDiskAccess } from "./checklistParser.js";
|
|
10
10
|
// Mock child_process to avoid actual database access
|
|
11
11
|
vi.mock("child_process", () => ({
|
|
12
|
-
|
|
12
|
+
execFileSync: vi.fn(),
|
|
13
13
|
spawnSync: vi.fn(() => ({ error: null })),
|
|
14
14
|
}));
|
|
15
15
|
// Mock fs for database existence checks
|
|
@@ -17,9 +17,9 @@ vi.mock("fs", () => ({
|
|
|
17
17
|
existsSync: vi.fn(() => true),
|
|
18
18
|
statSync: vi.fn(() => ({ mtimeMs: Date.now() })),
|
|
19
19
|
}));
|
|
20
|
-
import {
|
|
20
|
+
import { execFileSync } from "child_process";
|
|
21
21
|
import { existsSync } from "fs";
|
|
22
|
-
const mockExecSync = vi.mocked(
|
|
22
|
+
const mockExecSync = vi.mocked(execFileSync);
|
|
23
23
|
const mockExistsSync = vi.mocked(existsSync);
|
|
24
24
|
/**
|
|
25
25
|
* Builds a minimal Apple Notes protobuf structure with checklist items.
|
|
@@ -225,6 +225,6 @@ describe("getChecklistItems", () => {
|
|
|
225
225
|
it("extracts correct primary key from note ID", () => {
|
|
226
226
|
mockExecSync.mockReturnValue("");
|
|
227
227
|
getChecklistItems("x-coredata://12345-ABCDE/ICNote/p42");
|
|
228
|
-
expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining("Z_PK = 42"), expect.any(Object));
|
|
228
|
+
expect(mockExecSync).toHaveBeenCalledWith("sqlite3", expect.arrayContaining([expect.stringContaining("Z_PK = 42")]), expect.any(Object));
|
|
229
229
|
});
|
|
230
230
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apple-notes-mcp",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"description": "MCP server for Apple Notes - create, search, update, and manage notes via Claude and other AI assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"@types/turndown": "^5.0.6",
|
|
69
69
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
70
70
|
"@typescript-eslint/parser": "^8.0.0",
|
|
71
|
-
"@vitest/coverage-v8": "^2.
|
|
71
|
+
"@vitest/coverage-v8": "^3.2.6",
|
|
72
72
|
"eslint": "^9.0.0",
|
|
73
73
|
"globals": "^17.0.0",
|
|
74
74
|
"husky": "^9.1.7",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"tsconfig-paths": "^4.2.0",
|
|
79
79
|
"typescript": "^5.0.0",
|
|
80
80
|
"typescript-eslint": "^8.51.0",
|
|
81
|
-
"vitest": "^2.
|
|
81
|
+
"vitest": "^3.2.6"
|
|
82
82
|
},
|
|
83
83
|
"volta": {
|
|
84
84
|
"node": "24.17.0"
|