UNPKG

@gguf/claw

Version:

WhatsApp gateway CLI (Baileys web) with Pi RPC agent

503 lines (500 loc) 16.1 kB
import { B as resolveConfigPath, J as resolveOAuthDir, O as isRich, X as resolveStateDir, k as theme, p as defaultRuntime } from "./entry.js"; import "./auth-profiles-CfFGCDJa.js"; import { t as formatCliCommand } from "./command-format-3xiXujG0.js"; import { N as normalizeAgentId, c as resolveDefaultAgentId } from "./agent-scope-jm0ZdXwM.js"; import { g as shortenHomePath, h as shortenHomeInString } from "./utils-PmTbZoD1.js"; import { n as runExec } from "./exec-BIMFe4XS.js"; import "./github-copilot-token-rP-6QdKv.js"; import { D as INCLUDE_KEY, O as MAX_INCLUDE_DEPTH, n as createConfigIO, r as loadConfig } from "./config-DCT1RAo6.js"; import "./manifest-registry-tuAcHxrV.js"; import "./server-context-CM_E6wD5.js"; import "./errors-DdT2Dtkb.js"; import "./tailscale-BUcKO8Rr.js"; import "./auth-viF_w60n.js"; import "./client-cU7Xg1MO.js"; import "./call-CfqL-4Nc.js"; import "./message-channel-CAFcg7mw.js"; import { t as formatDocsLink } from "./links-jGisPfXW.js"; import "./plugins-TrKFfrLt.js"; import "./logging-fywhKCmE.js"; import "./accounts-B5QZU96b.js"; import "./paths-RvF0P6tQ.js"; import "./routes-yI5QIzeL.js"; import "./sandbox-Cnq9TXEn.js"; import "./skills-DtwGIkTI.js"; import "./commands-DMKDOFmC.js"; import { i as readChannelAllowFromStore } from "./pairing-store-DMex6WWe.js"; import { n as createIcaclsResetCommand, r as formatIcaclsResetCommand, t as runSecurityAudit } from "./audit-D-OqdjQu.js"; import path from "node:path"; import JSON5 from "json5"; import fs from "node:fs/promises"; //#region src/security/fix.ts async function safeChmod(params) { try { const st = await fs.lstat(params.path); if (st.isSymbolicLink()) return { kind: "chmod", path: params.path, mode: params.mode, ok: false, skipped: "symlink" }; if (params.require === "dir" && !st.isDirectory()) return { kind: "chmod", path: params.path, mode: params.mode, ok: false, skipped: "not-a-directory" }; if (params.require === "file" && !st.isFile()) return { kind: "chmod", path: params.path, mode: params.mode, ok: false, skipped: "not-a-file" }; if ((st.mode & 511) === params.mode) return { kind: "chmod", path: params.path, mode: params.mode, ok: false, skipped: "already" }; await fs.chmod(params.path, params.mode); return { kind: "chmod", path: params.path, mode: params.mode, ok: true }; } catch (err) { if (err.code === "ENOENT") return { kind: "chmod", path: params.path, mode: params.mode, ok: false, skipped: "missing" }; return { kind: "chmod", path: params.path, mode: params.mode, ok: false, error: String(err) }; } } async function safeAclReset(params) { const display = formatIcaclsResetCommand(params.path, { isDir: params.require === "dir", env: params.env }); try { const st = await fs.lstat(params.path); if (st.isSymbolicLink()) return { kind: "icacls", path: params.path, command: display, ok: false, skipped: "symlink" }; if (params.require === "dir" && !st.isDirectory()) return { kind: "icacls", path: params.path, command: display, ok: false, skipped: "not-a-directory" }; if (params.require === "file" && !st.isFile()) return { kind: "icacls", path: params.path, command: display, ok: false, skipped: "not-a-file" }; const cmd = createIcaclsResetCommand(params.path, { isDir: st.isDirectory(), env: params.env }); if (!cmd) return { kind: "icacls", path: params.path, command: display, ok: false, skipped: "missing-user" }; await (params.exec ?? runExec)(cmd.command, cmd.args); return { kind: "icacls", path: params.path, command: cmd.display, ok: true }; } catch (err) { if (err.code === "ENOENT") return { kind: "icacls", path: params.path, command: display, ok: false, skipped: "missing" }; return { kind: "icacls", path: params.path, command: display, ok: false, error: String(err) }; } } function setGroupPolicyAllowlist(params) { if (!params.cfg.channels) return; const section = params.cfg.channels[params.channel]; if (!section || typeof section !== "object") return; if (section.groupPolicy === "open") { section.groupPolicy = "allowlist"; params.changes.push(`channels.${params.channel}.groupPolicy=open -> allowlist`); params.policyFlips.add(`channels.${params.channel}.`); } const accounts = section.accounts; if (!accounts || typeof accounts !== "object") return; for (const [accountId, accountValue] of Object.entries(accounts)) { if (!accountId) continue; if (!accountValue || typeof accountValue !== "object") continue; const account = accountValue; if (account.groupPolicy === "open") { account.groupPolicy = "allowlist"; params.changes.push(`channels.${params.channel}.accounts.${accountId}.groupPolicy=open -> allowlist`); params.policyFlips.add(`channels.${params.channel}.accounts.${accountId}.`); } } } function setWhatsAppGroupAllowFromFromStore(params) { const section = params.cfg.channels?.whatsapp; if (!section || typeof section !== "object") return; if (params.storeAllowFrom.length === 0) return; const maybeApply = (prefix, obj) => { if (!params.policyFlips.has(prefix)) return; const allowFrom = Array.isArray(obj.allowFrom) ? obj.allowFrom : []; const groupAllowFrom = Array.isArray(obj.groupAllowFrom) ? obj.groupAllowFrom : []; if (allowFrom.length > 0) return; if (groupAllowFrom.length > 0) return; obj.groupAllowFrom = params.storeAllowFrom; params.changes.push(`${prefix}groupAllowFrom=pairing-store`); }; maybeApply("channels.whatsapp.", section); const accounts = section.accounts; if (!accounts || typeof accounts !== "object") return; for (const [accountId, accountValue] of Object.entries(accounts)) { if (!accountValue || typeof accountValue !== "object") continue; const account = accountValue; maybeApply(`channels.whatsapp.accounts.${accountId}.`, account); } } function applyConfigFixes(params) { const next = structuredClone(params.cfg ?? {}); const changes = []; const policyFlips = /* @__PURE__ */ new Set(); if (next.logging?.redactSensitive === "off") { next.logging = { ...next.logging, redactSensitive: "tools" }; changes.push("logging.redactSensitive=off -> \"tools\""); } for (const channel of [ "telegram", "whatsapp", "discord", "signal", "imessage", "slack", "msteams" ]) setGroupPolicyAllowlist({ cfg: next, channel, changes, policyFlips }); return { cfg: next, changes, policyFlips }; } function listDirectIncludes(parsed) { const out = []; const visit = (value) => { if (!value) return; if (Array.isArray(value)) { for (const item of value) visit(item); return; } if (typeof value !== "object") return; const rec = value; const includeVal = rec[INCLUDE_KEY]; if (typeof includeVal === "string") out.push(includeVal); else if (Array.isArray(includeVal)) { for (const item of includeVal) if (typeof item === "string") out.push(item); } for (const v of Object.values(rec)) visit(v); }; visit(parsed); return out; } function resolveIncludePath(baseConfigPath, includePath) { return path.normalize(path.isAbsolute(includePath) ? includePath : path.resolve(path.dirname(baseConfigPath), includePath)); } async function collectIncludePathsRecursive(params) { const visited = /* @__PURE__ */ new Set(); const result = []; const walk = async (basePath, parsed, depth) => { if (depth > MAX_INCLUDE_DEPTH) return; for (const raw of listDirectIncludes(parsed)) { const resolved = resolveIncludePath(basePath, raw); if (visited.has(resolved)) continue; visited.add(resolved); result.push(resolved); const rawText = await fs.readFile(resolved, "utf-8").catch(() => null); if (!rawText) continue; const nestedParsed = (() => { try { return JSON5.parse(rawText); } catch { return null; } })(); if (nestedParsed) await walk(resolved, nestedParsed, depth + 1); } }; await walk(params.configPath, params.parsed, 0); return result; } async function chmodCredentialsAndAgentState(params) { const credsDir = resolveOAuthDir(params.env, params.stateDir); params.actions.push(await safeChmod({ path: credsDir, mode: 448, require: "dir" })); const credsEntries = await fs.readdir(credsDir, { withFileTypes: true }).catch(() => []); for (const entry of credsEntries) { if (!entry.isFile()) continue; if (!entry.name.endsWith(".json")) continue; const p = path.join(credsDir, entry.name); params.actions.push(await safeChmod({ path: p, mode: 384, require: "file" })); } const ids = /* @__PURE__ */ new Set(); ids.add(resolveDefaultAgentId(params.cfg)); const list = Array.isArray(params.cfg.agents?.list) ? params.cfg.agents?.list : []; for (const agent of list ?? []) { if (!agent || typeof agent !== "object") continue; const id = typeof agent.id === "string" ? agent.id.trim() : ""; if (id) ids.add(id); } for (const agentId of ids) { const normalizedAgentId = normalizeAgentId(agentId); const agentRoot = path.join(params.stateDir, "agents", normalizedAgentId); const agentDir = path.join(agentRoot, "agent"); const sessionsDir = path.join(agentRoot, "sessions"); params.actions.push(await safeChmod({ path: agentRoot, mode: 448, require: "dir" })); params.actions.push(await params.applyPerms({ path: agentDir, mode: 448, require: "dir" })); const authPath = path.join(agentDir, "auth-profiles.json"); params.actions.push(await params.applyPerms({ path: authPath, mode: 384, require: "file" })); params.actions.push(await params.applyPerms({ path: sessionsDir, mode: 448, require: "dir" })); const storePath = path.join(sessionsDir, "sessions.json"); params.actions.push(await params.applyPerms({ path: storePath, mode: 384, require: "file" })); } } async function fixSecurityFootguns(opts) { const env = opts?.env ?? process.env; const platform = opts?.platform ?? process.platform; const exec = opts?.exec ?? runExec; const isWindows = platform === "win32"; const stateDir = opts?.stateDir ?? resolveStateDir(env); const configPath = opts?.configPath ?? resolveConfigPath(env, stateDir); const actions = []; const errors = []; const io = createConfigIO({ env, configPath }); const snap = await io.readConfigFileSnapshot(); if (!snap.valid) errors.push(...snap.issues.map((i) => `${i.path}: ${i.message}`)); let configWritten = false; let changes = []; if (snap.valid) { const fixed = applyConfigFixes({ cfg: snap.config, env }); changes = fixed.changes; const whatsappStoreAllowFrom = await readChannelAllowFromStore("whatsapp", env).catch(() => []); if (whatsappStoreAllowFrom.length > 0) setWhatsAppGroupAllowFromFromStore({ cfg: fixed.cfg, storeAllowFrom: whatsappStoreAllowFrom, changes, policyFlips: fixed.policyFlips }); if (changes.length > 0) try { await io.writeConfigFile(fixed.cfg); configWritten = true; } catch (err) { errors.push(`writeConfigFile failed: ${String(err)}`); } } const applyPerms = (params) => isWindows ? safeAclReset({ path: params.path, require: params.require, env, exec }) : safeChmod({ path: params.path, mode: params.mode, require: params.require }); actions.push(await applyPerms({ path: stateDir, mode: 448, require: "dir" })); actions.push(await applyPerms({ path: configPath, mode: 384, require: "file" })); if (snap.exists) { const includePaths = await collectIncludePathsRecursive({ configPath: snap.path, parsed: snap.parsed }).catch(() => []); for (const p of includePaths) actions.push(await applyPerms({ path: p, mode: 384, require: "file" })); } await chmodCredentialsAndAgentState({ env, stateDir, cfg: snap.config ?? {}, actions, applyPerms }).catch((err) => { errors.push(`chmodCredentialsAndAgentState failed: ${String(err)}`); }); return { ok: errors.length === 0, stateDir, configPath, configWritten, changes, actions, errors }; } //#endregion //#region src/cli/security-cli.ts function formatSummary(summary) { const rich = isRich(); const c = summary.critical; const w = summary.warn; const i = summary.info; const parts = []; parts.push(rich ? theme.error(`${c} critical`) : `${c} critical`); parts.push(rich ? theme.warn(`${w} warn`) : `${w} warn`); parts.push(rich ? theme.muted(`${i} info`) : `${i} info`); return parts.join(" · "); } function registerSecurityCli(program) { program.command("security").description("Security tools (audit)").addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/security", "docs.openclaw.ai/cli/security")}\n`).command("audit").description("Audit config + local state for common security foot-guns").option("--deep", "Attempt live Gateway probe (best-effort)", false).option("--fix", "Apply safe fixes (tighten defaults + chmod state/config)", false).option("--json", "Print JSON", false).action(async (opts) => { const fixResult = opts.fix ? await fixSecurityFootguns().catch((_err) => null) : null; const report = await runSecurityAudit({ config: loadConfig(), deep: Boolean(opts.deep), includeFilesystem: true, includeChannelSecurity: true }); if (opts.json) { defaultRuntime.log(JSON.stringify(fixResult ? { fix: fixResult, report } : report, null, 2)); return; } const rich = isRich(); const heading = (text) => rich ? theme.heading(text) : text; const muted = (text) => rich ? theme.muted(text) : text; const lines = []; lines.push(heading("OpenClaw security audit")); lines.push(muted(`Summary: ${formatSummary(report.summary)}`)); lines.push(muted(`Run deeper: ${formatCliCommand("openclaw security audit --deep")}`)); if (opts.fix) { lines.push(muted(`Fix: ${formatCliCommand("openclaw security audit --fix")}`)); if (!fixResult) lines.push(muted("Fixes: failed to apply (unexpected error)")); else if (fixResult.errors.length === 0 && fixResult.changes.length === 0 && fixResult.actions.every((a) => !a.ok)) lines.push(muted("Fixes: no changes applied")); else { lines.push(""); lines.push(heading("FIX")); for (const change of fixResult.changes) lines.push(muted(` ${shortenHomeInString(change)}`)); for (const action of fixResult.actions) { if (action.kind === "chmod") { const mode = action.mode.toString(8).padStart(3, "0"); if (action.ok) lines.push(muted(` chmod ${mode} ${shortenHomePath(action.path)}`)); else if (action.skipped) lines.push(muted(` skip chmod ${mode} ${shortenHomePath(action.path)} (${action.skipped})`)); else if (action.error) lines.push(muted(` chmod ${mode} ${shortenHomePath(action.path)} failed: ${action.error}`)); continue; } const command = shortenHomeInString(action.command); if (action.ok) lines.push(muted(` ${command}`)); else if (action.skipped) lines.push(muted(` skip ${command} (${action.skipped})`)); else if (action.error) lines.push(muted(` ${command} failed: ${action.error}`)); } if (fixResult.errors.length > 0) for (const err of fixResult.errors) lines.push(muted(` error: ${shortenHomeInString(err)}`)); } } const bySeverity = (sev) => report.findings.filter((f) => f.severity === sev); const render = (sev) => { const list = bySeverity(sev); if (list.length === 0) return; const label = sev === "critical" ? rich ? theme.error("CRITICAL") : "CRITICAL" : sev === "warn" ? rich ? theme.warn("WARN") : "WARN" : rich ? theme.muted("INFO") : "INFO"; lines.push(""); lines.push(heading(label)); for (const f of list) { lines.push(`${theme.muted(f.checkId)} ${f.title}`); lines.push(` ${f.detail}`); if (f.remediation?.trim()) lines.push(` ${muted(`Fix: ${f.remediation.trim()}`)}`); } }; render("critical"); render("warn"); render("info"); defaultRuntime.log(lines.join("\n")); }); } //#endregion export { registerSecurityCli };