|
| 1 | +// Regression tests for sanitizeModelWarningValue's C1 boundary truncation, |
| 2 | +// exercised through the exported resolveConfiguredModelRef warning path. |
| 3 | +// |
| 4 | +// A providerless model value that contains a residual C1 control byte (one that |
| 5 | +// survives ANSI stripping, e.g. U+0080) must have the providerless-model |
| 6 | +// warning truncated at that byte, so trailing attacker-controlled text after a |
| 7 | +// C1 control cannot ride along into the logged warning. |
| 8 | +import { describe, expect, it, vi } from "vitest"; |
| 9 | +import type { OpenClawConfig } from "../config/types.openclaw.js"; |
| 10 | + |
| 11 | +const { warnMock } = vi.hoisted(() => ({ warnMock: vi.fn() })); |
| 12 | + |
| 13 | +vi.mock("../logging/subsystem.js", () => ({ |
| 14 | + createSubsystemLogger: () => ({ |
| 15 | + warn: warnMock, |
| 16 | + info: vi.fn(), |
| 17 | + debug: vi.fn(), |
| 18 | + error: vi.fn(), |
| 19 | + }), |
| 20 | +})); |
| 21 | + |
| 22 | +const { resolveConfiguredModelRef } = await import("./model-selection-shared.js"); |
| 23 | + |
| 24 | +function warnFor(model: string): string { |
| 25 | + warnMock.mockClear(); |
| 26 | + const cfg = { agents: { defaults: { model } } } as unknown as OpenClawConfig; |
| 27 | + const ref = resolveConfiguredModelRef({ |
| 28 | + cfg, |
| 29 | + defaultProvider: "openai", |
| 30 | + defaultModel: "gpt-5", |
| 31 | + }); |
| 32 | + // The resolved ref preserves the raw model; only the logged warning is sanitized. |
| 33 | + expect(ref).toEqual({ provider: "openai", model }); |
| 34 | + return String(warnMock.mock.calls.at(-1)?.[0] ?? ""); |
| 35 | +} |
| 36 | + |
| 37 | +describe("sanitizeModelWarningValue C1 boundary", () => { |
| 38 | + it("truncates the providerless-model warning at a residual C1 byte", () => { |
| 39 | + const PAD = String.fromCharCode(0x80); // C1 control that survives ANSI stripping |
| 40 | + const warn = warnFor(`gpt4${PAD}EVIL`); |
| 41 | + // Warning is truncated at the C1 boundary: the visible suffix is dropped. |
| 42 | + expect(warn).toContain('Model "gpt4" specified without provider'); |
| 43 | + expect(warn).not.toContain("EVIL"); |
| 44 | + expect(warn).not.toContain(PAD); |
| 45 | + }); |
| 46 | + |
| 47 | + it("keeps a clean providerless-model value intact in the warning", () => { |
| 48 | + const warn = warnFor("gpt4"); |
| 49 | + expect(warn).toContain('Model "gpt4" specified without provider'); |
| 50 | + }); |
| 51 | +}); |
0 commit comments