fix(guest): normalize guestMode config on load + strict-boolean authorize gate

loadConfig now sanitizes guestMode the same way the write path does: bots is
coerced to "all" | string[] (numbers/objects/missing fall back to the default
"all"), and permissions are rebuilt from defaults with each known flag
strict-coerced to a boolean so a hand-edited/legacy/corrupt config.json can no
longer crash the gate or leak garbage index keys. The authorize guest gate now
uses === true instead of a truthy check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
saopig1andClaude Opus 4.8 committed 2026-06-25 15:59:42 +08:00
1 parent 952f1fbad3
commit 66230e6b43
4 files changed
+84 -9

No files matched your search

+46
View File
@@ -130,4 +130,50 @@ describe("guestMode config", () => {
expect(c.guestMode.permissions.skip).toBe(false); // back-filled default
rmSync(dir, { recursive: true, force: true });
});
// --- B1: loadConfig must sanitize a hand-edited/legacy/corrupt guestMode ---
function loadGuestMode(raw: unknown) {
const dir = mkdtempSync(join(tmpdir(), "tsmb-cfg-"));
const p = join(dir, "config.json");
writeFileSync(p, JSON.stringify(raw));
try {
return loadConfig(p).guestMode;
} finally {
rmSync(dir, { recursive: true, force: true });
}
}
describe("bots normalization", () => {
it("a numeric bots value falls back to the default \"all\" (no crash)", () => {
const gm = loadGuestMode({ guestMode: { bots: 5 } });
expect(gm.bots).toBe("all");
});
it("an array bots value is filtered to strings only", () => {
const gm = loadGuestMode({ guestMode: { bots: ["a", 2, "b"] } });
expect(gm.bots).toEqual(["a", "b"]);
});
it("the literal \"all\" is preserved", () => {
const gm = loadGuestMode({ guestMode: { bots: "all" } });
expect(gm.bots).toBe("all");
});
});
describe("permissions coercion", () => {
it("a non-boolean truthy flag is coerced to false; a real true stays true", () => {
const gm = loadGuestMode({ guestMode: { permissions: { skip: 1, playNext: true } } });
expect(gm.permissions.skip).toBe(false);
expect(gm.permissions.playNext).toBe(true);
});
it("a string permissions value yields defaults with no numeric index keys", () => {
const gm = loadGuestMode({ guestMode: { permissions: "hacked" } });
// 7 known flags present at their defaults
expect(gm.permissions).toEqual({
addToQueue: true, playNext: false, playNow: false,
skip: false, transport: false, removeClear: false, playMode: false,
});
// no garbage index keys leaked from spreading a string
expect((gm.permissions as unknown as Record<string, unknown>)["0"]).toBeUndefined();
});
});
});