fix(pairing): rethrow unreadable allowlist files

Co-authored-by: openclaw-clawsweeper[bot] <280122609+openclaw-clawsweeper[bot]@users.noreply.github.com>
This commit is contained in:
clawsweeper[bot]
2026-04-30 22:46:52 -07:00
committed by GitHub
parent 50d8ef2229
commit 9931603adb
2 changed files with 67 additions and 9 deletions

View File

@@ -1,6 +1,7 @@
import fs from "node:fs";
import { describe, expect, it, vi } from "vitest";
import {
readAllowFromFileWithExists,
readAllowFromFileSyncWithExists,
resolveAllowFromAccountId,
resolveAllowFromFilePath,
@@ -73,6 +74,52 @@ describe("allow-from store file keys", () => {
});
describe("allow-from store file reads", () => {
it("rethrows unexpected async read errors after a successful stat", async () => {
const error = fsError("permission denied", "EACCES");
const statSpy = vi.spyOn(fs.promises, "stat").mockResolvedValue({
mtimeMs: 1,
size: 2,
} as fs.Stats);
const readSpy = vi.spyOn(fs.promises, "readFile").mockRejectedValue(error);
try {
await expect(
readAllowFromFileWithExists({
cacheNamespace: "test-async-read-error",
filePath: "/tmp/openclaw-allowFrom.json",
normalizeStore: () => [],
}),
).rejects.toBe(error);
} finally {
readSpy.mockRestore();
statSpy.mockRestore();
}
});
it("rethrows unexpected sync read errors after a successful stat", () => {
const error = fsError("permission denied", "EACCES");
const statSpy = vi.spyOn(fs, "statSync").mockReturnValue({
mtimeMs: 1,
size: 2,
} as fs.Stats);
const readSpy = vi.spyOn(fs, "readFileSync").mockImplementation(() => {
throw error;
});
try {
expect(() =>
readAllowFromFileSyncWithExists({
cacheNamespace: "test-sync-read-error",
filePath: "/tmp/openclaw-allowFrom.json",
normalizeStore: () => [],
}),
).toThrow(error);
} finally {
readSpy.mockRestore();
statSpy.mockRestore();
}
});
it("rethrows unexpected sync stat errors", () => {
const error = fsError("permission denied", "EACCES");
const statSpy = vi.spyOn(fs, "statSync").mockImplementation(() => {

View File

@@ -3,7 +3,6 @@ import os from "node:os";
import path from "node:path";
import { resolveOAuthDir, resolveStateDir } from "../config/paths.js";
import { resolveRequiredHomeDir } from "../infra/home-dir.js";
import { readJsonFileWithFallback } from "../plugin-sdk/json-store.js";
import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
import {
normalizeLowercaseStringOrEmpty,
@@ -243,22 +242,34 @@ export async function readAllowFromFileWithExists(params: {
return { entries: [], exists: false };
}
const { value, exists } = await readJsonFileWithFallback<AllowFromStore>(params.filePath, {
version: 1,
allowFrom: [],
});
const entries = params.normalizeStore(value);
let raw = "";
try {
raw = await fs.promises.readFile(params.filePath, "utf8");
} catch (err) {
const code = (err as { code?: string }).code;
if (code === "ENOENT") {
return { entries: [], exists: false };
}
throw err;
}
let entries: string[] = [];
try {
entries = params.normalizeStore(JSON.parse(raw) as AllowFromStore);
} catch {
entries = [];
}
setAllowFromFileReadCache({
cacheNamespace: params.cacheNamespace,
filePath: params.filePath,
entry: {
exists,
exists: true,
mtimeMs: stat.mtimeMs,
size: stat.size,
entries,
},
});
return { entries, exists };
return { entries, exists: true };
}
export function readAllowFromFileSyncWithExists(params: {
@@ -296,7 +307,7 @@ export function readAllowFromFileSyncWithExists(params: {
if (code === "ENOENT") {
return { entries: [], exists: false };
}
return { entries: [], exists: false };
throw err;
}
try {