mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-25 16:12:13 +00:00
fix: sanitize pairing recovery requestId hints (#24771) (thanks @markmusson)
This commit is contained in:
@@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Status/Pairing recovery: show explicit pairing-approval command hints (including requestId when safe) when gateway probe failures report pairing-required closures. (#24771) Thanks @markmusson.
|
||||
- Discord/Threading: recover missing thread parent IDs by refetching thread metadata before resolving parent channel context. (#24897) Thanks @z-x-yang.
|
||||
- Web UI/i18n: load and hydrate saved locale translations during startup so non-English sessions apply immediately without manual toggling. (#24795) Thanks @chilu18.
|
||||
- Plugins/Config schema: support legacy plugin schemas without `toJSONSchema()` by falling back to permissive object schema generation. (#24933) Thanks @pandego.
|
||||
|
||||
@@ -41,6 +41,17 @@ function resolvePairingRecoveryContext(params: {
|
||||
error?: string | null;
|
||||
closeReason?: string | null;
|
||||
}): { requestId: string | null } | null {
|
||||
const sanitizeRequestId = (value: string): string | null => {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
// Keep CLI guidance injection-safe: allow only compact id characters.
|
||||
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(trimmed)) {
|
||||
return null;
|
||||
}
|
||||
return trimmed;
|
||||
};
|
||||
const source = [params.error, params.closeReason]
|
||||
.filter((part) => typeof part === "string" && part.trim().length > 0)
|
||||
.join(" ");
|
||||
@@ -48,7 +59,8 @@ function resolvePairingRecoveryContext(params: {
|
||||
return null;
|
||||
}
|
||||
const requestIdMatch = source.match(/requestId:\s*([^\s)]+)/i);
|
||||
const requestId = requestIdMatch && requestIdMatch[1] ? requestIdMatch[1].trim() : "";
|
||||
const requestId =
|
||||
requestIdMatch && requestIdMatch[1] ? sanitizeRequestId(requestIdMatch[1]) : null;
|
||||
return { requestId: requestId || null };
|
||||
}
|
||||
|
||||
|
||||
@@ -525,6 +525,46 @@ describe("statusCommand", () => {
|
||||
expect(joined).toContain("devices list");
|
||||
});
|
||||
|
||||
it("does not render unsafe requestId content into approval command hints", async () => {
|
||||
mocks.probeGateway.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
url: "ws://127.0.0.1:18789",
|
||||
connectLatencyMs: null,
|
||||
error: "connect failed: pairing required (requestId: req-123;rm -rf /)",
|
||||
close: { code: 1008, reason: "pairing required (requestId: req-123;rm -rf /)" },
|
||||
health: null,
|
||||
status: null,
|
||||
presence: null,
|
||||
configSnapshot: null,
|
||||
});
|
||||
|
||||
runtimeLogMock.mockClear();
|
||||
await statusCommand({}, runtime as never);
|
||||
const joined = runtimeLogMock.mock.calls.map((c: unknown[]) => String(c[0])).join("\n");
|
||||
expect(joined).toContain("Gateway pairing approval required.");
|
||||
expect(joined).not.toContain("devices approve req-123;rm -rf /");
|
||||
expect(joined).toContain("devices approve --latest");
|
||||
});
|
||||
|
||||
it("extracts requestId from close reason when error text omits it", async () => {
|
||||
mocks.probeGateway.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
url: "ws://127.0.0.1:18789",
|
||||
connectLatencyMs: null,
|
||||
error: "connect failed: pairing required",
|
||||
close: { code: 1008, reason: "pairing required (requestId: req-close-456)" },
|
||||
health: null,
|
||||
status: null,
|
||||
presence: null,
|
||||
configSnapshot: null,
|
||||
});
|
||||
|
||||
runtimeLogMock.mockClear();
|
||||
await statusCommand({}, runtime as never);
|
||||
const joined = runtimeLogMock.mock.calls.map((c: unknown[]) => String(c[0])).join("\n");
|
||||
expect(joined).toContain("devices approve req-close-456");
|
||||
});
|
||||
|
||||
it("includes sessions across agents in JSON output", async () => {
|
||||
const originalAgents = mocks.listAgentsForGateway.getMockImplementation();
|
||||
const originalResolveStorePath = mocks.resolveStorePath.getMockImplementation();
|
||||
|
||||
Reference in New Issue
Block a user