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 "${targetAccount}"
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 { execSync } from "child_process";
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
- execSync(`sqlite3 -readonly "${NOTES_DB_PATH}" "SELECT 1;"`, {
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 = execSync(`sqlite3 -readonly "${NOTES_DB_PATH}" "${query}"`, {
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
- execSync: vi.fn(),
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 { execSync } from "child_process";
20
+ import { execFileSync } from "child_process";
21
21
  import { existsSync } from "fs";
22
- const mockExecSync = vi.mocked(execSync);
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.0",
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.1.9",
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.0.0"
81
+ "vitest": "^3.2.6"
82
82
  },
83
83
  "volta": {
84
84
  "node": "24.17.0"