diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f331d7d1f1..87e1ea09392 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ Docs: https://docs.openclaw.ai - Memory/QMD: keep `memory_search` session-hit paths roundtrip-safe when exported session markdown lives under the workspace `qmd/` directory, so `memory_get` can read the exact returned path instead of failing on the generic `qmd/sessions/...` alias. (#43519) Thanks @holgergruenhagen and @vincentkoc. - Memory/QMD: treat null-byte collection corruption the same when QMD surfaces it as `ENOENT`, so managed-collection repair still rebuilds and retries instead of leaving QMD stuck on a broken path. Thanks @vincentkoc. - Memory/QMD: stop rewriting Han/CJK BM25 queries before `qmd search`, so OpenClaw search semantics match direct QMD results for mixed and spaced Chinese queries. Thanks @vincentkoc. +- Voice call/Plivo: pin stored callback bases to the configured public webhook URL so later call-control redirects stay on the intended origin even if webhook transport metadata differs. Thanks @zsxsoft and @vincentkoc. - Agents/memory flush: keep daily memory flush files append-only during embedded attempts so compaction writes do not overwrite earlier notes. (#53725) Thanks @HPluseven. - Web UI/markdown: stop bare auto-links from swallowing adjacent CJK text while preserving valid mixed-script path and query characters in rendered links. (#48410) Thanks @jnuyao. - BlueBubbles/iMessage: coalesce URL-only inbound messages with their link-preview balloon again so sharing a bare link no longer drops the URL from agent context. Thanks @vincentkoc. diff --git a/extensions/voice-call/src/providers/plivo.test.ts b/extensions/voice-call/src/providers/plivo.test.ts index e1aa6b492fa..a378f4b8f28 100644 --- a/extensions/voice-call/src/providers/plivo.test.ts +++ b/extensions/voice-call/src/providers/plivo.test.ts @@ -64,4 +64,30 @@ describe("PlivoProvider", () => { "plivo:v3:verified", ); }); + + it("pins stored callback bases to publicUrl instead of request Host", () => { + const provider = new PlivoProvider( + { + authId: "MA000000000000000000", + authToken: "test-token", + }, + { + publicUrl: "https://voice.openclaw.ai/voice/webhook?provider=plivo", + }, + ); + + provider.parseWebhookEvent({ + headers: { host: "attacker.example" }, + rawBody: + "CallUUID=call-uuid&CallStatus=in-progress&Direction=outbound&From=%2B15550000000&To=%2B15550000001&Event=StartApp", + url: "https://attacker.example/voice/webhook?provider=plivo&flow=answer&callId=internal-call-id", + method: "POST", + query: { provider: "plivo", flow: "answer", callId: "internal-call-id" }, + }); + + const callbackMap = (provider as unknown as { callUuidToWebhookUrl: Map }) + .callUuidToWebhookUrl; + + expect(callbackMap.get("call-uuid")).toBe("https://voice.openclaw.ai/voice/webhook"); + }); }); diff --git a/extensions/voice-call/src/providers/plivo.ts b/extensions/voice-call/src/providers/plivo.ts index 992ed478b89..2e257037e6c 100644 --- a/extensions/voice-call/src/providers/plivo.ts +++ b/extensions/voice-call/src/providers/plivo.ts @@ -544,6 +544,13 @@ export class PlivoProvider implements VoiceCallProvider { private baseWebhookUrlFromCtx(ctx: WebhookContext): string | null { try { + if (this.options.publicUrl) { + const base = new URL(this.options.publicUrl); + const requestUrl = new URL(ctx.url); + base.pathname = requestUrl.pathname; + return `${base.origin}${base.pathname}`; + } + const u = new URL( reconstructWebhookUrl(ctx, { allowedHosts: this.options.webhookSecurity?.allowedHosts,