diff --git a/extensions/nextcloud-talk/src/send.ts b/extensions/nextcloud-talk/src/send.ts index 1dd8f5094..13ff38495 100644 --- a/extensions/nextcloud-talk/src/send.ts +++ b/extensions/nextcloud-talk/src/send.ts @@ -48,6 +48,9 @@ function normalizeRoomToken(to: string): string { } if (!normalized) throw new Error("Room token is required for Nextcloud Talk sends"); + if (!/^[a-zA-Z0-9_-]+$/.test(normalized)) { + throw new Error(`Invalid room token: contains disallowed characters`); + } return normalized; } @@ -177,6 +180,9 @@ export async function sendReactionNextcloudTalk( account, ); const normalizedToken = normalizeRoomToken(roomToken); + if (!/^[a-zA-Z0-9_-]+$/.test(messageId)) { + throw new Error("Invalid message ID: contains disallowed characters"); + } const body = JSON.stringify({ reaction }); const { random, signature } = generateNextcloudTalkSignature({ diff --git a/extensions/voice-call/src/telephony-tts.ts b/extensions/voice-call/src/telephony-tts.ts index 147501e85..adad03ab5 100644 --- a/extensions/voice-call/src/telephony-tts.ts +++ b/extensions/voice-call/src/telephony-tts.ts @@ -80,6 +80,7 @@ function deepMerge(base: T, override: T): T { const result: Record = { ...base }; for (const [key, value] of Object.entries(override)) { if (value === undefined) continue; + if (key === "__proto__" || key === "constructor" || key === "prototype") continue; const existing = (base as Record)[key]; if (isPlainObject(existing) && isPlainObject(value)) { result[key] = deepMerge(existing, value); diff --git a/src/config/includes.ts b/src/config/includes.ts index 1817e47e3..08e253f36 100644 --- a/src/config/includes.ts +++ b/src/config/includes.ts @@ -70,6 +70,7 @@ export function deepMerge(target: unknown, source: unknown): unknown { if (isPlainObject(target) && isPlainObject(source)) { const result: Record = { ...target }; for (const key of Object.keys(source)) { + if (key === "__proto__" || key === "constructor" || key === "prototype") continue; result[key] = key in result ? deepMerge(result[key], source[key]) : source[key]; } return result; diff --git a/src/logging/logger.ts b/src/logging/logger.ts index ae36a75e1..62ee8bebe 100644 --- a/src/logging/logger.ts +++ b/src/logging/logger.ts @@ -8,6 +8,7 @@ import type { MoltbotConfig } from "../config/types.js"; import type { ConsoleStyle } from "./console.js"; import { type LogLevel, levelToMinLevel, normalizeLogLevel } from "./levels.js"; import { readLoggingConfig } from "./config.js"; +import { redactSensitiveText } from "./redact.js"; import { loggingState } from "./state.js"; // Pin to /tmp so mac Debug UI and docs match; os.tmpdir() can be a per-user @@ -96,7 +97,8 @@ function buildLogger(settings: ResolvedSettings): TsLogger { logger.attachTransport((logObj: LogObj) => { try { const time = logObj.date?.toISOString?.() ?? new Date().toISOString(); - const line = JSON.stringify({ ...logObj, time }); + const raw = JSON.stringify({ ...logObj, time }); + const line = redactSensitiveText(raw); fs.appendFileSync(settings.file, `${line}\n`, { encoding: "utf8" }); } catch { // never block on logging failures diff --git a/src/memory/internal.ts b/src/memory/internal.ts index b68570c35..2ea81a694 100644 --- a/src/memory/internal.ts +++ b/src/memory/internal.ts @@ -19,9 +19,7 @@ export type MemoryChunk = { }; export function ensureDir(dir: string): string { - try { - fsSync.mkdirSync(dir, { recursive: true }); - } catch {} + fsSync.mkdirSync(dir, { recursive: true }); return dir; } diff --git a/src/memory/manager.ts b/src/memory/manager.ts index 9a9991d10..776bc2ad8 100644 --- a/src/memory/manager.ts +++ b/src/memory/manager.ts @@ -976,9 +976,17 @@ export class MemoryIndexManager { progress?: MemorySyncProgressState; }) { const files = await listMemoryFiles(this.workspaceDir); - const fileEntries = await Promise.all( + const settled = await Promise.allSettled( files.map(async (file) => buildFileEntry(file, this.workspaceDir)), ); + const fileEntries = settled.flatMap((result, i) => { + if (result.status === "fulfilled") return [result.value]; + log.warn("memory sync: skipping unreadable file", { + file: files[i], + error: String(result.reason), + }); + return []; + }); log.debug("memory sync: indexing memory files", { files: fileEntries.length, needsFullReindex: params.needsFullReindex,