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.
- package/CLAUDE.md +364 -0
- package/Dockerfile +99 -0
- package/LICENSE +4 -6
- package/README.md +246 -206
- package/changelog/3.0.x/3.0.0.md +102 -0
- package/changelog/template.md +51 -0
- package/dist/config/server-config.d.ts +19 -0
- package/dist/config/server-config.d.ts.map +1 -0
- package/dist/config/server-config.js +55 -0
- package/dist/config/server-config.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -295
- package/dist/index.js.map +1 -0
- package/dist/mcp-server/prompts/definitions/index.d.ts +8 -0
- package/dist/mcp-server/prompts/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/prompts/definitions/index.js +8 -0
- package/dist/mcp-server/prompts/definitions/index.js.map +1 -0
- package/dist/mcp-server/resources/definitions/index.d.ts +36 -0
- package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/index.js +9 -0
- package/dist/mcp-server/resources/definitions/index.js.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-status.resource.d.ts +23 -0
- package/dist/mcp-server/resources/definitions/obsidian-status.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-status.resource.js +47 -0
- package/dist/mcp-server/resources/definitions/obsidian-status.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.d.ts +13 -0
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.js +30 -0
- package/dist/mcp-server/resources/definitions/obsidian-tags.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.d.ts +21 -0
- package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.js +38 -0
- package/dist/mcp-server/resources/definitions/obsidian-vault-note.resource.js.map +1 -0
- package/dist/mcp-server/tools/definitions/_shared/schemas.d.ts +45 -0
- package/dist/mcp-server/tools/definitions/_shared/schemas.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/_shared/schemas.js +66 -0
- package/dist/mcp-server/tools/definitions/_shared/schemas.js.map +1 -0
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.d.ts +51 -0
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.js +120 -0
- package/dist/mcp-server/tools/definitions/_shared/suggest-paths.js.map +1 -0
- package/dist/mcp-server/tools/definitions/index.d.ts +531 -0
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/index.js +38 -0
- package/dist/mcp-server/tools/definitions/index.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts +42 -0
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js +58 -0
- package/dist/mcp-server/tools/definitions/obsidian-append-to-note.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts +46 -0
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js +66 -0
- package/dist/mcp-server/tools/definitions/obsidian-delete-note.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.d.ts +19 -0
- package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.js +43 -0
- package/dist/mcp-server/tools/definitions/obsidian-execute-command.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts +92 -0
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js +245 -0
- package/dist/mcp-server/tools/definitions/obsidian-get-note.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.d.ts +13 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.js +38 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-commands.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts +59 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js +273 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-notes.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts +13 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js +38 -0
- package/dist/mcp-server/tools/definitions/obsidian-list-tags.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts +68 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js +178 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-frontmatter.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts +77 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js +172 -0
- package/dist/mcp-server/tools/definitions/obsidian-manage-tags.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts +22 -0
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js +88 -0
- package/dist/mcp-server/tools/definitions/obsidian-open-in-ui.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts +81 -0
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js +83 -0
- package/dist/mcp-server/tools/definitions/obsidian-patch-note.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts +59 -0
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js +167 -0
- package/dist/mcp-server/tools/definitions/obsidian-replace-in-note.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts +76 -0
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js +244 -0
- package/dist/mcp-server/tools/definitions/obsidian-search-notes.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts +41 -0
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js +76 -0
- package/dist/mcp-server/tools/definitions/obsidian-write-note.tool.js.map +1 -0
- package/dist/services/obsidian/frontmatter-ops.d.ts +34 -0
- package/dist/services/obsidian/frontmatter-ops.d.ts.map +1 -0
- package/dist/services/obsidian/frontmatter-ops.js +230 -0
- package/dist/services/obsidian/frontmatter-ops.js.map +1 -0
- package/dist/services/obsidian/obsidian-service.d.ts +73 -0
- package/dist/services/obsidian/obsidian-service.d.ts.map +1 -0
- package/dist/services/obsidian/obsidian-service.js +399 -0
- package/dist/services/obsidian/obsidian-service.js.map +1 -0
- package/dist/services/obsidian/section-extractor.d.ts +13 -0
- package/dist/services/obsidian/section-extractor.d.ts.map +1 -0
- package/dist/services/obsidian/section-extractor.js +124 -0
- package/dist/services/obsidian/section-extractor.js.map +1 -0
- package/dist/services/obsidian/types.d.ts +91 -0
- package/dist/services/obsidian/types.d.ts.map +1 -0
- package/dist/services/obsidian/types.js +7 -0
- package/dist/services/obsidian/types.js.map +1 -0
- package/package.json +63 -69
- package/server.json +167 -0
- package/CHANGELOG.md +0 -124
- package/dist/config/index.d.ts +0 -41
- package/dist/config/index.js +0 -191
- package/dist/mcp-server/server.d.ts +0 -33
- package/dist/mcp-server/server.js +0 -211
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/logic.d.ts +0 -51
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/logic.js +0 -168
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/registration.d.ts +0 -19
- package/dist/mcp-server/tools/obsidianDeleteNoteTool/registration.js +0 -91
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/logic.d.ts +0 -77
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/logic.js +0 -341
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/registration.d.ts +0 -18
- package/dist/mcp-server/tools/obsidianGlobalSearchTool/registration.js +0 -69
- package/dist/mcp-server/tools/obsidianListNotesTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianListNotesTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianListNotesTool/logic.d.ts +0 -68
- package/dist/mcp-server/tools/obsidianListNotesTool/logic.js +0 -215
- package/dist/mcp-server/tools/obsidianListNotesTool/registration.d.ts +0 -23
- package/dist/mcp-server/tools/obsidianListNotesTool/registration.js +0 -98
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/index.d.ts +0 -3
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/index.js +0 -2
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/logic.d.ts +0 -42
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/logic.js +0 -152
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/registration.d.ts +0 -3
- package/dist/mcp-server/tools/obsidianManageFrontmatterTool/registration.js +0 -52
- package/dist/mcp-server/tools/obsidianManageTagsTool/index.d.ts +0 -3
- package/dist/mcp-server/tools/obsidianManageTagsTool/index.js +0 -2
- package/dist/mcp-server/tools/obsidianManageTagsTool/logic.d.ts +0 -28
- package/dist/mcp-server/tools/obsidianManageTagsTool/logic.js +0 -161
- package/dist/mcp-server/tools/obsidianManageTagsTool/registration.d.ts +0 -3
- package/dist/mcp-server/tools/obsidianManageTagsTool/registration.js +0 -52
- package/dist/mcp-server/tools/obsidianReadNoteTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianReadNoteTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianReadNoteTool/logic.d.ts +0 -87
- package/dist/mcp-server/tools/obsidianReadNoteTool/logic.js +0 -216
- package/dist/mcp-server/tools/obsidianReadNoteTool/registration.d.ts +0 -20
- package/dist/mcp-server/tools/obsidianReadNoteTool/registration.js +0 -101
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/logic.d.ts +0 -255
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/logic.js +0 -583
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/registration.d.ts +0 -22
- package/dist/mcp-server/tools/obsidianSearchReplaceTool/registration.js +0 -111
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/index.d.ts +0 -12
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/index.js +0 -12
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/logic.d.ts +0 -183
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/logic.js +0 -490
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/registration.d.ts +0 -21
- package/dist/mcp-server/tools/obsidianUpdateNoteTool/registration.js +0 -108
- package/dist/mcp-server/transports/auth/core/authContext.d.ts +0 -33
- package/dist/mcp-server/transports/auth/core/authContext.js +0 -24
- package/dist/mcp-server/transports/auth/core/authTypes.d.ts +0 -17
- package/dist/mcp-server/transports/auth/core/authTypes.js +0 -5
- package/dist/mcp-server/transports/auth/core/authUtils.d.ts +0 -18
- package/dist/mcp-server/transports/auth/core/authUtils.js +0 -45
- package/dist/mcp-server/transports/auth/index.d.ts +0 -10
- package/dist/mcp-server/transports/auth/index.js +0 -9
- package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.d.ts +0 -27
- package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.js +0 -149
- package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.d.ts +0 -20
- package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.js +0 -124
- package/dist/mcp-server/transports/httpErrorHandler.d.ts +0 -26
- package/dist/mcp-server/transports/httpErrorHandler.js +0 -73
- package/dist/mcp-server/transports/httpTransport.d.ts +0 -21
- package/dist/mcp-server/transports/httpTransport.js +0 -208
- package/dist/mcp-server/transports/stdioTransport.d.ts +0 -42
- package/dist/mcp-server/transports/stdioTransport.js +0 -63
- package/dist/services/obsidianRestAPI/index.d.ts +0 -15
- package/dist/services/obsidianRestAPI/index.js +0 -17
- package/dist/services/obsidianRestAPI/methods/activeFileMethods.d.ts +0 -38
- package/dist/services/obsidianRestAPI/methods/activeFileMethods.js +0 -62
- package/dist/services/obsidianRestAPI/methods/commandMethods.d.ts +0 -22
- package/dist/services/obsidianRestAPI/methods/commandMethods.js +0 -31
- package/dist/services/obsidianRestAPI/methods/openMethods.d.ts +0 -16
- package/dist/services/obsidianRestAPI/methods/openMethods.js +0 -21
- package/dist/services/obsidianRestAPI/methods/patchMethods.d.ts +0 -37
- package/dist/services/obsidianRestAPI/methods/patchMethods.js +0 -94
- package/dist/services/obsidianRestAPI/methods/periodicNoteMethods.d.ts +0 -42
- package/dist/services/obsidianRestAPI/methods/periodicNoteMethods.js +0 -66
- package/dist/services/obsidianRestAPI/methods/searchMethods.d.ts +0 -25
- package/dist/services/obsidianRestAPI/methods/searchMethods.js +0 -36
- package/dist/services/obsidianRestAPI/methods/vaultMethods.d.ts +0 -58
- package/dist/services/obsidianRestAPI/methods/vaultMethods.js +0 -144
- package/dist/services/obsidianRestAPI/service.d.ts +0 -195
- package/dist/services/obsidianRestAPI/service.js +0 -379
- package/dist/services/obsidianRestAPI/types.d.ts +0 -127
- package/dist/services/obsidianRestAPI/types.js +0 -7
- package/dist/services/obsidianRestAPI/vaultCache/index.d.ts +0 -4
- package/dist/services/obsidianRestAPI/vaultCache/index.js +0 -4
- package/dist/services/obsidianRestAPI/vaultCache/service.d.ts +0 -88
- package/dist/services/obsidianRestAPI/vaultCache/service.js +0 -299
- package/dist/types-global/errors.d.ts +0 -73
- package/dist/types-global/errors.js +0 -71
- package/dist/utils/index.d.ts +0 -5
- package/dist/utils/index.js +0 -13
- package/dist/utils/internal/asyncUtils.d.ts +0 -54
- package/dist/utils/internal/asyncUtils.js +0 -101
- package/dist/utils/internal/errorHandler.d.ts +0 -176
- package/dist/utils/internal/errorHandler.js +0 -351
- package/dist/utils/internal/index.d.ts +0 -4
- package/dist/utils/internal/index.js +0 -4
- package/dist/utils/internal/logger.d.ts +0 -141
- package/dist/utils/internal/logger.js +0 -406
- package/dist/utils/internal/requestContext.d.ts +0 -83
- package/dist/utils/internal/requestContext.js +0 -72
- package/dist/utils/metrics/index.d.ts +0 -1
- package/dist/utils/metrics/index.js +0 -1
- package/dist/utils/metrics/tokenCounter.d.ts +0 -27
- package/dist/utils/metrics/tokenCounter.js +0 -128
- package/dist/utils/obsidian/index.d.ts +0 -5
- package/dist/utils/obsidian/index.js +0 -5
- package/dist/utils/obsidian/obsidianApiUtils.d.ts +0 -14
- package/dist/utils/obsidian/obsidianApiUtils.js +0 -29
- package/dist/utils/obsidian/obsidianStatUtils.d.ts +0 -68
- package/dist/utils/obsidian/obsidianStatUtils.js +0 -143
- package/dist/utils/parsing/dateParser.d.ts +0 -56
- package/dist/utils/parsing/dateParser.js +0 -104
- package/dist/utils/parsing/index.d.ts +0 -2
- package/dist/utils/parsing/index.js +0 -3
- package/dist/utils/parsing/jsonParser.d.ts +0 -80
- package/dist/utils/parsing/jsonParser.js +0 -133
- package/dist/utils/security/idGenerator.d.ts +0 -140
- package/dist/utils/security/idGenerator.js +0 -194
- package/dist/utils/security/index.d.ts +0 -3
- package/dist/utils/security/index.js +0 -3
- package/dist/utils/security/rateLimiter.d.ts +0 -156
- package/dist/utils/security/rateLimiter.js +0 -235
- package/dist/utils/security/sanitization.d.ts +0 -244
- 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);
|