From 16322d5cfcf7e55ccb1cb8eab811deb9afe25660 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 27 Apr 2026 11:50:42 -0700 Subject: [PATCH] fix(bonjour): harden DNS label truncation --- extensions/bonjour/src/advertiser.test.ts | 3 ++- extensions/bonjour/src/advertiser.ts | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/extensions/bonjour/src/advertiser.test.ts b/extensions/bonjour/src/advertiser.test.ts index c5c79dd08d9..939082acba1 100644 --- a/extensions/bonjour/src/advertiser.test.ts +++ b/extensions/bonjour/src/advertiser.test.ts @@ -736,7 +736,7 @@ describe("gateway bonjour advertiser", () => { }); it("truncates service name exceeding 63-byte DNS label limit", async () => { - const longHostname = "app-41627eae5842473f9e05f139ea307277-7f9477f4d6-lqqzf"; + const longHostname = "app-41627eae5842473f9e05f139ea307277-7f9477f4d6-lqqzf-abcdefghi"; enableAdvertiserUnitMode(longHostname); const destroy = vi.fn().mockResolvedValue(undefined); @@ -755,6 +755,7 @@ describe("gateway bonjour advertiser", () => { // Both name and hostname must be within the 63-byte DNS label limit expect(new TextEncoder().encode(serviceName).byteLength).toBeLessThanOrEqual(63); expect(new TextEncoder().encode(hostname).byteLength).toBeLessThanOrEqual(63); + expect(hostname).not.toMatch(/-$/); await started.stop(); }); diff --git a/extensions/bonjour/src/advertiser.ts b/extensions/bonjour/src/advertiser.ts index 68cd938a58e..b162212568b 100644 --- a/extensions/bonjour/src/advertiser.ts +++ b/extensions/bonjour/src/advertiser.ts @@ -185,7 +185,7 @@ function resolveSystemMdnsHostname(): string | null { const MAX_DNS_LABEL_BYTES = 63; -function truncateToDnsLabel(name: string): string { +function truncateToDnsLabel(name: string, fallback = "OpenClaw"): string { const encoder = new TextEncoder(); const encoded = encoder.encode(name); if (encoded.byteLength <= MAX_DNS_LABEL_BYTES) { @@ -195,7 +195,12 @@ function truncateToDnsLabel(name: string): string { const truncated = encoded.slice(0, MAX_DNS_LABEL_BYTES); const decoded = new TextDecoder("utf-8", { fatal: false }).decode(truncated); // Strip any replacement character from incomplete multi-byte sequence at the end - return decoded.replace(/\uFFFD$/, "").trim() || "OpenClaw"; + return ( + decoded + .replace(/\uFFFD$/, "") + .replace(/-+$/, "") + .trim() || fallback + ); } function safeServiceName(name: string) { @@ -373,6 +378,7 @@ export async function startGatewayBonjourAdvertiser( .replace(/\.local$/i, "") .split(".")[0] .trim() || "openclaw", + "openclaw", ); const instanceName = typeof opts.instanceName === "string" && opts.instanceName.trim()