chore: Enable "curly" rule to avoid single-statement if confusion/errors.

This commit is contained in:
cpojer
2026-01-31 16:19:20 +09:00
parent 009b16fab8
commit 5ceff756e1
1266 changed files with 27871 additions and 9393 deletions

View File

@@ -27,7 +27,9 @@ async function waitForSandboxCdp(params: { cdpPort: number; timeoutMs: number })
const t = setTimeout(() => ctrl.abort(), 1000);
try {
const res = await fetch(url, { signal: ctrl.signal });
if (res.ok) return true;
if (res.ok) {
return true;
}
} finally {
clearTimeout(t);
}
@@ -74,7 +76,9 @@ async function ensureSandboxBrowserImage(image: string) {
const result = await execDocker(["image", "inspect", image], {
allowFailure: true,
});
if (result.code === 0) return;
if (result.code === 0) {
return;
}
throw new Error(
`Sandbox browser image not found: ${image}. Build it with scripts/sandbox-browser-setup.sh.`,
);
@@ -87,8 +91,12 @@ export async function ensureSandboxBrowser(params: {
cfg: SandboxConfig;
evaluateEnabled?: boolean;
}): Promise<SandboxBrowserContext | null> {
if (!params.cfg.browser.enabled) return null;
if (!isToolAllowed(params.cfg.tools, "browser")) return null;
if (!params.cfg.browser.enabled) {
return null;
}
if (!isToolAllowed(params.cfg.tools, "browser")) {
return null;
}
const slug = params.cfg.scope === "shared" ? "shared" : slugifySessionKey(params.scopeKey);
const name = `${params.cfg.browser.containerPrefix}${slug}`;
@@ -152,12 +160,16 @@ export async function ensureSandboxBrowser(params: {
}
const bridge = (() => {
if (shouldReuse && existing) return existing.bridge;
if (shouldReuse && existing) {
return existing.bridge;
}
return null;
})();
const ensureBridge = async () => {
if (bridge) return bridge;
if (bridge) {
return bridge;
}
const onEnsureAttachTarget = params.cfg.browser.autoStart
? async () => {

View File

@@ -13,7 +13,9 @@ function isPrimitive(value: unknown): value is string | number | boolean | bigin
return value === null || (typeof value !== "object" && typeof value !== "function");
}
function normalizeForHash(value: unknown): unknown {
if (value === undefined) return undefined;
if (value === undefined) {
return undefined;
}
if (Array.isArray(value)) {
const normalized = value
.map(normalizeForHash)
@@ -31,7 +33,9 @@ function normalizeForHash(value: unknown): unknown {
const normalized: Record<string, unknown> = {};
for (const [key, entryValue] of entries) {
const next = normalizeForHash(entryValue);
if (next !== undefined) normalized[key] = next;
if (next !== undefined) {
normalized[key] = next;
}
}
return normalized;
}
@@ -39,10 +43,18 @@ function normalizeForHash(value: unknown): unknown {
}
function primitiveToString(value: unknown): string {
if (value === null) return "null";
if (typeof value === "string") return value;
if (typeof value === "number") return String(value);
if (typeof value === "boolean") return value ? "true" : "false";
if (value === null) {
return "null";
}
if (typeof value === "string") {
return value;
}
if (typeof value === "number") {
return String(value);
}
if (typeof value === "boolean") {
return value ? "true" : "false";
}
return JSON.stringify(value);
}

View File

@@ -27,7 +27,9 @@ export function resolveSandboxScope(params: {
scope?: SandboxScope;
perSession?: boolean;
}): SandboxScope {
if (params.scope) return params.scope;
if (params.scope) {
return params.scope;
}
if (typeof params.perSession === "boolean") {
return params.perSession ? "session" : "shared";
}

View File

@@ -21,13 +21,17 @@ export async function resolveSandboxContext(params: {
workspaceDir?: string;
}): Promise<SandboxContext | null> {
const rawSessionKey = params.sessionKey?.trim();
if (!rawSessionKey) return null;
if (!rawSessionKey) {
return null;
}
const runtime = resolveSandboxRuntimeStatus({
cfg: params.config,
sessionKey: rawSessionKey,
});
if (!runtime.sandboxed) return null;
if (!runtime.sandboxed) {
return null;
}
const cfg = resolveSandboxConfigForAgent(params.config, runtime.agentId);
@@ -101,13 +105,17 @@ export async function ensureSandboxWorkspaceForSession(params: {
workspaceDir?: string;
}): Promise<SandboxWorkspaceInfo | null> {
const rawSessionKey = params.sessionKey?.trim();
if (!rawSessionKey) return null;
if (!rawSessionKey) {
return null;
}
const runtime = resolveSandboxRuntimeStatus({
cfg: params.config,
sessionKey: rawSessionKey,
});
if (!runtime.sandboxed) return null;
if (!runtime.sandboxed) {
return null;
}
const cfg = resolveSandboxConfigForAgent(params.config, runtime.agentId);

View File

@@ -38,10 +38,14 @@ export async function readDockerPort(containerName: string, port: number) {
const result = await execDocker(["port", containerName, `${port}/tcp`], {
allowFailure: true,
});
if (result.code !== 0) return null;
if (result.code !== 0) {
return null;
}
const line = result.stdout.trim().split(/\r?\n/)[0] ?? "";
const match = line.match(/:(\d+)\s*$/);
if (!match) return null;
if (!match) {
return null;
}
const mapped = Number.parseInt(match[1] ?? "", 10);
return Number.isFinite(mapped) ? mapped : null;
}
@@ -50,7 +54,9 @@ async function dockerImageExists(image: string) {
const result = await execDocker(["image", "inspect", image], {
allowFailure: true,
});
if (result.code === 0) return true;
if (result.code === 0) {
return true;
}
const stderr = result.stderr.trim();
if (stderr.includes("No such image")) {
return false;
@@ -60,7 +66,9 @@ async function dockerImageExists(image: string) {
export async function ensureDockerImage(image: string) {
const exists = await dockerImageExists(image);
if (exists) return;
if (exists) {
return;
}
if (image === DEFAULT_SANDBOX_IMAGE) {
await execDocker(["pull", "debian:bookworm-slim"]);
await execDocker(["tag", "debian:bookworm-slim", DEFAULT_SANDBOX_IMAGE]);
@@ -73,12 +81,16 @@ export async function dockerContainerState(name: string) {
const result = await execDocker(["inspect", "-f", "{{.State.Running}}", name], {
allowFailure: true,
});
if (result.code !== 0) return { exists: false, running: false };
if (result.code !== 0) {
return { exists: false, running: false };
}
return { exists: true, running: result.stdout.trim() === "true" };
}
function normalizeDockerLimit(value?: string | number) {
if (value === undefined || value === null) return undefined;
if (value === undefined || value === null) {
return undefined;
}
if (typeof value === "number") {
return Number.isFinite(value) ? String(value) : undefined;
}
@@ -90,16 +102,24 @@ function formatUlimitValue(
name: string,
value: string | number | { soft?: number; hard?: number },
) {
if (!name.trim()) return null;
if (!name.trim()) {
return null;
}
if (typeof value === "number" || typeof value === "string") {
const raw = String(value).trim();
return raw ? `${name}=${raw}` : null;
}
const soft = typeof value.soft === "number" ? Math.max(0, value.soft) : undefined;
const hard = typeof value.hard === "number" ? Math.max(0, value.hard) : undefined;
if (soft === undefined && hard === undefined) return null;
if (soft === undefined) return `${name}=${hard}`;
if (hard === undefined) return `${name}=${soft}`;
if (soft === undefined && hard === undefined) {
return null;
}
if (soft === undefined) {
return `${name}=${hard}`;
}
if (hard === undefined) {
return `${name}=${soft}`;
}
return `${name}=${soft}:${hard}`;
}
@@ -120,14 +140,22 @@ export function buildSandboxCreateArgs(params: {
args.push("--label", `openclaw.configHash=${params.configHash}`);
}
for (const [key, value] of Object.entries(params.labels ?? {})) {
if (key && value) args.push("--label", `${key}=${value}`);
if (key && value) {
args.push("--label", `${key}=${value}`);
}
}
if (params.cfg.readOnlyRoot) {
args.push("--read-only");
}
if (params.cfg.readOnlyRoot) args.push("--read-only");
for (const entry of params.cfg.tmpfs) {
args.push("--tmpfs", entry);
}
if (params.cfg.network) args.push("--network", params.cfg.network);
if (params.cfg.user) args.push("--user", params.cfg.user);
if (params.cfg.network) {
args.push("--network", params.cfg.network);
}
if (params.cfg.user) {
args.push("--user", params.cfg.user);
}
for (const cap of params.cfg.capDrop) {
args.push("--cap-drop", cap);
}
@@ -139,18 +167,26 @@ export function buildSandboxCreateArgs(params: {
args.push("--security-opt", `apparmor=${params.cfg.apparmorProfile}`);
}
for (const entry of params.cfg.dns ?? []) {
if (entry.trim()) args.push("--dns", entry);
if (entry.trim()) {
args.push("--dns", entry);
}
}
for (const entry of params.cfg.extraHosts ?? []) {
if (entry.trim()) args.push("--add-host", entry);
if (entry.trim()) {
args.push("--add-host", entry);
}
}
if (typeof params.cfg.pidsLimit === "number" && params.cfg.pidsLimit > 0) {
args.push("--pids-limit", String(params.cfg.pidsLimit));
}
const memory = normalizeDockerLimit(params.cfg.memory);
if (memory) args.push("--memory", memory);
if (memory) {
args.push("--memory", memory);
}
const memorySwap = normalizeDockerLimit(params.cfg.memorySwap);
if (memorySwap) args.push("--memory-swap", memorySwap);
if (memorySwap) {
args.push("--memory-swap", memorySwap);
}
if (typeof params.cfg.cpus === "number" && params.cfg.cpus > 0) {
args.push("--cpus", String(params.cfg.cpus));
}
@@ -158,7 +194,9 @@ export function buildSandboxCreateArgs(params: {
[string, string | number | { soft?: number; hard?: number }]
>) {
const formatted = formatUlimitValue(name, value);
if (formatted) args.push("--ulimit", formatted);
if (formatted) {
args.push("--ulimit", formatted);
}
}
if (params.cfg.binds?.length) {
for (const bind of params.cfg.binds) {
@@ -213,9 +251,13 @@ async function readContainerConfigHash(containerName: string): Promise<string |
["inspect", "-f", `{{ index .Config.Labels "${label}" }}`, containerName],
{ allowFailure: true },
);
if (result.code !== 0) return null;
if (result.code !== 0) {
return null;
}
const raw = result.stdout.trim();
if (!raw || raw === "<no value>") return null;
if (!raw || raw === "<no value>") {
return null;
}
return raw;
};
return await readLabel("openclaw.configHash");

View File

@@ -16,7 +16,9 @@ async function pruneSandboxContainers(cfg: SandboxConfig) {
const now = Date.now();
const idleHours = cfg.prune.idleHours;
const maxAgeDays = cfg.prune.maxAgeDays;
if (idleHours === 0 && maxAgeDays === 0) return;
if (idleHours === 0 && maxAgeDays === 0) {
return;
}
const registry = await readRegistry();
for (const entry of registry.entries) {
const idleMs = now - entry.lastUsedAtMs;
@@ -42,7 +44,9 @@ async function pruneSandboxBrowsers(cfg: SandboxConfig) {
const now = Date.now();
const idleHours = cfg.prune.idleHours;
const maxAgeDays = cfg.prune.maxAgeDays;
if (idleHours === 0 && maxAgeDays === 0) return;
if (idleHours === 0 && maxAgeDays === 0) {
return;
}
const registry = await readBrowserRegistry();
for (const entry of registry.entries) {
const idleMs = now - entry.lastUsedAtMs;
@@ -71,7 +75,9 @@ async function pruneSandboxBrowsers(cfg: SandboxConfig) {
export async function maybePruneSandboxes(cfg: SandboxConfig) {
const now = Date.now();
if (now - lastPruneAtMs < 5 * 60 * 1000) return;
if (now - lastPruneAtMs < 5 * 60 * 1000) {
return;
}
lastPruneAtMs = now;
try {
await pruneSandboxContainers(cfg);

View File

@@ -37,7 +37,9 @@ export async function readRegistry(): Promise<SandboxRegistry> {
try {
const raw = await fs.readFile(SANDBOX_REGISTRY_PATH, "utf-8");
const parsed = JSON.parse(raw) as SandboxRegistry;
if (parsed && Array.isArray(parsed.entries)) return parsed;
if (parsed && Array.isArray(parsed.entries)) {
return parsed;
}
} catch {
// ignore
}
@@ -65,7 +67,9 @@ export async function updateRegistry(entry: SandboxRegistryEntry) {
export async function removeRegistryEntry(containerName: string) {
const registry = await readRegistry();
const next = registry.entries.filter((item) => item.containerName !== containerName);
if (next.length === registry.entries.length) return;
if (next.length === registry.entries.length) {
return;
}
await writeRegistry({ entries: next });
}
@@ -73,7 +77,9 @@ export async function readBrowserRegistry(): Promise<SandboxBrowserRegistry> {
try {
const raw = await fs.readFile(SANDBOX_BROWSER_REGISTRY_PATH, "utf-8");
const parsed = JSON.parse(raw) as SandboxBrowserRegistry;
if (parsed && Array.isArray(parsed.entries)) return parsed;
if (parsed && Array.isArray(parsed.entries)) {
return parsed;
}
} catch {
// ignore
}
@@ -104,6 +110,8 @@ export async function updateBrowserRegistry(entry: SandboxBrowserRegistryEntry)
export async function removeBrowserRegistryEntry(containerName: string) {
const registry = await readBrowserRegistry();
const next = registry.entries.filter((item) => item.containerName !== containerName);
if (next.length === registry.entries.length) return;
if (next.length === registry.entries.length) {
return;
}
await writeBrowserRegistry({ entries: next });
}

View File

@@ -8,8 +8,12 @@ import { resolveSandboxToolPolicyForAgent } from "./tool-policy.js";
import type { SandboxConfig, SandboxToolPolicyResolved } from "./types.js";
function shouldSandboxSession(cfg: SandboxConfig, sessionKey: string, mainSessionKey: string) {
if (cfg.mode === "off") return false;
if (cfg.mode === "all") return true;
if (cfg.mode === "off") {
return false;
}
if (cfg.mode === "all") {
return true;
}
return sessionKey.trim() !== mainSessionKey.trim();
}
@@ -17,7 +21,9 @@ function resolveMainSessionKeyForSandbox(params: {
cfg?: OpenClawConfig;
agentId: string;
}): string {
if (params.cfg?.session?.scope === "global") return "global";
if (params.cfg?.session?.scope === "global") {
return "global";
}
return resolveAgentMainSessionKey({
cfg: params.cfg,
agentId: params.agentId,
@@ -78,20 +84,26 @@ export function formatSandboxToolPolicyBlockedMessage(params: {
toolName: string;
}): string | undefined {
const tool = params.toolName.trim().toLowerCase();
if (!tool) return undefined;
if (!tool) {
return undefined;
}
const runtime = resolveSandboxRuntimeStatus({
cfg: params.cfg,
sessionKey: params.sessionKey,
});
if (!runtime.sandboxed) return undefined;
if (!runtime.sandboxed) {
return undefined;
}
const deny = new Set(expandToolGroups(runtime.toolPolicy.deny));
const allow = expandToolGroups(runtime.toolPolicy.allow);
const allowSet = allow.length > 0 ? new Set(allow) : null;
const blockedByDeny = deny.has(tool);
const blockedByAllow = allowSet ? !allowSet.has(tool) : false;
if (!blockedByDeny && !blockedByAllow) return undefined;
if (!blockedByDeny && !blockedByAllow) {
return undefined;
}
const reasons: string[] = [];
const fixes: string[] = [];
@@ -112,7 +124,9 @@ export function formatSandboxToolPolicyBlockedMessage(params: {
lines.push(`Reason: ${reasons.join(" + ")}`);
lines.push("Fix:");
lines.push(`- agents.defaults.sandbox.mode=off (disable sandbox)`);
for (const fix of fixes) lines.push(`- ${fix}`);
for (const fix of fixes) {
lines.push(`- ${fix}`);
}
if (runtime.mode === "non-main") {
lines.push(`- Use main session key (direct): ${runtime.mainSessionKey}`);
}

View File

@@ -24,16 +24,24 @@ export function resolveSandboxWorkspaceDir(root: string, sessionKey: string) {
export function resolveSandboxScopeKey(scope: "session" | "agent" | "shared", sessionKey: string) {
const trimmed = sessionKey.trim() || "main";
if (scope === "shared") return "shared";
if (scope === "session") return trimmed;
if (scope === "shared") {
return "shared";
}
if (scope === "session") {
return trimmed;
}
const agentId = resolveAgentIdFromSessionKey(trimmed);
return `agent:${agentId}`;
}
export function resolveSandboxAgentId(scopeKey: string): string | undefined {
const trimmed = scopeKey.trim();
if (!trimmed || trimmed === "shared") return undefined;
if (!trimmed || trimmed === "shared") {
return undefined;
}
const parts = trimmed.split(":").filter(Boolean);
if (parts[0] === "agent" && parts[1]) return normalizeAgentId(parts[1]);
if (parts[0] === "agent" && parts[1]) {
return normalizeAgentId(parts[1]);
}
return resolveAgentIdFromSessionKey(trimmed);
}

View File

@@ -15,9 +15,15 @@ type CompiledPattern =
function compilePattern(pattern: string): CompiledPattern {
const normalized = pattern.trim().toLowerCase();
if (!normalized) return { kind: "exact", value: "" };
if (normalized === "*") return { kind: "all" };
if (!normalized.includes("*")) return { kind: "exact", value: normalized };
if (!normalized) {
return { kind: "exact", value: "" };
}
if (normalized === "*") {
return { kind: "all" };
}
if (!normalized.includes("*")) {
return { kind: "exact", value: normalized };
}
const escaped = normalized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
return {
kind: "regex",
@@ -26,7 +32,9 @@ function compilePattern(pattern: string): CompiledPattern {
}
function compilePatterns(patterns?: string[]): CompiledPattern[] {
if (!Array.isArray(patterns)) return [];
if (!Array.isArray(patterns)) {
return [];
}
return expandToolGroups(patterns)
.map(compilePattern)
.filter((pattern) => pattern.kind !== "exact" || pattern.value);
@@ -34,9 +42,15 @@ function compilePatterns(patterns?: string[]): CompiledPattern[] {
function matchesAny(name: string, patterns: CompiledPattern[]): boolean {
for (const pattern of patterns) {
if (pattern.kind === "all") return true;
if (pattern.kind === "exact" && name === pattern.value) return true;
if (pattern.kind === "regex" && pattern.value.test(name)) return true;
if (pattern.kind === "all") {
return true;
}
if (pattern.kind === "exact" && name === pattern.value) {
return true;
}
if (pattern.kind === "regex" && pattern.value.test(name)) {
return true;
}
}
return false;
}
@@ -44,9 +58,13 @@ function matchesAny(name: string, patterns: CompiledPattern[]): boolean {
export function isToolAllowed(policy: SandboxToolPolicy, name: string) {
const normalized = name.trim().toLowerCase();
const deny = compilePatterns(policy.deny);
if (matchesAny(normalized, deny)) return false;
if (matchesAny(normalized, deny)) {
return false;
}
const allow = compilePatterns(policy.allow);
if (allow.length === 0) return true;
if (allow.length === 0) {
return true;
}
return matchesAny(normalized, allow);
}