mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 21:38:25 +00:00
chore: Enable "curly" rule to avoid single-statement if confusion/errors.
This commit is contained in:
@@ -73,9 +73,15 @@ function hashExecApprovalsRaw(raw: string | null): string {
|
||||
}
|
||||
|
||||
function expandHome(value: string): string {
|
||||
if (!value) return value;
|
||||
if (value === "~") return os.homedir();
|
||||
if (value.startsWith("~/")) return path.join(os.homedir(), value.slice(2));
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
if (value === "~") {
|
||||
return os.homedir();
|
||||
}
|
||||
if (value.startsWith("~/")) {
|
||||
return path.join(os.homedir(), value.slice(2));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -100,12 +106,18 @@ function mergeLegacyAgent(
|
||||
const seen = new Set<string>();
|
||||
const pushEntry = (entry: ExecAllowlistEntry) => {
|
||||
const key = normalizeAllowlistPattern(entry.pattern);
|
||||
if (!key || seen.has(key)) return;
|
||||
if (!key || seen.has(key)) {
|
||||
return;
|
||||
}
|
||||
seen.add(key);
|
||||
allowlist.push(entry);
|
||||
};
|
||||
for (const entry of current.allowlist ?? []) pushEntry(entry);
|
||||
for (const entry of legacy.allowlist ?? []) pushEntry(entry);
|
||||
for (const entry of current.allowlist ?? []) {
|
||||
pushEntry(entry);
|
||||
}
|
||||
for (const entry of legacy.allowlist ?? []) {
|
||||
pushEntry(entry);
|
||||
}
|
||||
|
||||
return {
|
||||
security: current.security ?? legacy.security,
|
||||
@@ -124,10 +136,14 @@ function ensureDir(filePath: string) {
|
||||
function ensureAllowlistIds(
|
||||
allowlist: ExecAllowlistEntry[] | undefined,
|
||||
): ExecAllowlistEntry[] | undefined {
|
||||
if (!Array.isArray(allowlist) || allowlist.length === 0) return allowlist;
|
||||
if (!Array.isArray(allowlist) || allowlist.length === 0) {
|
||||
return allowlist;
|
||||
}
|
||||
let changed = false;
|
||||
const next = allowlist.map((entry) => {
|
||||
if (entry.id) return entry;
|
||||
if (entry.id) {
|
||||
return entry;
|
||||
}
|
||||
changed = true;
|
||||
return { ...entry, id: crypto.randomUUID() };
|
||||
});
|
||||
@@ -248,12 +264,16 @@ export function ensureExecApprovals(): ExecApprovalsFile {
|
||||
}
|
||||
|
||||
function normalizeSecurity(value: ExecSecurity | undefined, fallback: ExecSecurity): ExecSecurity {
|
||||
if (value === "allowlist" || value === "full" || value === "deny") return value;
|
||||
if (value === "allowlist" || value === "full" || value === "deny") {
|
||||
return value;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function normalizeAsk(value: ExecAsk | undefined, fallback: ExecAsk): ExecAsk {
|
||||
if (value === "always" || value === "off" || value === "on-miss") return value;
|
||||
if (value === "always" || value === "off" || value === "on-miss") {
|
||||
return value;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
@@ -345,7 +365,9 @@ type CommandResolution = {
|
||||
function isExecutableFile(filePath: string): boolean {
|
||||
try {
|
||||
const stat = fs.statSync(filePath);
|
||||
if (!stat.isFile()) return false;
|
||||
if (!stat.isFile()) {
|
||||
return false;
|
||||
}
|
||||
if (process.platform !== "win32") {
|
||||
fs.accessSync(filePath, fs.constants.X_OK);
|
||||
}
|
||||
@@ -357,11 +379,15 @@ function isExecutableFile(filePath: string): boolean {
|
||||
|
||||
function parseFirstToken(command: string): string | null {
|
||||
const trimmed = command.trim();
|
||||
if (!trimmed) return null;
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
const first = trimmed[0];
|
||||
if (first === '"' || first === "'") {
|
||||
const end = trimmed.indexOf(first, 1);
|
||||
if (end > 1) return trimmed.slice(1, end);
|
||||
if (end > 1) {
|
||||
return trimmed.slice(1, end);
|
||||
}
|
||||
return trimmed.slice(1);
|
||||
}
|
||||
const match = /^[^\s]+/.exec(trimmed);
|
||||
@@ -398,7 +424,9 @@ function resolveExecutablePath(rawExecutable: string, cwd?: string, env?: NodeJS
|
||||
for (const entry of entries) {
|
||||
for (const ext of extensions) {
|
||||
const candidate = path.join(entry, expanded + ext);
|
||||
if (isExecutableFile(candidate)) return candidate;
|
||||
if (isExecutableFile(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
@@ -410,7 +438,9 @@ export function resolveCommandResolution(
|
||||
env?: NodeJS.ProcessEnv,
|
||||
): CommandResolution | null {
|
||||
const rawExecutable = parseFirstToken(command);
|
||||
if (!rawExecutable) return null;
|
||||
if (!rawExecutable) {
|
||||
return null;
|
||||
}
|
||||
const resolvedPath = resolveExecutablePath(rawExecutable, cwd, env);
|
||||
const executableName = resolvedPath ? path.basename(resolvedPath) : rawExecutable;
|
||||
return { rawExecutable, resolvedPath, executableName };
|
||||
@@ -422,7 +452,9 @@ export function resolveCommandResolutionFromArgv(
|
||||
env?: NodeJS.ProcessEnv,
|
||||
): CommandResolution | null {
|
||||
const rawExecutable = argv[0]?.trim();
|
||||
if (!rawExecutable) return null;
|
||||
if (!rawExecutable) {
|
||||
return null;
|
||||
}
|
||||
const resolvedPath = resolveExecutablePath(rawExecutable, cwd, env);
|
||||
const executableName = resolvedPath ? path.basename(resolvedPath) : rawExecutable;
|
||||
return { rawExecutable, resolvedPath, executableName };
|
||||
@@ -474,7 +506,9 @@ function globToRegExp(pattern: string): RegExp {
|
||||
|
||||
function matchesPattern(pattern: string, target: string): boolean {
|
||||
const trimmed = pattern.trim();
|
||||
if (!trimmed) return false;
|
||||
if (!trimmed) {
|
||||
return false;
|
||||
}
|
||||
const expanded = trimmed.startsWith("~") ? expandHome(trimmed) : trimmed;
|
||||
const hasWildcard = /[*?]/.test(expanded);
|
||||
let normalizedPattern = expanded;
|
||||
@@ -493,13 +527,23 @@ function resolveAllowlistCandidatePath(
|
||||
resolution: CommandResolution | null,
|
||||
cwd?: string,
|
||||
): string | undefined {
|
||||
if (!resolution) return undefined;
|
||||
if (resolution.resolvedPath) return resolution.resolvedPath;
|
||||
if (!resolution) {
|
||||
return undefined;
|
||||
}
|
||||
if (resolution.resolvedPath) {
|
||||
return resolution.resolvedPath;
|
||||
}
|
||||
const raw = resolution.rawExecutable?.trim();
|
||||
if (!raw) return undefined;
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
const expanded = raw.startsWith("~") ? expandHome(raw) : raw;
|
||||
if (!expanded.includes("/") && !expanded.includes("\\")) return undefined;
|
||||
if (path.isAbsolute(expanded)) return expanded;
|
||||
if (!expanded.includes("/") && !expanded.includes("\\")) {
|
||||
return undefined;
|
||||
}
|
||||
if (path.isAbsolute(expanded)) {
|
||||
return expanded;
|
||||
}
|
||||
const base = cwd && cwd.trim() ? cwd.trim() : process.cwd();
|
||||
return path.resolve(base, expanded);
|
||||
}
|
||||
@@ -508,14 +552,22 @@ export function matchAllowlist(
|
||||
entries: ExecAllowlistEntry[],
|
||||
resolution: CommandResolution | null,
|
||||
): ExecAllowlistEntry | null {
|
||||
if (!entries.length || !resolution?.resolvedPath) return null;
|
||||
if (!entries.length || !resolution?.resolvedPath) {
|
||||
return null;
|
||||
}
|
||||
const resolvedPath = resolution.resolvedPath;
|
||||
for (const entry of entries) {
|
||||
const pattern = entry.pattern?.trim();
|
||||
if (!pattern) continue;
|
||||
if (!pattern) {
|
||||
continue;
|
||||
}
|
||||
const hasPath = pattern.includes("/") || pattern.includes("\\") || pattern.includes("~");
|
||||
if (!hasPath) continue;
|
||||
if (matchesPattern(pattern, resolvedPath)) return entry;
|
||||
if (!hasPath) {
|
||||
continue;
|
||||
}
|
||||
if (matchesPattern(pattern, resolvedPath)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -579,12 +631,16 @@ function iterateQuoteAware(
|
||||
continue;
|
||||
}
|
||||
if (inSingle) {
|
||||
if (ch === "'") inSingle = false;
|
||||
if (ch === "'") {
|
||||
inSingle = false;
|
||||
}
|
||||
buf += ch;
|
||||
continue;
|
||||
}
|
||||
if (inDouble) {
|
||||
if (ch === '"') inDouble = false;
|
||||
if (ch === '"') {
|
||||
inDouble = false;
|
||||
}
|
||||
buf += ch;
|
||||
continue;
|
||||
}
|
||||
@@ -805,10 +861,18 @@ export function analyzeArgvCommand(params: {
|
||||
|
||||
function isPathLikeToken(value: string): boolean {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return false;
|
||||
if (trimmed === "-") return false;
|
||||
if (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("~")) return true;
|
||||
if (trimmed.startsWith("/")) return true;
|
||||
if (!trimmed) {
|
||||
return false;
|
||||
}
|
||||
if (trimmed === "-") {
|
||||
return false;
|
||||
}
|
||||
if (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("~")) {
|
||||
return true;
|
||||
}
|
||||
if (trimmed.startsWith("/")) {
|
||||
return true;
|
||||
}
|
||||
return /^[A-Za-z]:[\\/]/.test(trimmed);
|
||||
}
|
||||
|
||||
@@ -821,7 +885,9 @@ function defaultFileExists(filePath: string): boolean {
|
||||
}
|
||||
|
||||
export function normalizeSafeBins(entries?: string[]): Set<string> {
|
||||
if (!Array.isArray(entries)) return new Set();
|
||||
if (!Array.isArray(entries)) {
|
||||
return new Set();
|
||||
}
|
||||
const normalized = entries
|
||||
.map((entry) => entry.trim().toLowerCase())
|
||||
.filter((entry) => entry.length > 0);
|
||||
@@ -829,7 +895,9 @@ export function normalizeSafeBins(entries?: string[]): Set<string> {
|
||||
}
|
||||
|
||||
export function resolveSafeBins(entries?: string[] | null): Set<string> {
|
||||
if (entries === undefined) return normalizeSafeBins(DEFAULT_SAFE_BINS);
|
||||
if (entries === undefined) {
|
||||
return normalizeSafeBins(DEFAULT_SAFE_BINS);
|
||||
}
|
||||
return normalizeSafeBins(entries ?? []);
|
||||
}
|
||||
|
||||
@@ -840,22 +908,34 @@ export function isSafeBinUsage(params: {
|
||||
cwd?: string;
|
||||
fileExists?: (filePath: string) => boolean;
|
||||
}): boolean {
|
||||
if (params.safeBins.size === 0) return false;
|
||||
if (params.safeBins.size === 0) {
|
||||
return false;
|
||||
}
|
||||
const resolution = params.resolution;
|
||||
const execName = resolution?.executableName?.toLowerCase();
|
||||
if (!execName) return false;
|
||||
if (!execName) {
|
||||
return false;
|
||||
}
|
||||
const matchesSafeBin =
|
||||
params.safeBins.has(execName) ||
|
||||
(process.platform === "win32" && params.safeBins.has(path.parse(execName).name));
|
||||
if (!matchesSafeBin) return false;
|
||||
if (!resolution?.resolvedPath) return false;
|
||||
if (!matchesSafeBin) {
|
||||
return false;
|
||||
}
|
||||
if (!resolution?.resolvedPath) {
|
||||
return false;
|
||||
}
|
||||
const cwd = params.cwd ?? process.cwd();
|
||||
const exists = params.fileExists ?? defaultFileExists;
|
||||
const argv = params.argv.slice(1);
|
||||
for (let i = 0; i < argv.length; i += 1) {
|
||||
const token = argv[i];
|
||||
if (!token) continue;
|
||||
if (token === "-") continue;
|
||||
if (!token) {
|
||||
continue;
|
||||
}
|
||||
if (token === "-") {
|
||||
continue;
|
||||
}
|
||||
if (token.startsWith("-")) {
|
||||
const eqIndex = token.indexOf("=");
|
||||
if (eqIndex > 0) {
|
||||
@@ -866,8 +946,12 @@ export function isSafeBinUsage(params: {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (isPathLikeToken(token)) return false;
|
||||
if (exists(path.resolve(cwd, token))) return false;
|
||||
if (isPathLikeToken(token)) {
|
||||
return false;
|
||||
}
|
||||
if (exists(path.resolve(cwd, token))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -897,7 +981,9 @@ function evaluateSegments(
|
||||
? { ...segment.resolution, resolvedPath: candidatePath }
|
||||
: segment.resolution;
|
||||
const match = matchAllowlist(params.allowlist, candidateResolution);
|
||||
if (match) matches.push(match);
|
||||
if (match) {
|
||||
matches.push(match);
|
||||
}
|
||||
const safe = isSafeBinUsage({
|
||||
argv: segment.argv,
|
||||
resolution: segment.resolution,
|
||||
@@ -993,12 +1079,16 @@ function splitCommandChain(command: string): string[] | null {
|
||||
continue;
|
||||
}
|
||||
if (inSingle) {
|
||||
if (ch === "'") inSingle = false;
|
||||
if (ch === "'") {
|
||||
inSingle = false;
|
||||
}
|
||||
buf += ch;
|
||||
continue;
|
||||
}
|
||||
if (inDouble) {
|
||||
if (ch === '"') inDouble = false;
|
||||
if (ch === '"') {
|
||||
inDouble = false;
|
||||
}
|
||||
buf += ch;
|
||||
continue;
|
||||
}
|
||||
@@ -1014,19 +1104,25 @@ function splitCommandChain(command: string): string[] | null {
|
||||
}
|
||||
|
||||
if (ch === "&" && command[i + 1] === "&") {
|
||||
if (!pushPart()) invalidChain = true;
|
||||
if (!pushPart()) {
|
||||
invalidChain = true;
|
||||
}
|
||||
i += 1;
|
||||
foundChain = true;
|
||||
continue;
|
||||
}
|
||||
if (ch === "|" && command[i + 1] === "|") {
|
||||
if (!pushPart()) invalidChain = true;
|
||||
if (!pushPart()) {
|
||||
invalidChain = true;
|
||||
}
|
||||
i += 1;
|
||||
foundChain = true;
|
||||
continue;
|
||||
}
|
||||
if (ch === ";") {
|
||||
if (!pushPart()) invalidChain = true;
|
||||
if (!pushPart()) {
|
||||
invalidChain = true;
|
||||
}
|
||||
foundChain = true;
|
||||
continue;
|
||||
}
|
||||
@@ -1035,8 +1131,12 @@ function splitCommandChain(command: string): string[] | null {
|
||||
}
|
||||
|
||||
const pushedFinal = pushPart();
|
||||
if (!foundChain) return null;
|
||||
if (invalidChain || !pushedFinal) return null;
|
||||
if (!foundChain) {
|
||||
return null;
|
||||
}
|
||||
if (invalidChain || !pushedFinal) {
|
||||
return null;
|
||||
}
|
||||
return parts.length > 0 ? parts : null;
|
||||
}
|
||||
|
||||
@@ -1187,8 +1287,12 @@ export function addAllowlistEntry(
|
||||
const existing = agents[target] ?? {};
|
||||
const allowlist = Array.isArray(existing.allowlist) ? existing.allowlist : [];
|
||||
const trimmed = pattern.trim();
|
||||
if (!trimmed) return;
|
||||
if (allowlist.some((entry) => entry.pattern === trimmed)) return;
|
||||
if (!trimmed) {
|
||||
return;
|
||||
}
|
||||
if (allowlist.some((entry) => entry.pattern === trimmed)) {
|
||||
return;
|
||||
}
|
||||
allowlist.push({ id: crypto.randomUUID(), pattern: trimmed, lastUsedAt: Date.now() });
|
||||
agents[target] = { ...existing, allowlist };
|
||||
approvals.agents = agents;
|
||||
@@ -1214,14 +1318,18 @@ export async function requestExecApprovalViaSocket(params: {
|
||||
timeoutMs?: number;
|
||||
}): Promise<ExecApprovalDecision | null> {
|
||||
const { socketPath, token, request } = params;
|
||||
if (!socketPath || !token) return null;
|
||||
if (!socketPath || !token) {
|
||||
return null;
|
||||
}
|
||||
const timeoutMs = params.timeoutMs ?? 15_000;
|
||||
return await new Promise((resolve) => {
|
||||
const client = new net.Socket();
|
||||
let settled = false;
|
||||
let buffer = "";
|
||||
const finish = (value: ExecApprovalDecision | null) => {
|
||||
if (settled) return;
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
try {
|
||||
client.destroy();
|
||||
@@ -1250,7 +1358,9 @@ export async function requestExecApprovalViaSocket(params: {
|
||||
const line = buffer.slice(0, idx).trim();
|
||||
buffer = buffer.slice(idx + 1);
|
||||
idx = buffer.indexOf("\n");
|
||||
if (!line) continue;
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const msg = JSON.parse(line) as { type?: string; decision?: ExecApprovalDecision };
|
||||
if (msg?.type === "decision" && msg.decision) {
|
||||
|
||||
Reference in New Issue
Block a user