mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 22:36:48 +00:00
fix(exec): import legacy approvals into sqlite
This commit is contained in:
@@ -13,7 +13,19 @@ enum ExecApprovalsSQLiteStateStore {
|
||||
}
|
||||
|
||||
static func readRawState() -> String? {
|
||||
OpenClawSQLiteStateStore.readExecApprovalsRaw(configKey: self.configKey)
|
||||
if let raw = OpenClawSQLiteStateStore.readExecApprovalsRaw(configKey: self.configKey) {
|
||||
return raw
|
||||
}
|
||||
guard let raw = try? String(contentsOf: self.legacyURL(), encoding: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
do {
|
||||
try self.writeRawState(raw)
|
||||
try? FileManager.default.removeItem(at: self.legacyURL())
|
||||
} catch {
|
||||
return raw
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
static func writeRawState(_ raw: String) throws {
|
||||
@@ -43,4 +55,8 @@ enum ExecApprovalsSQLiteStateStore {
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
private static func legacyURL() -> URL {
|
||||
OpenClawPaths.stateDirURL.appendingPathComponent("exec-approvals.json", isDirectory: false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,40 @@ struct ExecApprovalsStoreRefactorTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func `ensure state imports legacy json approvals before sqlite defaults`() async throws {
|
||||
try await self.withTempStateDir { stateDir in
|
||||
try FileManager().createDirectory(at: stateDir, withIntermediateDirectories: true)
|
||||
let legacyURL = stateDir.appendingPathComponent("exec-approvals.json")
|
||||
try """
|
||||
{
|
||||
"version": 1,
|
||||
"socket": { "path": "/tmp/legacy.sock", "token": "legacy-token" },
|
||||
"defaults": { "security": "allowlist", "ask": "on-miss" },
|
||||
"agents": {
|
||||
"main": {
|
||||
"allowlist": [
|
||||
{ "id": "00000000-0000-0000-0000-000000000001", "pattern": "/usr/bin/rg" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
""".write(to: legacyURL, atomically: true, encoding: .utf8)
|
||||
|
||||
let ensured = ExecApprovalsStore.ensureState()
|
||||
|
||||
#expect(ensured.socket?.path == "/tmp/legacy.sock")
|
||||
#expect(ensured.socket?.token == "legacy-token")
|
||||
#expect(ensured.defaults?.security == .allowlist)
|
||||
#expect(ensured.defaults?.ask == .onMiss)
|
||||
#expect(ensured.agents?["main"]?.allowlist?.map(\.pattern) == ["/usr/bin/rg"])
|
||||
#expect(!FileManager().fileExists(atPath: legacyURL.path))
|
||||
let storedRaw = try Self.readStoredApprovalsRaw()
|
||||
#expect(storedRaw?.contains("legacy-token") == true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func `update allowlist accepts basename pattern`() async throws {
|
||||
try await self.withTempStateDir { _ in
|
||||
|
||||
@@ -196,6 +196,33 @@ describe("exec approvals store helpers", () => {
|
||||
expect(readApprovalsFile().socket).toEqual(ensured.socket);
|
||||
});
|
||||
|
||||
it("imports legacy JSON approvals before creating SQLite defaults", () => {
|
||||
const dir = createHomeDir();
|
||||
const legacyPath = path.join(dir, ".openclaw", "exec-approvals.json");
|
||||
fs.mkdirSync(path.dirname(legacyPath), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
legacyPath,
|
||||
JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
socket: { path: "/tmp/legacy.sock", token: "legacy-token" },
|
||||
defaults: { security: "allowlist", ask: "on-miss" },
|
||||
agents: { main: { allowlist: [{ pattern: "/usr/bin/rg", id: "keep-id" }] } },
|
||||
} satisfies ExecApprovalsFile,
|
||||
null,
|
||||
2,
|
||||
) + "\n",
|
||||
);
|
||||
|
||||
const ensured = ensureExecApprovals();
|
||||
|
||||
expect(ensured.socket).toEqual({ path: "/tmp/legacy.sock", token: "legacy-token" });
|
||||
expect(ensured.defaults).toEqual({ security: "allowlist", ask: "on-miss" });
|
||||
expect(ensured.agents?.main?.allowlist).toEqual([{ pattern: "/usr/bin/rg", id: "keep-id" }]);
|
||||
expect(fs.existsSync(legacyPath)).toBe(false);
|
||||
expect(readSqliteRaw()).toContain("legacy-token");
|
||||
});
|
||||
|
||||
it("adds trimmed allowlist entries once and persists generated ids", () => {
|
||||
createHomeDir();
|
||||
vi.spyOn(Date, "now").mockReturnValue(123_456);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import type { Insertable } from "kysely";
|
||||
import { DEFAULT_AGENT_ID } from "../routing/session-key.js";
|
||||
import {
|
||||
@@ -218,6 +219,7 @@ const DEFAULT_ASK: ExecAsk = "off";
|
||||
export const DEFAULT_EXEC_APPROVAL_ASK_FALLBACK: ExecSecurity = "full";
|
||||
const DEFAULT_AUTO_ALLOW_SKILLS = false;
|
||||
const DEFAULT_SOCKET = "~/.openclaw/exec-approvals.sock";
|
||||
const LEGACY_EXEC_APPROVALS_FILE = "~/.openclaw/exec-approvals.json";
|
||||
const EXEC_APPROVALS_CONFIG_KEY = "current";
|
||||
|
||||
type ExecApprovalsDatabase = Pick<OpenClawStateKyselyDatabase, "exec_approvals_config">;
|
||||
@@ -467,6 +469,36 @@ function readExecApprovalsRawFromSqlite(env: NodeJS.ProcessEnv = process.env): s
|
||||
return row?.raw_json ?? null;
|
||||
}
|
||||
|
||||
function readLegacyExecApprovalsRaw(env: NodeJS.ProcessEnv = process.env): {
|
||||
raw: string | null;
|
||||
exists: boolean;
|
||||
path: string;
|
||||
} {
|
||||
const filePath = resolveLegacyExecApprovalsPath(env);
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return { raw: null, exists: false, path: filePath };
|
||||
}
|
||||
return { raw: fs.readFileSync(filePath, "utf8"), exists: true, path: filePath };
|
||||
}
|
||||
|
||||
function resolveLegacyExecApprovalsPath(env: NodeJS.ProcessEnv = process.env): string {
|
||||
return expandHomePrefix(LEGACY_EXEC_APPROVALS_FILE, { env });
|
||||
}
|
||||
|
||||
function readExecApprovalsRaw(env: NodeJS.ProcessEnv = process.env): string | null {
|
||||
const sqliteRaw = readExecApprovalsRawFromSqlite(env);
|
||||
if (sqliteRaw !== null) {
|
||||
return sqliteRaw;
|
||||
}
|
||||
const legacy = readLegacyExecApprovalsRaw(env);
|
||||
if (!legacy.exists || legacy.raw === null) {
|
||||
return null;
|
||||
}
|
||||
writeExecApprovalsRawToSqlite(legacy.raw, env);
|
||||
fs.rmSync(legacy.path, { force: true });
|
||||
return legacy.raw;
|
||||
}
|
||||
|
||||
export function writeExecApprovalsRawToSqlite(
|
||||
raw: string,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
@@ -523,7 +555,7 @@ function parseExecApprovalsRaw(raw: string | null): ExecApprovalsFile {
|
||||
}
|
||||
|
||||
export function readExecApprovalsSnapshot(): ExecApprovalsSnapshot {
|
||||
const sqliteRaw = readExecApprovalsRawFromSqlite();
|
||||
const sqliteRaw = readExecApprovalsRaw();
|
||||
return {
|
||||
path: resolveExecApprovalsStoreLocationForDisplay(),
|
||||
exists: sqliteRaw !== null,
|
||||
@@ -534,7 +566,7 @@ export function readExecApprovalsSnapshot(): ExecApprovalsSnapshot {
|
||||
}
|
||||
|
||||
export function loadExecApprovals(): ExecApprovalsFile {
|
||||
return parseExecApprovalsRaw(readExecApprovalsRawFromSqlite());
|
||||
return parseExecApprovalsRaw(readExecApprovalsRaw());
|
||||
}
|
||||
|
||||
export function saveExecApprovals(file: ExecApprovalsFile) {
|
||||
|
||||
Reference in New Issue
Block a user