fix: harden logging, memory sync, config merge, and input validation
- Redact sensitive text in file log transport before writing to disk - Validate Nextcloud Talk room tokens and message IDs against path traversal - Propagate real errors from ensureDir instead of silently swallowing - Use Promise.allSettled in memory sync so one bad file does not abort indexing - Block __proto__/constructor/prototype keys in deepMerge (config includes + voice-call TTS)
This commit is contained in:
parent
19823c5498
commit
d5be6aa3f4
@ -48,6 +48,9 @@ function normalizeRoomToken(to: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!normalized) throw new Error("Room token is required for Nextcloud Talk sends");
|
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;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,6 +180,9 @@ export async function sendReactionNextcloudTalk(
|
|||||||
account,
|
account,
|
||||||
);
|
);
|
||||||
const normalizedToken = normalizeRoomToken(roomToken);
|
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 body = JSON.stringify({ reaction });
|
||||||
const { random, signature } = generateNextcloudTalkSignature({
|
const { random, signature } = generateNextcloudTalkSignature({
|
||||||
|
|||||||
@ -80,6 +80,7 @@ function deepMerge<T>(base: T, override: T): T {
|
|||||||
const result: Record<string, unknown> = { ...base };
|
const result: Record<string, unknown> = { ...base };
|
||||||
for (const [key, value] of Object.entries(override)) {
|
for (const [key, value] of Object.entries(override)) {
|
||||||
if (value === undefined) continue;
|
if (value === undefined) continue;
|
||||||
|
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
||||||
const existing = (base as Record<string, unknown>)[key];
|
const existing = (base as Record<string, unknown>)[key];
|
||||||
if (isPlainObject(existing) && isPlainObject(value)) {
|
if (isPlainObject(existing) && isPlainObject(value)) {
|
||||||
result[key] = deepMerge(existing, value);
|
result[key] = deepMerge(existing, value);
|
||||||
|
|||||||
@ -70,6 +70,7 @@ export function deepMerge(target: unknown, source: unknown): unknown {
|
|||||||
if (isPlainObject(target) && isPlainObject(source)) {
|
if (isPlainObject(target) && isPlainObject(source)) {
|
||||||
const result: Record<string, unknown> = { ...target };
|
const result: Record<string, unknown> = { ...target };
|
||||||
for (const key of Object.keys(source)) {
|
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];
|
result[key] = key in result ? deepMerge(result[key], source[key]) : source[key];
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import type { MoltbotConfig } from "../config/types.js";
|
|||||||
import type { ConsoleStyle } from "./console.js";
|
import type { ConsoleStyle } from "./console.js";
|
||||||
import { type LogLevel, levelToMinLevel, normalizeLogLevel } from "./levels.js";
|
import { type LogLevel, levelToMinLevel, normalizeLogLevel } from "./levels.js";
|
||||||
import { readLoggingConfig } from "./config.js";
|
import { readLoggingConfig } from "./config.js";
|
||||||
|
import { redactSensitiveText } from "./redact.js";
|
||||||
import { loggingState } from "./state.js";
|
import { loggingState } from "./state.js";
|
||||||
|
|
||||||
// Pin to /tmp so mac Debug UI and docs match; os.tmpdir() can be a per-user
|
// 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<LogObj> {
|
|||||||
logger.attachTransport((logObj: LogObj) => {
|
logger.attachTransport((logObj: LogObj) => {
|
||||||
try {
|
try {
|
||||||
const time = logObj.date?.toISOString?.() ?? new Date().toISOString();
|
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" });
|
fs.appendFileSync(settings.file, `${line}\n`, { encoding: "utf8" });
|
||||||
} catch {
|
} catch {
|
||||||
// never block on logging failures
|
// never block on logging failures
|
||||||
|
|||||||
@ -19,9 +19,7 @@ export type MemoryChunk = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function ensureDir(dir: string): string {
|
export function ensureDir(dir: string): string {
|
||||||
try {
|
fsSync.mkdirSync(dir, { recursive: true });
|
||||||
fsSync.mkdirSync(dir, { recursive: true });
|
|
||||||
} catch {}
|
|
||||||
return dir;
|
return dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -976,9 +976,17 @@ export class MemoryIndexManager {
|
|||||||
progress?: MemorySyncProgressState;
|
progress?: MemorySyncProgressState;
|
||||||
}) {
|
}) {
|
||||||
const files = await listMemoryFiles(this.workspaceDir);
|
const files = await listMemoryFiles(this.workspaceDir);
|
||||||
const fileEntries = await Promise.all(
|
const settled = await Promise.allSettled(
|
||||||
files.map(async (file) => buildFileEntry(file, this.workspaceDir)),
|
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", {
|
log.debug("memory sync: indexing memory files", {
|
||||||
files: fileEntries.length,
|
files: fileEntries.length,
|
||||||
needsFullReindex: params.needsFullReindex,
|
needsFullReindex: params.needsFullReindex,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user