mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 22:21:23 +00:00
fix: use Windows ACLs for security audit
This commit is contained in:
@@ -22,14 +22,12 @@ import type { SandboxToolPolicy } from "../agents/sandbox/types.js";
|
||||
import { INCLUDE_KEY, MAX_INCLUDE_DEPTH } from "../config/includes.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import {
|
||||
formatOctal,
|
||||
isGroupReadable,
|
||||
isGroupWritable,
|
||||
isWorldReadable,
|
||||
isWorldWritable,
|
||||
modeBits,
|
||||
formatPermissionDetail,
|
||||
formatPermissionRemediation,
|
||||
inspectPathPermissions,
|
||||
safeStat,
|
||||
} from "./audit-fs.js";
|
||||
import type { ExecFn } from "./windows-acl.js";
|
||||
|
||||
export type SecurityAuditFinding = {
|
||||
checkId: string;
|
||||
@@ -707,6 +705,9 @@ async function collectIncludePathsRecursive(params: {
|
||||
|
||||
export async function collectIncludeFilePermFindings(params: {
|
||||
configSnapshot: ConfigFileSnapshot;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
platform?: NodeJS.Platform;
|
||||
execIcacls?: ExecFn;
|
||||
}): Promise<SecurityAuditFinding[]> {
|
||||
const findings: SecurityAuditFinding[] = [];
|
||||
if (!params.configSnapshot.exists) return findings;
|
||||
@@ -720,32 +721,53 @@ export async function collectIncludeFilePermFindings(params: {
|
||||
|
||||
for (const p of includePaths) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const st = await safeStat(p);
|
||||
if (!st.ok) continue;
|
||||
const bits = modeBits(st.mode);
|
||||
if (isWorldWritable(bits) || isGroupWritable(bits)) {
|
||||
const perms = await inspectPathPermissions(p, {
|
||||
env: params.env,
|
||||
platform: params.platform,
|
||||
exec: params.execIcacls,
|
||||
});
|
||||
if (!perms.ok) continue;
|
||||
if (perms.worldWritable || perms.groupWritable) {
|
||||
findings.push({
|
||||
checkId: "fs.config_include.perms_writable",
|
||||
severity: "critical",
|
||||
title: "Config include file is writable by others",
|
||||
detail: `${p} mode=${formatOctal(bits)}; another user could influence your effective config.`,
|
||||
remediation: `chmod 600 ${p}`,
|
||||
detail: `${formatPermissionDetail(p, perms)}; another user could influence your effective config.`,
|
||||
remediation: formatPermissionRemediation({
|
||||
targetPath: p,
|
||||
perms,
|
||||
isDir: false,
|
||||
posixMode: 0o600,
|
||||
env: params.env,
|
||||
}),
|
||||
});
|
||||
} else if (isWorldReadable(bits)) {
|
||||
} else if (perms.worldReadable) {
|
||||
findings.push({
|
||||
checkId: "fs.config_include.perms_world_readable",
|
||||
severity: "critical",
|
||||
title: "Config include file is world-readable",
|
||||
detail: `${p} mode=${formatOctal(bits)}; include files can contain tokens and private settings.`,
|
||||
remediation: `chmod 600 ${p}`,
|
||||
detail: `${formatPermissionDetail(p, perms)}; include files can contain tokens and private settings.`,
|
||||
remediation: formatPermissionRemediation({
|
||||
targetPath: p,
|
||||
perms,
|
||||
isDir: false,
|
||||
posixMode: 0o600,
|
||||
env: params.env,
|
||||
}),
|
||||
});
|
||||
} else if (isGroupReadable(bits)) {
|
||||
} else if (perms.groupReadable) {
|
||||
findings.push({
|
||||
checkId: "fs.config_include.perms_group_readable",
|
||||
severity: "warn",
|
||||
title: "Config include file is group-readable",
|
||||
detail: `${p} mode=${formatOctal(bits)}; include files can contain tokens and private settings.`,
|
||||
remediation: `chmod 600 ${p}`,
|
||||
detail: `${formatPermissionDetail(p, perms)}; include files can contain tokens and private settings.`,
|
||||
remediation: formatPermissionRemediation({
|
||||
targetPath: p,
|
||||
perms,
|
||||
isDir: false,
|
||||
posixMode: 0o600,
|
||||
env: params.env,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -757,28 +779,45 @@ export async function collectStateDeepFilesystemFindings(params: {
|
||||
cfg: ClawdbotConfig;
|
||||
env: NodeJS.ProcessEnv;
|
||||
stateDir: string;
|
||||
platform?: NodeJS.Platform;
|
||||
execIcacls?: ExecFn;
|
||||
}): Promise<SecurityAuditFinding[]> {
|
||||
const findings: SecurityAuditFinding[] = [];
|
||||
const oauthDir = resolveOAuthDir(params.env, params.stateDir);
|
||||
|
||||
const oauthStat = await safeStat(oauthDir);
|
||||
if (oauthStat.ok && oauthStat.isDir) {
|
||||
const bits = modeBits(oauthStat.mode);
|
||||
if (isWorldWritable(bits) || isGroupWritable(bits)) {
|
||||
const oauthPerms = await inspectPathPermissions(oauthDir, {
|
||||
env: params.env,
|
||||
platform: params.platform,
|
||||
exec: params.execIcacls,
|
||||
});
|
||||
if (oauthPerms.ok && oauthPerms.isDir) {
|
||||
if (oauthPerms.worldWritable || oauthPerms.groupWritable) {
|
||||
findings.push({
|
||||
checkId: "fs.credentials_dir.perms_writable",
|
||||
severity: "critical",
|
||||
title: "Credentials dir is writable by others",
|
||||
detail: `${oauthDir} mode=${formatOctal(bits)}; another user could drop/modify credential files.`,
|
||||
remediation: `chmod 700 ${oauthDir}`,
|
||||
detail: `${formatPermissionDetail(oauthDir, oauthPerms)}; another user could drop/modify credential files.`,
|
||||
remediation: formatPermissionRemediation({
|
||||
targetPath: oauthDir,
|
||||
perms: oauthPerms,
|
||||
isDir: true,
|
||||
posixMode: 0o700,
|
||||
env: params.env,
|
||||
}),
|
||||
});
|
||||
} else if (isGroupReadable(bits) || isWorldReadable(bits)) {
|
||||
} else if (oauthPerms.groupReadable || oauthPerms.worldReadable) {
|
||||
findings.push({
|
||||
checkId: "fs.credentials_dir.perms_readable",
|
||||
severity: "warn",
|
||||
title: "Credentials dir is readable by others",
|
||||
detail: `${oauthDir} mode=${formatOctal(bits)}; credentials and allowlists can be sensitive.`,
|
||||
remediation: `chmod 700 ${oauthDir}`,
|
||||
detail: `${formatPermissionDetail(oauthDir, oauthPerms)}; credentials and allowlists can be sensitive.`,
|
||||
remediation: formatPermissionRemediation({
|
||||
targetPath: oauthDir,
|
||||
perms: oauthPerms,
|
||||
isDir: true,
|
||||
posixMode: 0o700,
|
||||
env: params.env,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -795,40 +834,64 @@ export async function collectStateDeepFilesystemFindings(params: {
|
||||
const agentDir = path.join(params.stateDir, "agents", agentId, "agent");
|
||||
const authPath = path.join(agentDir, "auth-profiles.json");
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const authStat = await safeStat(authPath);
|
||||
if (authStat.ok) {
|
||||
const bits = modeBits(authStat.mode);
|
||||
if (isWorldWritable(bits) || isGroupWritable(bits)) {
|
||||
const authPerms = await inspectPathPermissions(authPath, {
|
||||
env: params.env,
|
||||
platform: params.platform,
|
||||
exec: params.execIcacls,
|
||||
});
|
||||
if (authPerms.ok) {
|
||||
if (authPerms.worldWritable || authPerms.groupWritable) {
|
||||
findings.push({
|
||||
checkId: "fs.auth_profiles.perms_writable",
|
||||
severity: "critical",
|
||||
title: "auth-profiles.json is writable by others",
|
||||
detail: `${authPath} mode=${formatOctal(bits)}; another user could inject credentials.`,
|
||||
remediation: `chmod 600 ${authPath}`,
|
||||
detail: `${formatPermissionDetail(authPath, authPerms)}; another user could inject credentials.`,
|
||||
remediation: formatPermissionRemediation({
|
||||
targetPath: authPath,
|
||||
perms: authPerms,
|
||||
isDir: false,
|
||||
posixMode: 0o600,
|
||||
env: params.env,
|
||||
}),
|
||||
});
|
||||
} else if (isWorldReadable(bits) || isGroupReadable(bits)) {
|
||||
} else if (authPerms.worldReadable || authPerms.groupReadable) {
|
||||
findings.push({
|
||||
checkId: "fs.auth_profiles.perms_readable",
|
||||
severity: "warn",
|
||||
title: "auth-profiles.json is readable by others",
|
||||
detail: `${authPath} mode=${formatOctal(bits)}; auth-profiles.json contains API keys and OAuth tokens.`,
|
||||
remediation: `chmod 600 ${authPath}`,
|
||||
detail: `${formatPermissionDetail(authPath, authPerms)}; auth-profiles.json contains API keys and OAuth tokens.`,
|
||||
remediation: formatPermissionRemediation({
|
||||
targetPath: authPath,
|
||||
perms: authPerms,
|
||||
isDir: false,
|
||||
posixMode: 0o600,
|
||||
env: params.env,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const storePath = path.join(params.stateDir, "agents", agentId, "sessions", "sessions.json");
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const storeStat = await safeStat(storePath);
|
||||
if (storeStat.ok) {
|
||||
const bits = modeBits(storeStat.mode);
|
||||
if (isWorldReadable(bits) || isGroupReadable(bits)) {
|
||||
const storePerms = await inspectPathPermissions(storePath, {
|
||||
env: params.env,
|
||||
platform: params.platform,
|
||||
exec: params.execIcacls,
|
||||
});
|
||||
if (storePerms.ok) {
|
||||
if (storePerms.worldReadable || storePerms.groupReadable) {
|
||||
findings.push({
|
||||
checkId: "fs.sessions_store.perms_readable",
|
||||
severity: "warn",
|
||||
title: "sessions.json is readable by others",
|
||||
detail: `${storePath} mode=${formatOctal(bits)}; routing and transcript metadata can be sensitive.`,
|
||||
remediation: `chmod 600 ${storePath}`,
|
||||
detail: `${formatPermissionDetail(storePath, storePerms)}; routing and transcript metadata can be sensitive.`,
|
||||
remediation: formatPermissionRemediation({
|
||||
targetPath: storePath,
|
||||
perms: storePerms,
|
||||
isDir: false,
|
||||
posixMode: 0o600,
|
||||
env: params.env,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -840,16 +903,25 @@ export async function collectStateDeepFilesystemFindings(params: {
|
||||
const expanded = logFile.startsWith("~") ? expandTilde(logFile, params.env) : logFile;
|
||||
if (expanded) {
|
||||
const logPath = path.resolve(expanded);
|
||||
const st = await safeStat(logPath);
|
||||
if (st.ok) {
|
||||
const bits = modeBits(st.mode);
|
||||
if (isWorldReadable(bits) || isGroupReadable(bits)) {
|
||||
const logPerms = await inspectPathPermissions(logPath, {
|
||||
env: params.env,
|
||||
platform: params.platform,
|
||||
exec: params.execIcacls,
|
||||
});
|
||||
if (logPerms.ok) {
|
||||
if (logPerms.worldReadable || logPerms.groupReadable) {
|
||||
findings.push({
|
||||
checkId: "fs.log_file.perms_readable",
|
||||
severity: "warn",
|
||||
title: "Log file is readable by others",
|
||||
detail: `${logPath} mode=${formatOctal(bits)}; logs can contain private messages and tool output.`,
|
||||
remediation: `chmod 600 ${logPath}`,
|
||||
detail: `${formatPermissionDetail(logPath, logPerms)}; logs can contain private messages and tool output.`,
|
||||
remediation: formatPermissionRemediation({
|
||||
targetPath: logPath,
|
||||
perms: logPerms,
|
||||
isDir: false,
|
||||
posixMode: 0o600,
|
||||
env: params.env,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user