mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-20 21:21:44 +00:00
fix(google-meet): guard node-host meet URLs (#104687)
This commit is contained in:
@@ -63,6 +63,33 @@ describe("google-meet node host bridge sessions", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects non-Meet start URLs before local Chrome side effects", async () => {
|
||||
const originalPlatform = process.platform;
|
||||
children.length = 0;
|
||||
vi.mocked(spawnSync).mockClear();
|
||||
|
||||
Object.defineProperty(process, "platform", { configurable: true, value: "darwin" });
|
||||
try {
|
||||
await expect(
|
||||
handleGoogleMeetNodeHostCommand(
|
||||
JSON.stringify({
|
||||
action: "start",
|
||||
url: "https://example.com/private-call",
|
||||
mode: "realtime",
|
||||
launch: true,
|
||||
audioInputCommand: ["mock-rec"],
|
||||
audioOutputCommand: ["mock-play"],
|
||||
}),
|
||||
),
|
||||
).rejects.toThrow("url must be an explicit https://meet.google.com/... URL");
|
||||
|
||||
expect(spawnSync).not.toHaveBeenCalled();
|
||||
expect(children).toHaveLength(0);
|
||||
} finally {
|
||||
Object.defineProperty(process, "platform", { configurable: true, value: originalPlatform });
|
||||
}
|
||||
});
|
||||
|
||||
it("starts observe-only Chrome without BlackHole or bridge processes", async () => {
|
||||
const originalPlatform = process.platform;
|
||||
children.length = 0;
|
||||
|
||||
29
extensions/google-meet/src/meet-url.ts
Normal file
29
extensions/google-meet/src/meet-url.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
// Google Meet plugin module implements shared Meet URL contracts.
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
export function normalizeMeetUrl(input: unknown): string {
|
||||
const raw = normalizeOptionalString(input);
|
||||
if (!raw) {
|
||||
throw new Error("url required");
|
||||
}
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(raw);
|
||||
} catch {
|
||||
throw new Error("url must be a valid Google Meet URL");
|
||||
}
|
||||
if (url.protocol !== "https:" || url.hostname.toLowerCase() !== "meet.google.com") {
|
||||
throw new Error("url must be an explicit https://meet.google.com/... URL");
|
||||
}
|
||||
if (!/^\/[a-z]{3}-[a-z]{4}-[a-z]{3}(?:$|[/?#])/i.test(url.pathname)) {
|
||||
throw new Error("url must include a Google Meet meeting code");
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND,
|
||||
DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND,
|
||||
} from "./config.js";
|
||||
import { normalizeMeetUrl } from "./meet-url.js";
|
||||
import {
|
||||
GOOGLE_MEET_SYSTEM_PROFILER_COMMAND,
|
||||
outputMentionsBlackHole2ch,
|
||||
@@ -278,10 +279,7 @@ function clearAudio(params: Record<string, unknown>) {
|
||||
}
|
||||
|
||||
function startChrome(params: Record<string, unknown>) {
|
||||
const url = readString(params.url);
|
||||
if (!url) {
|
||||
throw new Error("url required");
|
||||
}
|
||||
const url = normalizeMeetUrl(params.url);
|
||||
const timeoutMs = readNumber(params.joinTimeoutMs, 30_000);
|
||||
const mode = readString(params.mode);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import type {
|
||||
OpenClawPluginNodeInvokePolicyResult,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import type { GoogleMeetConfig } from "./config.js";
|
||||
import { normalizeMeetUrl } from "./runtime.js";
|
||||
import { normalizeMeetUrl } from "./meet-url.js";
|
||||
|
||||
export const GOOGLE_MEET_CHROME_NODE_COMMAND = "googlemeet.chrome";
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
GoogleMeetModeInput,
|
||||
GoogleMeetTransport,
|
||||
} from "./config.js";
|
||||
import { normalizeMeetUrl } from "./meet-url.js";
|
||||
import { addGoogleMeetSetupCheck, getGoogleMeetSetupStatus } from "./setup.js";
|
||||
import {
|
||||
isSameMeetUrlForReuse,
|
||||
@@ -50,6 +51,8 @@ import {
|
||||
type VoiceCallGateway,
|
||||
} from "./voice-call-gateway.js";
|
||||
|
||||
export { normalizeMeetUrl } from "./meet-url.js";
|
||||
|
||||
type ChromeAudioBridgeResult = NonNullable<
|
||||
| Awaited<ReturnType<typeof launchChromeMeet>>["audioBridge"]
|
||||
| Awaited<ReturnType<typeof launchChromeMeetOnNode>>["audioBridge"]
|
||||
@@ -78,26 +81,6 @@ function buildTwilioVoiceCallSessionKey(meetingSessionId: string): string {
|
||||
return `voice:google-meet:${meetingSessionId}`;
|
||||
}
|
||||
|
||||
export function normalizeMeetUrl(input: unknown): string {
|
||||
const raw = normalizeOptionalString(input);
|
||||
if (!raw) {
|
||||
throw new Error("url required");
|
||||
}
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(raw);
|
||||
} catch {
|
||||
throw new Error("url must be a valid Google Meet URL");
|
||||
}
|
||||
if (url.protocol !== "https:" || url.hostname.toLowerCase() !== "meet.google.com") {
|
||||
throw new Error("url must be an explicit https://meet.google.com/... URL");
|
||||
}
|
||||
if (!/^\/[a-z]{3}-[a-z]{4}-[a-z]{3}(?:$|[/?#])/i.test(url.pathname)) {
|
||||
throw new Error("url must include a Google Meet meeting code");
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function resolveTransport(input: GoogleMeetTransport | undefined, config: GoogleMeetConfig) {
|
||||
return input ?? config.defaultTransport;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user