@gguf/claw
Version:
Multi-channel AI gateway with extensible messaging integrations
493 lines (490 loc) • 16.1 kB
JavaScript
import { Dt as theme, Et as isRich, Yt as resolveStateDir, _ as defaultRuntime, ct as shortenHomeInString, lt as shortenHomePath, qt as resolveOAuthDir, zt as resolveConfigPath } from "./entry.js";
import "./auth-profiles-DFa1zzNy.js";
import { t as formatCliCommand } from "./command-format-D3syQOZg.js";
import { l as normalizeAgentId } from "./session-key-BGiG_JcT.js";
import { n as runExec } from "./exec-CBKBIMpA.js";
import { l as resolveDefaultAgentId } from "./agent-scope-RzK9Zcks.js";
import "./github-copilot-token-DuFIqfeC.js";
import "./frontmatter-D-YR-Ghi.js";
import "./skills-DJmGZazd.js";
import "./manifest-registry-DS2iK5AZ.js";
import { i as loadConfig, r as createConfigIO } from "./config-B2kL1ciP.js";
import "./client-CDjZdZtI.js";
import "./call-DXhJGwEy.js";
import "./message-channel-CVHJDItx.js";
import "./pairing-token-Byh6drgn.js";
import "./sessions-BD5dyLxb.js";
import "./normalize-Db7Xtx2v.js";
import "./accounts-BpzwDfBB.js";
import "./bindings-KqaGKS1E.js";
import "./logging-CFvkxgcX.js";
import "./plugins-RqhjLCb6.js";
import "./accounts-B3D4iWUP.js";
import "./image-ops-lDlFpoR2.js";
import "./sandbox-CO-R8v6J.js";
import "./common-DcSh1hZE.js";
import "./chrome-52ZF7gmE.js";
import "./tailscale-BzRVNhMW.js";
import "./auth-Dq2pFnjj.js";
import "./server-context-C1f7cijA.js";
import "./routes-BJmh1Ify.js";
import "./redact-C2s6sr73.js";
import "./errors-CFEPAPWc.js";
import "./fs-safe-3YwsxSr5.js";
import "./paths-CXKNzNjU.js";
import "./ssrf-Cv2DiHsm.js";
import "./store-e6fIrP17.js";
import "./ports-59j-bA53.js";
import "./trash-DVLJ0k6q.js";
import "./dock-BZuwgj1O.js";
import "./accounts-Jg3_1Y-r.js";
import "./paths-CXpciDEv.js";
import "./workspace-dirs-BXftTDSV.js";
import { i as readChannelAllowFromStore } from "./pairing-store-CruPwgBw.js";
import "./pi-tools.policy-DeRwGLEO.js";
import { t as formatDocsLink } from "./links-CW8Bx7rK.js";
import { t as formatHelpExamples } from "./help-format-B0pWGnZs.js";
import "./dangerous-tools-CKw5lwBh.js";
import "./skill-scanner-BlmeBzA8.js";
import { i as collectIncludePathsRecursive, n as createIcaclsResetCommand, r as formatIcaclsResetCommand, t as runSecurityAudit } from "./audit-CY7Dg3f7.js";
import "./dm-policy-shared-BaTHScul.js";
import path from "node:path";
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
};
}
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"
}));
const sessionEntries = await fs.readdir(sessionsDir, { withFileTypes: true }).catch(() => []);
for (const entry of sessionEntries) {
if (!entry.isFile()) continue;
if (!entry.name.endsWith(".jsonl")) continue;
const p = path.join(sessionsDir, entry.name);
params.actions.push(await params.applyPerms({
path: p,
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("Audit local config and state for common security foot-guns").addHelpText("after", () => `\n${theme.heading("Examples:")}\n${formatHelpExamples([
["openclaw security audit", "Run a local security audit."],
["openclaw security audit --deep", "Include best-effort live Gateway probe checks."],
["openclaw security audit --fix", "Apply safe remediations and file-permission fixes."],
["openclaw security audit --json", "Output machine-readable JSON."]
])}\n\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 };