From 7b3b7583c0afed67014fe48201a9015b05ef37de Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 25 Apr 2026 09:30:11 +0200 Subject: [PATCH] chore: improve sanitize function to catch escaped characters --- .../modules/backends/smb/smb-backend.ts | 2 +- .../core/src/utils/__tests__/sanitize.test.ts | 74 +++++++++++++++++++ packages/core/src/utils/sanitize.ts | 2 +- 3 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 packages/core/src/utils/__tests__/sanitize.test.ts diff --git a/app/server/modules/backends/smb/smb-backend.ts b/app/server/modules/backends/smb/smb-backend.ts index e010721f..a4d51a45 100644 --- a/app/server/modules/backends/smb/smb-backend.ts +++ b/app/server/modules/backends/smb/smb-backend.ts @@ -65,7 +65,7 @@ const mount = async (config: BackendConfig, path: string) => { const args = ["-t", "cifs", "-o", options.join(","), source, path]; logger.debug(`Mounting SMB volume ${path}...`); - logger.info(`Executing mount: mount ${args.join(" ")}`); + logger.info(`Executing SMB mount for ${source} at ${path}`); try { await executeMount(args); diff --git a/packages/core/src/utils/__tests__/sanitize.test.ts b/packages/core/src/utils/__tests__/sanitize.test.ts new file mode 100644 index 00000000..ed9eafc3 --- /dev/null +++ b/packages/core/src/utils/__tests__/sanitize.test.ts @@ -0,0 +1,74 @@ +import { describe, expect, test } from "vitest"; +import { sanitizeSensitiveData } from "../sanitize"; + +const withNodeEnv = (nodeEnv: string, fn: () => void) => { + const originalNodeEnv = process.env.NODE_ENV; + process.env.NODE_ENV = nodeEnv; + + try { + fn(); + } finally { + process.env.NODE_ENV = originalNodeEnv; + } +}; + +describe("sanitizeSensitiveData", () => { + test("redacts escaped comma password values", () => { + withNodeEnv("production", () => { + const sanitized = sanitizeSensitiveData( + "mount -t cifs -o username=user,password=abc\\,LEAKED_SUFFIX //server/share /mnt", + ); + + expect(sanitized).toBe("mount -t cifs -o username=user,password=*** //server/share /mnt"); + expect(sanitized).not.toContain("LEAKED_SUFFIX"); + }); + }); + + test.each([ + { + name: "password key", + input: "mount -o username=user,password=plainsecret //server/share /mnt", + expected: "mount -o username=user,password=*** //server/share /mnt", + secret: "plainsecret", + }, + { + name: "pass key", + input: "backend returned pass=hunter2 while mounting", + expected: "backend returned pass=*** while mounting", + secret: "hunter2", + }, + { + name: "uppercase key", + input: "mount -o PASSWORD=CaseSecret //server/share /mnt", + expected: "mount -o PASSWORD=*** //server/share /mnt", + secret: "CaseSecret", + }, + { + name: "URL basic auth", + input: "request failed for https://alice:supersecret@example.com/hook", + expected: "request failed for https://alice:***@example.com/hook", + secret: "supersecret", + }, + { + name: "space-delimited URL credential entry", + input: "https://dav.example.test/path operator dav-password", + expected: "https://dav.example.test/path operator ***", + secret: "dav-password", + }, + ])("redacts $name", ({ input, expected, secret }) => { + withNodeEnv("production", () => { + const sanitized = sanitizeSensitiveData(input); + + expect(sanitized).toBe(expected); + expect(sanitized).not.toContain(secret); + }); + }); + + test("does not redact in development", () => { + withNodeEnv("development", () => { + const input = "mount -o username=user,password=devsecret //server/share /mnt"; + + expect(sanitizeSensitiveData(input)).toBe(input); + }); + }); +}); diff --git a/packages/core/src/utils/sanitize.ts b/packages/core/src/utils/sanitize.ts index d72e2b7e..6f4042a2 100644 --- a/packages/core/src/utils/sanitize.ts +++ b/packages/core/src/utils/sanitize.ts @@ -7,7 +7,7 @@ export const sanitizeSensitiveData = (text: string): string => { return text; } - let sanitized = text.replace(/\b(pass|password)=([^\s,]+)/gi, "$1=***"); + let sanitized = text.replace(/\b(pass|password)=((?:\\.|[^\s,])*)/gi, "$1=***"); sanitized = sanitized.replace(/\/\/([^:@\s]+):([^@\s]+)@/g, "//$1:***@");