UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

343 lines (342 loc) 10 kB
import { i as redactSecrets, u as redactToolPayloadText } from "./redact-DduLliKq.js"; import { y as resolveStateDir } from "./paths-mvMm5bYV.js"; import path from "node:path"; //#region src/config/io.audit.ts const CONFIG_AUDIT_ARGV_CAP = 8; const SECRET_FLAG_NAMES = new Set([ "--token", "--api-key", "--apikey", "--secret", "--password", "--passwd", "--auth-token", "--access-token", "--refresh-token", "--client-secret", "--hook-token", "--gateway-token", "--bot-token", "--app-token", "--remote-token", "--push-token", "--webhook-secret", "--webhook-token", "--service-account-token", "--op-service-account-token", "--bearer", "--bearer-token", "--pat", "--personal-access-token", "--oauth-token", "--id-token", "--identity-token", "--session-token", "--service-token", "--private-key", "--recovery-key", "--gateway-key", "--session-key", "--active-key" ]); const SECRET_FLAG_SUFFIX_PATTERN = /^--(?:[a-z0-9]+(?:-[a-z0-9]+)*-)?(?:token|secret|password|passwd|api[-_]?key|api[-_]?secret|webhook|credential|bearer|pat|private[-_]?key|recovery[-_]?key|signing[-_]?key|encryption[-_]?key|master[-_]?key|session[-_]?key|gateway[-_]?key|service[-_]?key|hook[-_]?key)$/; function isSecretFlagName(flagName) { if (flagName === null) return false; if (SECRET_FLAG_NAMES.has(flagName)) return true; return SECRET_FLAG_SUFFIX_PATTERN.test(flagName); } function parseFlagName(arg) { if (typeof arg !== "string" || !arg.startsWith("--")) return null; const eq = arg.indexOf("="); return (eq === -1 ? arg : arg.slice(0, eq)).toLowerCase(); } function redactConfigAuditArgv(argv) { const result = []; let redactNext = false; for (const current of argv) { if (typeof current !== "string") { result.push(current); redactNext = false; continue; } if (redactNext) { redactNext = false; result.push("***"); continue; } const currentFlag = parseFlagName(current); if (currentFlag !== null && isSecretFlagName(currentFlag)) { if (current.includes("=")) { const eq = current.indexOf("="); result.push(`${current.slice(0, eq + 1)}***`); continue; } result.push(current); redactNext = true; continue; } result.push(redactToolPayloadText(current)); } return result; } function capArgv(argv) { if (!Array.isArray(argv)) return []; return argv.slice(0, CONFIG_AUDIT_ARGV_CAP); } function snapshotConfigAuditProcessInfo() { return { pid: process.pid, ppid: process.ppid, cwd: process.cwd(), argv: redactConfigAuditArgv(capArgv(process.argv)), execArgv: redactConfigAuditArgv(capArgv(process.execArgv)) }; } const CONFIG_AUDIT_LOG_FILENAME = "config-audit.jsonl"; function normalizeAuditLabel(value) { if (typeof value !== "string") return null; const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : null; } function resolveConfigAuditProcessInfo(processInfo) { if (processInfo) return { ...processInfo, argv: redactConfigAuditArgv(capArgv(processInfo.argv)), execArgv: redactConfigAuditArgv(capArgv(processInfo.execArgv)) }; return snapshotConfigAuditProcessInfo(); } function resolveConfigAuditLogPath(env, homedir) { return path.join(resolveStateDir(env, homedir), "logs", CONFIG_AUDIT_LOG_FILENAME); } function formatConfigOverwriteLogMessage(params) { const changeSummary = typeof params.changedPathCount === "number" ? `, changedPaths=${params.changedPathCount}` : ""; return `Config overwrite: ${params.configPath} (sha256 ${params.previousHash ?? "unknown"} -> ${params.nextHash}, backup=${params.configPath}.bak${changeSummary})`; } function createConfigWriteAuditRecordBase(params) { const processSnapshot = resolveConfigAuditProcessInfo(params.processInfo); return { ts: params.now ?? (/* @__PURE__ */ new Date()).toISOString(), source: "config-io", event: "config.write", configPath: params.configPath, pid: processSnapshot.pid, ppid: processSnapshot.ppid, cwd: processSnapshot.cwd, argv: processSnapshot.argv, execArgv: processSnapshot.execArgv, watchMode: params.env.OPENCLAW_WATCH_MODE === "1", watchSession: normalizeAuditLabel(params.env.OPENCLAW_WATCH_SESSION), watchCommand: normalizeAuditLabel(params.env.OPENCLAW_WATCH_COMMAND), existsBefore: params.existsBefore, previousHash: params.previousHash, nextHash: params.nextHash, previousBytes: params.previousBytes, nextBytes: params.nextBytes, previousDev: params.previousMetadata.dev, previousIno: params.previousMetadata.ino, previousMode: params.previousMetadata.mode, previousNlink: params.previousMetadata.nlink, previousUid: params.previousMetadata.uid, previousGid: params.previousMetadata.gid, changedPathCount: typeof params.changedPathCount === "number" ? params.changedPathCount : null, hasMetaBefore: params.hasMetaBefore, hasMetaAfter: params.hasMetaAfter, gatewayModeBefore: params.gatewayModeBefore, gatewayModeAfter: params.gatewayModeAfter, suspicious: params.suspicious }; } function finalizeConfigWriteAuditRecord(params) { const errorCode = params.err && typeof params.err === "object" && "code" in params.err && typeof params.err.code === "string" ? params.err.code : void 0; const errorMessage = params.err && typeof params.err === "object" && "message" in params.err && typeof params.err.message === "string" ? params.err.message : void 0; const nextMetadata = params.nextMetadata ?? { dev: null, ino: null, mode: null, nlink: null, uid: null, gid: null }; const success = params.result !== "failed" && params.result !== "rejected"; return { ...params.base, result: params.result, nextHash: success ? params.base.nextHash : null, nextBytes: success ? params.base.nextBytes : null, nextDev: success ? nextMetadata.dev : null, nextIno: success ? nextMetadata.ino : null, nextMode: success ? nextMetadata.mode : null, nextNlink: success ? nextMetadata.nlink : null, nextUid: success ? nextMetadata.uid : null, nextGid: success ? nextMetadata.gid : null, errorCode, errorMessage }; } function resolveConfigAuditAppendRecord(params) { if ("record" in params) return redactSecrets(params.record); const { fs: _fs, env: _env, homedir: _homedir, ...record } = params; return redactSecrets(record); } async function scrubConfigAuditLog(params) { const auditPath = resolveConfigAuditLogPath(params.env, params.homedir); let raw; try { raw = await params.fs.promises.readFile(auditPath, "utf-8"); } catch (err) { if (err?.code === "ENOENT") return { scanned: 0, rewritten: 0, skipped: 0, aborted: false }; throw err; } const originalByteLength = Buffer.byteLength(raw, "utf-8"); let scanned = 0; let rewritten = 0; let skipped = 0; let changed = false; const outLines = []; const lines = raw.split("\n"); for (const line of lines) { if (line.length === 0) { outLines.push(line); continue; } scanned += 1; let record; try { record = JSON.parse(line); } catch { outLines.push(line); skipped += 1; continue; } if (!record || typeof record !== "object" || Array.isArray(record)) { outLines.push(line); skipped += 1; continue; } const obj = record; let mutated = false; for (const key of ["argv", "execArgv"]) { const value = obj[key]; if (!Array.isArray(value)) continue; if (!value.every((entry) => typeof entry === "string")) continue; const redacted = redactConfigAuditArgv(value); let differs = false; for (let i = 0; i < redacted.length; i++) if (redacted[i] !== value[i]) { differs = true; break; } if (differs) { obj[key] = redacted; mutated = true; } } if (mutated) { rewritten += 1; changed = true; outLines.push(JSON.stringify(obj)); } else outLines.push(line); } if (!changed || params.dryRun) return { scanned, rewritten, skipped, aborted: false }; let preRenameSize; try { preRenameSize = (await params.fs.promises.stat(auditPath)).size; } catch { return { scanned, rewritten, skipped, aborted: true }; } if (preRenameSize !== originalByteLength) return { scanned, rewritten, skipped, aborted: true }; const tmpPath = `${auditPath}.scrub.tmp`; try { await params.fs.promises.writeFile(tmpPath, outLines.join("\n"), { encoding: "utf-8", mode: 384 }); let finalPreRenameSize; try { finalPreRenameSize = (await params.fs.promises.stat(auditPath)).size; } catch { try { await params.fs.promises.unlink(tmpPath); } catch {} return { scanned, rewritten, skipped, aborted: true }; } if (finalPreRenameSize !== originalByteLength) { try { await params.fs.promises.unlink(tmpPath); } catch {} return { scanned, rewritten, skipped, aborted: true }; } await params.fs.promises.rename(tmpPath, auditPath); } catch (err) { try { await params.fs.promises.unlink(tmpPath); } catch {} throw err; } return { scanned, rewritten, skipped, aborted: false }; } async function appendConfigAuditRecord(params) { try { const auditPath = resolveConfigAuditLogPath(params.env, params.homedir); const record = resolveConfigAuditAppendRecord(params); await params.fs.promises.mkdir(path.dirname(auditPath), { recursive: true, mode: 448 }); await params.fs.promises.appendFile(auditPath, `${JSON.stringify(record)}\n`, { encoding: "utf-8", mode: 384 }); } catch {} } function appendConfigAuditRecordSync(params) { try { const auditPath = resolveConfigAuditLogPath(params.env, params.homedir); const record = resolveConfigAuditAppendRecord(params); params.fs.mkdirSync(path.dirname(auditPath), { recursive: true, mode: 448 }); params.fs.appendFileSync(auditPath, `${JSON.stringify(record)}\n`, { encoding: "utf-8", mode: 384 }); } catch {} } //#endregion export { formatConfigOverwriteLogMessage as a, finalizeConfigWriteAuditRecord as i, appendConfigAuditRecordSync as n, scrubConfigAuditLog as o, createConfigWriteAuditRecordBase as r, snapshotConfigAuditProcessInfo as s, appendConfigAuditRecord as t };