mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-08 06:54:24 +00:00
fix: harden voice-call tts deep merge
This commit is contained in:
@@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Security/Voice Call: harden `voice-call` telephony TTS override merging by blocking unsafe deep-merge keys (`__proto__`, `prototype`, `constructor`) and add regression coverage for top-level and nested prototype-pollution payloads.
|
||||
- Security/Net: enforce strict dotted-decimal IPv4 literals in SSRF checks and fail closed on unsupported legacy forms (octal/hex/short/packed, for example `0177.0.0.1`, `127.1`, `2130706433`) before DNS lookup.
|
||||
- Security/Discord: enforce trusted-sender guild permission checks for moderation actions (`timeout`, `kick`, `ban`) and ignore untrusted `senderUserId` params to prevent privilege escalation in tool-driven flows. Thanks @aether-ai-agent for reporting.
|
||||
- Security/ACP+Exec: add `openclaw acp --token-file/--password-file` secret-file support (with inline secret flag warnings), redact ACP working-directory prefixes to `~` home-relative paths, constrain exec script preflight file inspection to the effective `workdir` boundary, and add security-audit warnings when `tools.exec.host="sandbox"` is configured while sandbox mode is off.
|
||||
|
||||
75
extensions/voice-call/src/telephony-tts.test.ts
Normal file
75
extensions/voice-call/src/telephony-tts.test.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { VoiceCallTtsConfig } from "./config.js";
|
||||
import type { CoreConfig } from "./core-bridge.js";
|
||||
import { createTelephonyTtsProvider } from "./telephony-tts.js";
|
||||
|
||||
function createCoreConfig(): CoreConfig {
|
||||
const tts: VoiceCallTtsConfig = {
|
||||
provider: "openai",
|
||||
openai: {
|
||||
model: "gpt-4o-mini-tts",
|
||||
voice: "alloy",
|
||||
},
|
||||
};
|
||||
return { messages: { tts } };
|
||||
}
|
||||
|
||||
async function mergeOverride(override: unknown): Promise<Record<string, unknown>> {
|
||||
let mergedConfig: CoreConfig | undefined;
|
||||
const provider = createTelephonyTtsProvider({
|
||||
coreConfig: createCoreConfig(),
|
||||
ttsOverride: override as VoiceCallTtsConfig,
|
||||
runtime: {
|
||||
textToSpeechTelephony: async ({ cfg }) => {
|
||||
mergedConfig = cfg;
|
||||
return {
|
||||
success: true,
|
||||
audioBuffer: Buffer.alloc(2),
|
||||
sampleRate: 8000,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await provider.synthesizeForTelephony("hello");
|
||||
expect(mergedConfig?.messages?.tts).toBeDefined();
|
||||
return mergedConfig?.messages?.tts as Record<string, unknown>;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
delete (Object.prototype as Record<string, unknown>).polluted;
|
||||
});
|
||||
|
||||
describe("createTelephonyTtsProvider deepMerge hardening", () => {
|
||||
it("merges safe nested overrides", async () => {
|
||||
const tts = await mergeOverride({
|
||||
openai: { voice: "coral" },
|
||||
});
|
||||
const openai = tts.openai as Record<string, unknown>;
|
||||
|
||||
expect(openai.voice).toBe("coral");
|
||||
expect(openai.model).toBe("gpt-4o-mini-tts");
|
||||
});
|
||||
|
||||
it("blocks top-level __proto__ keys", async () => {
|
||||
const tts = await mergeOverride(
|
||||
JSON.parse('{"__proto__":{"polluted":"top"},"openai":{"voice":"coral"}}'),
|
||||
);
|
||||
const openai = tts.openai as Record<string, unknown>;
|
||||
|
||||
expect((Object.prototype as Record<string, unknown>).polluted).toBeUndefined();
|
||||
expect(tts.polluted).toBeUndefined();
|
||||
expect(openai.voice).toBe("coral");
|
||||
});
|
||||
|
||||
it("blocks nested __proto__ keys", async () => {
|
||||
const tts = await mergeOverride(
|
||||
JSON.parse('{"openai":{"model":"safe","__proto__":{"polluted":"nested"}}}'),
|
||||
);
|
||||
const openai = tts.openai as Record<string, unknown>;
|
||||
|
||||
expect((Object.prototype as Record<string, unknown>).polluted).toBeUndefined();
|
||||
expect(openai.polluted).toBeUndefined();
|
||||
expect(openai.model).toBe("safe");
|
||||
});
|
||||
});
|
||||
@@ -20,6 +20,8 @@ export type TelephonyTtsProvider = {
|
||||
synthesizeForTelephony: (text: string) => Promise<Buffer>;
|
||||
};
|
||||
|
||||
const BLOCKED_MERGE_KEYS = new Set(["__proto__", "prototype", "constructor"]);
|
||||
|
||||
export function createTelephonyTtsProvider(params: {
|
||||
coreConfig: CoreConfig;
|
||||
ttsOverride?: VoiceCallTtsConfig;
|
||||
@@ -86,7 +88,7 @@ function deepMerge<T>(base: T, override: T): T {
|
||||
}
|
||||
const result: Record<string, unknown> = { ...base };
|
||||
for (const [key, value] of Object.entries(override)) {
|
||||
if (value === undefined) {
|
||||
if (BLOCKED_MERGE_KEYS.has(key) || value === undefined) {
|
||||
continue;
|
||||
}
|
||||
const existing = (base as Record<string, unknown>)[key];
|
||||
|
||||
Reference in New Issue
Block a user