obsidian-mcp-server 2.0.7 → 3.0.0

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.
Files changed (253) hide show
  1. package/CLAUDE.md +364 -0
  2. package/Dockerfile +99 -0
  3. package/LICENSE +4 -6
  4. package/README.md +246 -206
  5. package/changelog/3.0.x/3.0.0.md +102 -0
  6. package/changelog/template.md +51 -0
  7. package/dist/config/server-config.d.ts +19 -0
  8. package/dist/config/server-config.d.ts.map +1 -0
  9. package/dist/config/server-config.js +55 -0
  10. package/dist/config/server-config.js.map +1 -0
  11. package/dist/index.d.ts +7 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +23 -295
  14. package/dist/index.js.map +1 -0
  15. package/dist/mcp-server/prompts/definitions/index.d.ts +8 -0
  16. package/dist/mcp-server/prompts/definitions/index.d.ts.map +1 -0
  17. package/dist/mcp-server/prompts/definitions/index.js +8 -0
  18. package/dist/mcp-server/prompts/definitions/index.js.map +1 -0
  19. package/dist/mcp-server/resources/definitions/index.d.ts +36 -0
  20. package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -0
  21. package/dist/mcp-server/resources/definitions/index.js +9 -0
  22. package/dist/mcp-server/resources/definitions/index.js.map +1 -0
  23. package/dist/mcp-server/resources/definitions/obsidian-status.resource.d.ts +23 -0
  24. package/dist/mcp-server/resources/definitions/obsidian-status.resource.d.ts.map +1 -0
  25. package/dist/mcp-server/resources/definitions/obsidian-status.resource.js +47 -0
  26. package/dist/mcp-server/resources/definitions/obsidian-status.resource.js.map +1 -0
  27. package/dist/mcp-server/resources/definitions/obsidian-tags.resource.d.ts +13 -0
  28. package/dist/mcp-server/resources/definitions/obsidian-tags.resource.d.ts.map +1 -0
  29. package/dist/mcp-server/resources/definitions/obsidian-tags.resource.js +30 -0
  30. package/dist/mcp-server/resources/definitions/obsidian-tags.resource.js.map +1 -0
  31. package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.d.ts +21 -0
  32. package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.d.ts.map +1 -0
  33. package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.js +38 -0
  34. package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.js.map +1 -0
  35. package/dist/mcp-server/tools/definitions/_shared/schemas.d.ts +45 -0
  36. package/dist/mcp-server/tools/definitions/_shared/schemas.d.ts.map +1 -0
  37. package/dist/mcp-server/tools/definitions/_shared/schemas.js +66 -0
  38. package/dist/mcp-server/tools/definitions/_shared/schemas.js.map +1 -0
  39. package/dist/mcp-server/tools/definitions/_shared/suggest-paths.d.ts +51 -0
  40. package/dist/mcp-server/tools/definitions/_shared/suggest-paths.d.ts.map +1 -0
  41. package/dist/mcp-server/tools/definitions/_shared/suggest-paths.js +120 -0
  42. package/dist/mcp-server/tools/definitions/_shared/suggest-paths.js.map +1 -0
  43. package/dist/mcp-server/tools/definitions/index.d.ts +531 -0
  44. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -0
  45. package/dist/mcp-server/tools/definitions/index.js +38 -0
  46. package/dist/mcp-server/tools/definitions/index.js.map +1 -0
  47. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts +42 -0
  48. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts.map +1 -0
  49. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js +58 -0
  50. package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js.map +1 -0
  51. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts +46 -0
  52. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts.map +1 -0
  53. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js +66 -0
  54. package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js.map +1 -0
  55. package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.d.ts +19 -0
  56. package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.d.ts.map +1 -0
  57. package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.js +43 -0
  58. package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.js.map +1 -0
  59. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts +92 -0
  60. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts.map +1 -0
  61. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js +245 -0
  62. package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js.map +1 -0
  63. package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.d.ts +13 -0
  64. package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.d.ts.map +1 -0
  65. package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.js +38 -0
  66. package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.js.map +1 -0
  67. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts +59 -0
  68. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts.map +1 -0
  69. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js +273 -0
  70. package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js.map +1 -0
  71. package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts +13 -0
  72. package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts.map +1 -0
  73. package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js +38 -0
  74. package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js.map +1 -0
  75. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts +68 -0
  76. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts.map +1 -0
  77. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js +178 -0
  78. package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js.map +1 -0
  79. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts +77 -0
  80. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts.map +1 -0
  81. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js +172 -0
  82. package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js.map +1 -0
  83. package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts +22 -0
  84. package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts.map +1 -0
  85. package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js +88 -0
  86. package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js.map +1 -0
  87. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts +81 -0
  88. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts.map +1 -0
  89. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js +83 -0
  90. package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js.map +1 -0
  91. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts +59 -0
  92. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts.map +1 -0
  93. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js +167 -0
  94. package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js.map +1 -0
  95. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts +76 -0
  96. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts.map +1 -0
  97. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js +244 -0
  98. package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js.map +1 -0
  99. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts +41 -0
  100. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts.map +1 -0
  101. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js +76 -0
  102. package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js.map +1 -0
  103. package/dist/services/obsidian/frontmatter-ops.d.ts +34 -0
  104. package/dist/services/obsidian/frontmatter-ops.d.ts.map +1 -0
  105. package/dist/services/obsidian/frontmatter-ops.js +230 -0
  106. package/dist/services/obsidian/frontmatter-ops.js.map +1 -0
  107. package/dist/services/obsidian/obsidian-service.d.ts +73 -0
  108. package/dist/services/obsidian/obsidian-service.d.ts.map +1 -0
  109. package/dist/services/obsidian/obsidian-service.js +399 -0
  110. package/dist/services/obsidian/obsidian-service.js.map +1 -0
  111. package/dist/services/obsidian/section-extractor.d.ts +13 -0
  112. package/dist/services/obsidian/section-extractor.d.ts.map +1 -0
  113. package/dist/services/obsidian/section-extractor.js +124 -0
  114. package/dist/services/obsidian/section-extractor.js.map +1 -0
  115. package/dist/services/obsidian/types.d.ts +91 -0
  116. package/dist/services/obsidian/types.d.ts.map +1 -0
  117. package/dist/services/obsidian/types.js +7 -0
  118. package/dist/services/obsidian/types.js.map +1 -0
  119. package/package.json +63 -69
  120. package/server.json +167 -0
  121. package/CHANGELOG.md +0 -124
  122. package/dist/config/index.d.ts +0 -41
  123. package/dist/config/index.js +0 -191
  124. package/dist/mcp-server/server.d.ts +0 -33
  125. package/dist/mcp-server/server.js +0 -211
  126. package/dist/mcp-server/tools/obsidianDeleteNoteTool/index.d.ts +0 -12
  127. package/dist/mcp-server/tools/obsidianDeleteNoteTool/index.js +0 -12
  128. package/dist/mcp-server/tools/obsidianDeleteNoteTool/logic.d.ts +0 -51
  129. package/dist/mcp-server/tools/obsidianDeleteNoteTool/logic.js +0 -168
  130. package/dist/mcp-server/tools/obsidianDeleteNoteTool/registration.d.ts +0 -19
  131. package/dist/mcp-server/tools/obsidianDeleteNoteTool/registration.js +0 -91
  132. package/dist/mcp-server/tools/obsidianGlobalSearchTool/index.d.ts +0 -12
  133. package/dist/mcp-server/tools/obsidianGlobalSearchTool/index.js +0 -12
  134. package/dist/mcp-server/tools/obsidianGlobalSearchTool/logic.d.ts +0 -77
  135. package/dist/mcp-server/tools/obsidianGlobalSearchTool/logic.js +0 -341
  136. package/dist/mcp-server/tools/obsidianGlobalSearchTool/registration.d.ts +0 -18
  137. package/dist/mcp-server/tools/obsidianGlobalSearchTool/registration.js +0 -69
  138. package/dist/mcp-server/tools/obsidianListNotesTool/index.d.ts +0 -12
  139. package/dist/mcp-server/tools/obsidianListNotesTool/index.js +0 -12
  140. package/dist/mcp-server/tools/obsidianListNotesTool/logic.d.ts +0 -68
  141. package/dist/mcp-server/tools/obsidianListNotesTool/logic.js +0 -215
  142. package/dist/mcp-server/tools/obsidianListNotesTool/registration.d.ts +0 -23
  143. package/dist/mcp-server/tools/obsidianListNotesTool/registration.js +0 -98
  144. package/dist/mcp-server/tools/obsidianManageFrontmatterTool/index.d.ts +0 -3
  145. package/dist/mcp-server/tools/obsidianManageFrontmatterTool/index.js +0 -2
  146. package/dist/mcp-server/tools/obsidianManageFrontmatterTool/logic.d.ts +0 -42
  147. package/dist/mcp-server/tools/obsidianManageFrontmatterTool/logic.js +0 -152
  148. package/dist/mcp-server/tools/obsidianManageFrontmatterTool/registration.d.ts +0 -3
  149. package/dist/mcp-server/tools/obsidianManageFrontmatterTool/registration.js +0 -52
  150. package/dist/mcp-server/tools/obsidianManageTagsTool/index.d.ts +0 -3
  151. package/dist/mcp-server/tools/obsidianManageTagsTool/index.js +0 -2
  152. package/dist/mcp-server/tools/obsidianManageTagsTool/logic.d.ts +0 -28
  153. package/dist/mcp-server/tools/obsidianManageTagsTool/logic.js +0 -161
  154. package/dist/mcp-server/tools/obsidianManageTagsTool/registration.d.ts +0 -3
  155. package/dist/mcp-server/tools/obsidianManageTagsTool/registration.js +0 -52
  156. package/dist/mcp-server/tools/obsidianReadNoteTool/index.d.ts +0 -12
  157. package/dist/mcp-server/tools/obsidianReadNoteTool/index.js +0 -12
  158. package/dist/mcp-server/tools/obsidianReadNoteTool/logic.d.ts +0 -87
  159. package/dist/mcp-server/tools/obsidianReadNoteTool/logic.js +0 -216
  160. package/dist/mcp-server/tools/obsidianReadNoteTool/registration.d.ts +0 -20
  161. package/dist/mcp-server/tools/obsidianReadNoteTool/registration.js +0 -101
  162. package/dist/mcp-server/tools/obsidianSearchReplaceTool/index.d.ts +0 -12
  163. package/dist/mcp-server/tools/obsidianSearchReplaceTool/index.js +0 -12
  164. package/dist/mcp-server/tools/obsidianSearchReplaceTool/logic.d.ts +0 -255
  165. package/dist/mcp-server/tools/obsidianSearchReplaceTool/logic.js +0 -583
  166. package/dist/mcp-server/tools/obsidianSearchReplaceTool/registration.d.ts +0 -22
  167. package/dist/mcp-server/tools/obsidianSearchReplaceTool/registration.js +0 -111
  168. package/dist/mcp-server/tools/obsidianUpdateNoteTool/index.d.ts +0 -12
  169. package/dist/mcp-server/tools/obsidianUpdateNoteTool/index.js +0 -12
  170. package/dist/mcp-server/tools/obsidianUpdateNoteTool/logic.d.ts +0 -183
  171. package/dist/mcp-server/tools/obsidianUpdateNoteTool/logic.js +0 -490
  172. package/dist/mcp-server/tools/obsidianUpdateNoteTool/registration.d.ts +0 -21
  173. package/dist/mcp-server/tools/obsidianUpdateNoteTool/registration.js +0 -108
  174. package/dist/mcp-server/transports/auth/core/authContext.d.ts +0 -33
  175. package/dist/mcp-server/transports/auth/core/authContext.js +0 -24
  176. package/dist/mcp-server/transports/auth/core/authTypes.d.ts +0 -17
  177. package/dist/mcp-server/transports/auth/core/authTypes.js +0 -5
  178. package/dist/mcp-server/transports/auth/core/authUtils.d.ts +0 -18
  179. package/dist/mcp-server/transports/auth/core/authUtils.js +0 -45
  180. package/dist/mcp-server/transports/auth/index.d.ts +0 -10
  181. package/dist/mcp-server/transports/auth/index.js +0 -9
  182. package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.d.ts +0 -27
  183. package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.js +0 -149
  184. package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.d.ts +0 -20
  185. package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.js +0 -124
  186. package/dist/mcp-server/transports/httpErrorHandler.d.ts +0 -26
  187. package/dist/mcp-server/transports/httpErrorHandler.js +0 -73
  188. package/dist/mcp-server/transports/httpTransport.d.ts +0 -21
  189. package/dist/mcp-server/transports/httpTransport.js +0 -208
  190. package/dist/mcp-server/transports/stdioTransport.d.ts +0 -42
  191. package/dist/mcp-server/transports/stdioTransport.js +0 -63
  192. package/dist/services/obsidianRestAPI/index.d.ts +0 -15
  193. package/dist/services/obsidianRestAPI/index.js +0 -17
  194. package/dist/services/obsidianRestAPI/methods/activeFileMethods.d.ts +0 -38
  195. package/dist/services/obsidianRestAPI/methods/activeFileMethods.js +0 -62
  196. package/dist/services/obsidianRestAPI/methods/commandMethods.d.ts +0 -22
  197. package/dist/services/obsidianRestAPI/methods/commandMethods.js +0 -31
  198. package/dist/services/obsidianRestAPI/methods/openMethods.d.ts +0 -16
  199. package/dist/services/obsidianRestAPI/methods/openMethods.js +0 -21
  200. package/dist/services/obsidianRestAPI/methods/patchMethods.d.ts +0 -37
  201. package/dist/services/obsidianRestAPI/methods/patchMethods.js +0 -94
  202. package/dist/services/obsidianRestAPI/methods/periodicNoteMethods.d.ts +0 -42
  203. package/dist/services/obsidianRestAPI/methods/periodicNoteMethods.js +0 -66
  204. package/dist/services/obsidianRestAPI/methods/searchMethods.d.ts +0 -25
  205. package/dist/services/obsidianRestAPI/methods/searchMethods.js +0 -36
  206. package/dist/services/obsidianRestAPI/methods/vaultMethods.d.ts +0 -58
  207. package/dist/services/obsidianRestAPI/methods/vaultMethods.js +0 -144
  208. package/dist/services/obsidianRestAPI/service.d.ts +0 -195
  209. package/dist/services/obsidianRestAPI/service.js +0 -379
  210. package/dist/services/obsidianRestAPI/types.d.ts +0 -127
  211. package/dist/services/obsidianRestAPI/types.js +0 -7
  212. package/dist/services/obsidianRestAPI/vaultCache/index.d.ts +0 -4
  213. package/dist/services/obsidianRestAPI/vaultCache/index.js +0 -4
  214. package/dist/services/obsidianRestAPI/vaultCache/service.d.ts +0 -88
  215. package/dist/services/obsidianRestAPI/vaultCache/service.js +0 -299
  216. package/dist/types-global/errors.d.ts +0 -73
  217. package/dist/types-global/errors.js +0 -71
  218. package/dist/utils/index.d.ts +0 -5
  219. package/dist/utils/index.js +0 -13
  220. package/dist/utils/internal/asyncUtils.d.ts +0 -54
  221. package/dist/utils/internal/asyncUtils.js +0 -101
  222. package/dist/utils/internal/errorHandler.d.ts +0 -176
  223. package/dist/utils/internal/errorHandler.js +0 -351
  224. package/dist/utils/internal/index.d.ts +0 -4
  225. package/dist/utils/internal/index.js +0 -4
  226. package/dist/utils/internal/logger.d.ts +0 -141
  227. package/dist/utils/internal/logger.js +0 -406
  228. package/dist/utils/internal/requestContext.d.ts +0 -83
  229. package/dist/utils/internal/requestContext.js +0 -72
  230. package/dist/utils/metrics/index.d.ts +0 -1
  231. package/dist/utils/metrics/index.js +0 -1
  232. package/dist/utils/metrics/tokenCounter.d.ts +0 -27
  233. package/dist/utils/metrics/tokenCounter.js +0 -128
  234. package/dist/utils/obsidian/index.d.ts +0 -5
  235. package/dist/utils/obsidian/index.js +0 -5
  236. package/dist/utils/obsidian/obsidianApiUtils.d.ts +0 -14
  237. package/dist/utils/obsidian/obsidianApiUtils.js +0 -29
  238. package/dist/utils/obsidian/obsidianStatUtils.d.ts +0 -68
  239. package/dist/utils/obsidian/obsidianStatUtils.js +0 -143
  240. package/dist/utils/parsing/dateParser.d.ts +0 -56
  241. package/dist/utils/parsing/dateParser.js +0 -104
  242. package/dist/utils/parsing/index.d.ts +0 -2
  243. package/dist/utils/parsing/index.js +0 -3
  244. package/dist/utils/parsing/jsonParser.d.ts +0 -80
  245. package/dist/utils/parsing/jsonParser.js +0 -133
  246. package/dist/utils/security/idGenerator.d.ts +0 -140
  247. package/dist/utils/security/idGenerator.js +0 -194
  248. package/dist/utils/security/index.d.ts +0 -3
  249. package/dist/utils/security/index.js +0 -3
  250. package/dist/utils/security/rateLimiter.d.ts +0 -156
  251. package/dist/utils/security/rateLimiter.js +0 -235
  252. package/dist/utils/security/sanitization.d.ts +0 -244
  253. package/dist/utils/security/sanitization.js +0 -599
@@ -1,599 +0,0 @@
1
- /**
2
- * @fileoverview Provides a comprehensive sanitization utility class for various input types,
3
- * including HTML, strings, URLs, file paths, JSON, and numbers. It also includes
4
- * functionality for redacting sensitive information from objects for safe logging.
5
- * @module src/utils/security/sanitization
6
- */
7
- import path from "path";
8
- import sanitizeHtml from "sanitize-html";
9
- import validator from "validator";
10
- import { BaseErrorCode, McpError } from "../../types-global/errors.js";
11
- import { logger, requestContextService, } from "../internal/index.js"; // Use internal index
12
- /**
13
- * A singleton utility class for performing various input sanitization tasks.
14
- * It provides methods to clean and validate strings, HTML, URLs, file paths, JSON,
15
- * and numbers, and to redact sensitive data for logging.
16
- */
17
- export class Sanitization {
18
- constructor() {
19
- this.sensitiveFields = [
20
- "password",
21
- "token",
22
- "secret",
23
- "key",
24
- "apiKey",
25
- "auth",
26
- "credential",
27
- "jwt",
28
- "ssn",
29
- "credit",
30
- "card",
31
- "cvv",
32
- "authorization",
33
- "passphrase",
34
- "privatekey", // Added more common sensitive field names
35
- "obsidianapikey", // Specific to this project potentially
36
- ];
37
- this.defaultHtmlSanitizeConfig = {
38
- allowedTags: [
39
- "h1",
40
- "h2",
41
- "h3",
42
- "h4",
43
- "h5",
44
- "h6",
45
- "p",
46
- "a",
47
- "ul",
48
- "ol",
49
- "li",
50
- "b",
51
- "i",
52
- "strong",
53
- "em",
54
- "strike",
55
- "code",
56
- "hr",
57
- "br",
58
- "div",
59
- "table",
60
- "thead",
61
- "tbody",
62
- "tr",
63
- "th",
64
- "td",
65
- "pre",
66
- "blockquote", // Added blockquote
67
- ],
68
- allowedAttributes: {
69
- a: ["href", "name", "target", "title"], // Added title for links
70
- img: ["src", "alt", "title", "width", "height"],
71
- "*": ["class", "id", "style", "data-*"], // Allow data-* attributes
72
- },
73
- preserveComments: false,
74
- };
75
- // Singleton constructor
76
- }
77
- /**
78
- * Gets the singleton instance of the `Sanitization` class.
79
- * @returns {Sanitization} The singleton instance.
80
- */
81
- static getInstance() {
82
- if (!Sanitization.instance) {
83
- Sanitization.instance = new Sanitization();
84
- }
85
- return Sanitization.instance;
86
- }
87
- /**
88
- * Sets or extends the list of field names considered sensitive for log redaction.
89
- * Field names are matched case-insensitively.
90
- * @param {string[]} fields - An array of field names to add to the sensitive list.
91
- * @param {RequestContext} [context] - Optional context for logging this configuration change.
92
- */
93
- setSensitiveFields(fields, context) {
94
- const opContext = context ||
95
- requestContextService.createRequestContext({
96
- operation: "Sanitization.setSensitiveFields",
97
- });
98
- this.sensitiveFields = [
99
- ...new Set([
100
- ...this.sensitiveFields,
101
- ...fields.map((f) => f.toLowerCase()),
102
- ]),
103
- ];
104
- logger.debug("Updated sensitive fields list for log redaction.", {
105
- ...opContext,
106
- newCount: this.sensitiveFields.length,
107
- });
108
- }
109
- /**
110
- * Retrieves a copy of the current list of sensitive field names used for log redaction.
111
- * @returns {string[]} An array of sensitive field names (all lowercase).
112
- */
113
- getSensitiveFields() {
114
- return [...this.sensitiveFields];
115
- }
116
- /**
117
- * Sanitizes an HTML string by removing potentially malicious tags and attributes,
118
- * based on a configurable allow-list.
119
- * @param {string} input - The HTML string to sanitize.
120
- * @param {HtmlSanitizeConfig} [config] - Optional custom configuration for HTML sanitization.
121
- * Overrides defaults for `allowedTags`, `allowedAttributes`, etc.
122
- * @returns {string} The sanitized HTML string. Returns an empty string if input is falsy.
123
- */
124
- sanitizeHtml(input, config) {
125
- if (!input)
126
- return "";
127
- const effectiveConfig = { ...this.defaultHtmlSanitizeConfig, ...config };
128
- const options = {
129
- allowedTags: effectiveConfig.allowedTags,
130
- allowedAttributes: effectiveConfig.allowedAttributes,
131
- transformTags: effectiveConfig.transformTags,
132
- };
133
- if (effectiveConfig.preserveComments) {
134
- // Ensure '!--' is not duplicated if already present
135
- options.allowedTags = [
136
- ...new Set([...(options.allowedTags || []), "!--"]),
137
- ];
138
- }
139
- return sanitizeHtml(input, options);
140
- }
141
- /**
142
- * Sanitizes a tag name by removing the leading '#' and replacing invalid characters.
143
- * @param {string} input - The tag string to sanitize.
144
- * @returns {string} The sanitized tag name.
145
- */
146
- sanitizeTagName(input) {
147
- if (!input)
148
- return "";
149
- // Remove leading '#' and replace spaces/invalid characters with nothing
150
- return input.replace(/^#/, "").replace(/[\s#,\\?%*:|"<>]/g, "");
151
- }
152
- /**
153
- >>>>>>> REPLACE
154
- * Sanitizes a string based on its intended usage context (e.g., HTML, URL, plain text).
155
- *
156
- * **Security Note:** Using `context: 'javascript'` is explicitly disallowed and will throw an `McpError`.
157
- * This is to prevent accidental introduction of XSS vulnerabilities through ineffective sanitization
158
- * of JavaScript code. Proper contextual encoding or safer methods should be used for JavaScript.
159
- *
160
- * @param {string} input - The string to sanitize.
161
- * @param {SanitizeStringOptions} [options={}] - Options specifying the sanitization context
162
- * and any context-specific parameters (like `allowedTags` for HTML).
163
- * @param {RequestContext} [contextForLogging] - Optional context for logging warnings or errors.
164
- * @returns {string} The sanitized string. Returns an empty string if input is falsy.
165
- * @throws {McpError} If `options.context` is `'javascript'`.
166
- */
167
- sanitizeString(input, options = {}, contextForLogging) {
168
- const opContext = contextForLogging ||
169
- requestContextService.createRequestContext({
170
- operation: "sanitizeString",
171
- inputContext: options.context,
172
- });
173
- if (!input)
174
- return "";
175
- switch (options.context) {
176
- case "html":
177
- return this.sanitizeHtml(input, {
178
- allowedTags: options.allowedTags,
179
- allowedAttributes: options.allowedAttributes
180
- ? this.convertAttributesFormat(options.allowedAttributes)
181
- : undefined,
182
- });
183
- case "attribute":
184
- // For HTML attributes, strip all tags. Values should be further encoded by the templating engine.
185
- return sanitizeHtml(input, { allowedTags: [], allowedAttributes: {} });
186
- case "url":
187
- // Validate and trim. Throws McpError on failure.
188
- try {
189
- return this.sanitizeUrl(input, ["http", "https"], opContext); // Use sanitizeUrl for consistent validation
190
- }
191
- catch (urlError) {
192
- logger.warning("Invalid URL detected during string sanitization (context: url).", {
193
- ...opContext,
194
- input,
195
- error: urlError instanceof Error ? urlError.message : String(urlError),
196
- });
197
- return ""; // Return empty or rethrow, depending on desired strictness. Empty for now.
198
- }
199
- case "javascript":
200
- logger.error("Attempted JavaScript sanitization via sanitizeString, which is disallowed.", { ...opContext, inputPreview: input.substring(0, 100) });
201
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, "JavaScript sanitization is not supported via sanitizeString due to security risks. Use appropriate contextual encoding or safer alternatives.", opContext);
202
- case "text":
203
- default:
204
- // Default to stripping all HTML for plain text contexts.
205
- return sanitizeHtml(input, { allowedTags: [], allowedAttributes: {} });
206
- }
207
- }
208
- /**
209
- * Sanitizes a URL string by validating its format and protocol.
210
- * @param {string} input - The URL string to sanitize.
211
- * @param {string[]} [allowedProtocols=['http', 'https']] - An array of allowed URL protocols (e.g., 'http', 'https', 'ftp').
212
- * @param {RequestContext} [contextForLogging] - Optional context for logging errors.
213
- * @returns {string} The sanitized and trimmed URL string.
214
- * @throws {McpError} If the URL is invalid, uses a disallowed protocol, or contains 'javascript:'.
215
- */
216
- sanitizeUrl(input, allowedProtocols = ["http", "https"], contextForLogging) {
217
- const opContext = contextForLogging ||
218
- requestContextService.createRequestContext({ operation: "sanitizeUrl" });
219
- try {
220
- if (!input || typeof input !== "string") {
221
- throw new Error("Invalid URL input: must be a non-empty string.");
222
- }
223
- const trimmedInput = input.trim();
224
- // Stricter check for 'javascript:' regardless of validator's protocol check
225
- if (trimmedInput.toLowerCase().startsWith("javascript:")) {
226
- throw new Error("JavaScript pseudo-protocol is explicitly disallowed.");
227
- }
228
- if (!validator.isURL(trimmedInput, {
229
- protocols: allowedProtocols,
230
- require_protocol: true,
231
- })) {
232
- throw new Error(`Invalid URL format or protocol not in allowed list: [${allowedProtocols.join(", ")}].`);
233
- }
234
- return trimmedInput;
235
- }
236
- catch (error) {
237
- const message = error instanceof Error ? error.message : "Invalid or disallowed URL.";
238
- logger.warning(`URL sanitization failed: ${message}`, {
239
- ...opContext,
240
- input,
241
- });
242
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, message, {
243
- ...opContext,
244
- input,
245
- });
246
- }
247
- }
248
- /**
249
- * Sanitizes a file path to prevent path traversal attacks and normalize its format.
250
- *
251
- * @param {string} input - The file path string to sanitize.
252
- * @param {PathSanitizeOptions} [options={}] - Options to control sanitization behavior (e.g., `rootDir`, `toPosix`).
253
- * @param {RequestContext} [contextForLogging] - Optional context for logging warnings or errors.
254
- * @returns {SanitizedPathInfo} An object containing the sanitized path and metadata about the sanitization.
255
- * @throws {McpError} If the path is invalid (e.g., empty, contains null bytes) or determined to be unsafe
256
- * (e.g., attempts to traverse outside `rootDir` or current working directory if no `rootDir`).
257
- */
258
- sanitizePath(input, options = {}, contextForLogging) {
259
- const opContext = contextForLogging ||
260
- requestContextService.createRequestContext({ operation: "sanitizePath" });
261
- const originalInput = input;
262
- const effectiveOptions = {
263
- toPosix: options.toPosix ?? false,
264
- allowAbsolute: options.allowAbsolute ?? false,
265
- rootDir: options.rootDir ? path.resolve(options.rootDir) : undefined, // Resolve rootDir upfront
266
- };
267
- let wasAbsoluteInitially = false;
268
- let convertedToRelative = false;
269
- try {
270
- if (!input || typeof input !== "string") {
271
- throw new Error("Invalid path input: must be a non-empty string.");
272
- }
273
- if (input.includes("\0")) {
274
- throw new Error("Path contains null byte, which is disallowed.");
275
- }
276
- let normalized = path.normalize(input); // Normalize first (e.g., 'a/b/../c' -> 'a/c')
277
- wasAbsoluteInitially = path.isAbsolute(normalized);
278
- if (effectiveOptions.toPosix) {
279
- normalized = normalized.replace(/\\/g, "/");
280
- }
281
- let finalSanitizedPath;
282
- if (effectiveOptions.rootDir) {
283
- // Resolve the input path against the root directory.
284
- // If 'normalized' is absolute, path.resolve treats it as the new root.
285
- // To correctly join, ensure 'normalized' is treated as relative to 'rootDir' if it's not already escaping.
286
- let tempPathForResolve = normalized;
287
- if (path.isAbsolute(normalized) && !effectiveOptions.allowAbsolute) {
288
- // If absolute paths are not allowed, make it relative before resolving with rootDir
289
- tempPathForResolve = normalized.replace(/^(?:[A-Za-z]:)?[/\\]+/, "");
290
- convertedToRelative = true;
291
- }
292
- else if (path.isAbsolute(normalized) &&
293
- effectiveOptions.allowAbsolute) {
294
- // Absolute path is allowed, check if it's within rootDir
295
- if (!normalized.startsWith(effectiveOptions.rootDir + path.sep) &&
296
- normalized !== effectiveOptions.rootDir) {
297
- throw new Error("Absolute path is outside the specified root directory.");
298
- }
299
- finalSanitizedPath = path.relative(effectiveOptions.rootDir, normalized);
300
- finalSanitizedPath =
301
- finalSanitizedPath === "" ? "." : finalSanitizedPath; // Handle case where path is rootDir itself
302
- // Early return if absolute path is allowed and within root.
303
- return {
304
- sanitizedPath: finalSanitizedPath,
305
- originalInput,
306
- wasAbsolute: wasAbsoluteInitially,
307
- convertedToRelative,
308
- optionsUsed: effectiveOptions,
309
- };
310
- }
311
- // If path was relative or made relative, join with rootDir
312
- const fullPath = path.resolve(effectiveOptions.rootDir, tempPathForResolve);
313
- if (!fullPath.startsWith(effectiveOptions.rootDir + path.sep) &&
314
- fullPath !== effectiveOptions.rootDir) {
315
- throw new Error("Path traversal detected: sanitized path escapes root directory.");
316
- }
317
- finalSanitizedPath = path.relative(effectiveOptions.rootDir, fullPath);
318
- finalSanitizedPath =
319
- finalSanitizedPath === "" ? "." : finalSanitizedPath;
320
- }
321
- else {
322
- // No rootDir specified
323
- if (path.isAbsolute(normalized)) {
324
- if (effectiveOptions.allowAbsolute) {
325
- finalSanitizedPath = normalized; // Absolute path allowed
326
- }
327
- else {
328
- // Convert to relative (strip leading slash/drive)
329
- finalSanitizedPath = normalized.replace(/^(?:[A-Za-z]:)?[/\\]+/, "");
330
- convertedToRelative = true;
331
- }
332
- }
333
- else {
334
- // Path is relative, and no rootDir
335
- // For relative paths without a rootDir, ensure they don't traverse "above" the conceptual CWD.
336
- // path.resolve('.') gives current working directory.
337
- const resolvedAgainstCwd = path.resolve(normalized);
338
- if (!resolvedAgainstCwd.startsWith(path.resolve("."))) {
339
- // This check is a bit tricky because '..' is valid if it stays within CWD's subtree.
340
- // A more robust check might involve comparing segments or ensuring it doesn't go "too high".
341
- // For simplicity, if it resolves outside CWD's prefix, consider it traversal.
342
- // This might be too strict for some use cases but safer for general utility.
343
- // A common pattern is to check if path.relative(cwd, resolvedPath) starts with '..'.
344
- if (path
345
- .relative(path.resolve("."), resolvedAgainstCwd)
346
- .startsWith("..")) {
347
- throw new Error("Relative path traversal detected (escapes current working directory context).");
348
- }
349
- }
350
- finalSanitizedPath = normalized;
351
- }
352
- }
353
- return {
354
- sanitizedPath: finalSanitizedPath,
355
- originalInput,
356
- wasAbsolute: wasAbsoluteInitially,
357
- convertedToRelative,
358
- optionsUsed: effectiveOptions,
359
- };
360
- }
361
- catch (error) {
362
- const message = error instanceof Error ? error.message : "Invalid or unsafe path.";
363
- logger.warning(`Path sanitization error: ${message}`, {
364
- ...opContext,
365
- input: originalInput,
366
- options: effectiveOptions,
367
- errorDetails: String(error),
368
- });
369
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, message, {
370
- ...opContext,
371
- input: originalInput,
372
- });
373
- }
374
- }
375
- /**
376
- * Sanitizes a JSON string by parsing it to validate its format.
377
- * Optionally checks if the JSON string's byte size exceeds a maximum limit.
378
- *
379
- * @template T The expected type of the parsed JSON object. Defaults to `unknown`.
380
- * @param {string} input - The JSON string to sanitize/validate.
381
- * @param {number} [maxSizeBytes] - Optional maximum allowed size of the JSON string in bytes.
382
- * @param {RequestContext} [contextForLogging] - Optional context for logging errors.
383
- * @returns {T} The parsed JavaScript object.
384
- * @throws {McpError} If the input is not a string, is not valid JSON, or exceeds `maxSizeBytes`.
385
- */
386
- sanitizeJson(input, maxSizeBytes, contextForLogging) {
387
- const opContext = contextForLogging ||
388
- requestContextService.createRequestContext({ operation: "sanitizeJson" });
389
- try {
390
- if (typeof input !== "string") {
391
- throw new Error("Invalid input: expected a JSON string.");
392
- }
393
- if (maxSizeBytes !== undefined &&
394
- Buffer.byteLength(input, "utf8") > maxSizeBytes) {
395
- throw new McpError(// Throw McpError directly
396
- BaseErrorCode.VALIDATION_ERROR, `JSON content exceeds maximum allowed size of ${maxSizeBytes} bytes. Actual size: ${Buffer.byteLength(input, "utf8")} bytes.`, {
397
- ...opContext,
398
- size: Buffer.byteLength(input, "utf8"),
399
- maxSize: maxSizeBytes,
400
- });
401
- }
402
- const parsed = JSON.parse(input);
403
- // Note: This function only validates JSON structure. It does not sanitize content within the JSON.
404
- // For deep sanitization of object values, additional logic would be needed.
405
- return parsed;
406
- }
407
- catch (error) {
408
- if (error instanceof McpError)
409
- throw error; // Re-throw if already McpError (e.g., size limit)
410
- const message = error instanceof Error ? error.message : "Invalid JSON format.";
411
- logger.warning(`JSON sanitization failed: ${message}`, {
412
- ...opContext,
413
- inputPreview: input.substring(0, 100),
414
- errorDetails: String(error),
415
- });
416
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, message, {
417
- ...opContext,
418
- inputPreview: input.length > 100 ? `${input.substring(0, 100)}...` : input,
419
- });
420
- }
421
- }
422
- /**
423
- * Sanitizes a numeric input (number or string) by converting it to a number
424
- * and optionally clamping it within a specified min/max range.
425
- *
426
- * @param {number | string} input - The numeric value or string representation of a number.
427
- * @param {number} [min] - Optional minimum allowed value (inclusive).
428
- * @param {number} [max] - Optional maximum allowed value (inclusive).
429
- * @param {RequestContext} [contextForLogging] - Optional context for logging clamping or errors.
430
- * @returns {number} The sanitized (and potentially clamped) number.
431
- * @throws {McpError} If the input cannot be parsed into a valid, finite number.
432
- */
433
- sanitizeNumber(input, min, max, contextForLogging) {
434
- const opContext = contextForLogging ||
435
- requestContextService.createRequestContext({
436
- operation: "sanitizeNumber",
437
- });
438
- let value;
439
- if (typeof input === "string") {
440
- const trimmedInput = input.trim();
441
- // Validator's isNumeric allows empty strings, so check explicitly.
442
- if (trimmedInput === "" || !validator.isNumeric(trimmedInput)) {
443
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Invalid number format: string is not numeric or is empty.", { ...opContext, input });
444
- }
445
- value = parseFloat(trimmedInput);
446
- }
447
- else if (typeof input === "number") {
448
- value = input;
449
- }
450
- else {
451
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Invalid input type: expected number or string.", { ...opContext, input: String(input) });
452
- }
453
- if (isNaN(value) || !isFinite(value)) {
454
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Invalid number value (NaN or Infinity).", { ...opContext, input });
455
- }
456
- let clamped = false;
457
- let originalValueForLog = value; // Store original before clamping for logging
458
- if (min !== undefined && value < min) {
459
- value = min;
460
- clamped = true;
461
- }
462
- if (max !== undefined && value > max) {
463
- value = max;
464
- clamped = true;
465
- }
466
- if (clamped) {
467
- logger.debug("Number clamped to range.", {
468
- ...opContext,
469
- originalValue: originalValueForLog,
470
- min,
471
- max,
472
- finalValue: value,
473
- });
474
- }
475
- return value;
476
- }
477
- /**
478
- * Sanitizes an object or array for logging by deep cloning it and redacting fields
479
- * whose names (case-insensitively) match any of the configured sensitive field names.
480
- * Redacted fields are replaced with the string `'[REDACTED]'`.
481
- *
482
- * @param {unknown} input - The object, array, or other value to sanitize for logging.
483
- * If input is not an object or array, it's returned as is.
484
- * @param {RequestContext} [contextForLogging] - Optional context for logging errors during sanitization.
485
- * @returns {unknown} A sanitized copy of the input, safe for logging.
486
- * Returns `'[Log Sanitization Failed]'` if an unexpected error occurs during sanitization.
487
- */
488
- sanitizeForLogging(input, contextForLogging) {
489
- const opContext = contextForLogging ||
490
- requestContextService.createRequestContext({
491
- operation: "sanitizeForLogging",
492
- });
493
- try {
494
- // Primitives and null are returned as is.
495
- if (input === null || typeof input !== "object") {
496
- return input;
497
- }
498
- // Use structuredClone if available (Node.js >= 17), otherwise fallback to JSON parse/stringify.
499
- // JSON.parse(JSON.stringify(obj)) is a common way to deep clone, but has limitations
500
- // (e.g., loses functions, undefined, Date objects become strings).
501
- // For logging, this is often acceptable.
502
- const clonedInput = typeof structuredClone === "function"
503
- ? structuredClone(input)
504
- : JSON.parse(JSON.stringify(input));
505
- this.redactSensitiveFields(clonedInput);
506
- return clonedInput;
507
- }
508
- catch (error) {
509
- logger.error("Error during log sanitization process.", error instanceof Error ? error : undefined, {
510
- ...opContext,
511
- errorDetails: error instanceof Error ? error.message : String(error),
512
- });
513
- return "[Log Sanitization Failed]"; // Fallback string indicating sanitization failure
514
- }
515
- }
516
- /**
517
- * Helper to convert attribute format for sanitize-html.
518
- * `sanitize-html` expects `allowedAttributes` in a specific format.
519
- * This method assumes the input `attrs` (from `SanitizeStringOptions`)
520
- * is already in the correct format or a compatible one.
521
- * @param {Record<string, string[]>} attrs - Attributes configuration.
522
- * @returns {sanitizeHtml.IOptions['allowedAttributes']} Attributes in `sanitize-html` format.
523
- * @private
524
- */
525
- convertAttributesFormat(attrs) {
526
- // The type Record<string, string[]> is compatible with sanitizeHtml.IOptions['allowedAttributes']
527
- // which can be Record<string, Array<string | RegExp>> or boolean.
528
- // No complex conversion needed if options.allowedAttributes is already Record<string, string[]>.
529
- return attrs;
530
- }
531
- /**
532
- * Recursively redacts sensitive fields within an object or array.
533
- * This method modifies the input object/array in place.
534
- * @param {unknown} obj - The object or array to redact sensitive fields from.
535
- * @private
536
- */
537
- redactSensitiveFields(obj) {
538
- if (!obj || typeof obj !== "object") {
539
- return; // Not an object or array, or null
540
- }
541
- if (Array.isArray(obj)) {
542
- obj.forEach((item) => {
543
- // Recurse only if the item is an object (including nested arrays)
544
- if (item && typeof item === "object") {
545
- this.redactSensitiveFields(item);
546
- }
547
- });
548
- return;
549
- }
550
- // It's an object (but not an array)
551
- for (const key in obj) {
552
- // Check if the property belongs to the object itself, not its prototype
553
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
554
- const value = obj[key];
555
- const lowerKey = key.toLowerCase();
556
- // Special handling for non-serializable but non-sensitive objects
557
- if (key === "httpsAgent") {
558
- obj[key] = "[HttpAgent Instance]";
559
- continue; // Skip further processing for this key
560
- }
561
- // Check if the lowercase key includes any of the lowercase sensitive field terms
562
- const isSensitive = this.sensitiveFields.some((field) => lowerKey.includes(field));
563
- if (isSensitive) {
564
- obj[key] = "[REDACTED]";
565
- }
566
- else if (value && typeof value === "object") {
567
- // If the value is another object or array, recurse
568
- this.redactSensitiveFields(value);
569
- }
570
- }
571
- }
572
- }
573
- }
574
- /**
575
- * A default, shared instance of the `Sanitization` class.
576
- * Use this instance for all sanitization tasks.
577
- *
578
- * Example:
579
- * ```typescript
580
- * import { sanitization, sanitizeInputForLogging } from './sanitization';
581
- *
582
- * const unsafeHtml = "<script>alert('xss')</script><p>Safe</p>";
583
- * const safeHtml = sanitization.sanitizeHtml(unsafeHtml);
584
- *
585
- * const sensitiveData = { password: '123', username: 'user' };
586
- * const safeLogData = sanitizeInputForLogging(sensitiveData);
587
- * // safeLogData will be { password: '[REDACTED]', username: 'user' }
588
- * ```
589
- */
590
- export const sanitization = Sanitization.getInstance();
591
- /**
592
- * A convenience function that wraps `sanitization.sanitizeForLogging`.
593
- * Sanitizes an object or array for logging by redacting sensitive fields.
594
- *
595
- * @param {unknown} input - The data to sanitize for logging.
596
- * @param {RequestContext} [contextForLogging] - Optional context for logging errors during sanitization.
597
- * @returns {unknown} A sanitized copy of the input, safe for logging.
598
- */
599
- export const sanitizeInputForLogging = (input, contextForLogging) => sanitization.sanitizeForLogging(input, contextForLogging);