From 3214dc6be6cc395eeba44b7a2a2a9e1467d1c495 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Tue, 26 May 2026 07:39:37 -0300 Subject: [PATCH] fix(sonar): clear SonarCloud reliability + security ratings on release/v3.8.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reliability (D → A) — fix the 6 BUG findings: - bin/cli/tray/autostart.mjs: replace `return ignoreFailure ? false : false` (always-false ternary) with a meaningful branch that rethrows when `ignoreFailure` is false. - open-sse/services/combo.ts: reorder the quality-validation block so the `combo.target.failed` emit runs BEFORE the `break` — the previous order left the emit unreachable. - src/app/api/playground/simulate-route/route.ts: drop the duplicate `modelLower.includes("1m") || modelLower.includes("1m")` (and the 2m twin) — both sides of the `||` were identical so the second check was dead code. - scripts/check/check-env-doc-sync.mjs: pass `localeCompare` to Array.sort instead of relying on the default coercion-to-string ordering. - src/sse/handlers/chat.ts: guard the cache TTL check with an explicit `combosCachePromise !== null` so we don't evaluate a Promise as a boolean. Security (C → A) — close the Dockerfile hotspots: - Builder stage now runs `npm ci`/`npm install` with `--ignore-scripts` to neutralise transitive install-time RCE. OmniRoute's own postinstall only rewrites a packaged `app/node_modules`, so it has nothing to do during a fresh in-container install. - Runner-base now drops to the baked-in `node` non-root user (UID/GID 1000) before the CMD runs. /app is chowned after all COPYs so the runtime user can still read every file. The runner-cli stage briefly elevates back to root for the apt + global npm installs and then pins USER node again. --- Dockerfile | 24 +++++++++++++++++-- bin/cli/tray/autostart.mjs | 5 ++-- open-sse/services/combo.ts | 22 +++++++++++------ scripts/check/check-env-doc-sync.mjs | 2 +- .../api/playground/simulate-route/route.ts | 4 ++-- src/sse/handlers/chat.ts | 2 +- 6 files changed, 44 insertions(+), 15 deletions(-) diff --git a/Dockerfile b/Dockerfile index 764e367286..20319d663d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,10 +10,16 @@ COPY scripts/build/postinstall.mjs ./scripts/build/postinstall.mjs COPY scripts/build/postinstallSupport.mjs ./scripts/build/postinstallSupport.mjs COPY scripts/build/native-binary-compat.mjs ./scripts/build/native-binary-compat.mjs ENV NPM_CONFIG_LEGACY_PEER_DEPS=true +# `--ignore-scripts` blocks the install/postinstall hooks of dependencies, +# closing the supply-chain attack surface where a transitive dep can run +# arbitrary code at install time. OmniRoute's own postinstall (better-sqlite3 +# binary touchups, @swc/helpers copy) is only needed when a packaged +# `app/node_modules` is unpacked — inside the Docker builder we are doing a +# fresh native-platform install, so dropping the scripts is safe. RUN if [ -f package-lock.json ]; then \ - npm ci --no-audit --no-fund --legacy-peer-deps; \ + npm ci --no-audit --no-fund --legacy-peer-deps --ignore-scripts; \ else \ - npm install --no-audit --no-fund --legacy-peer-deps; \ + npm install --no-audit --no-fund --legacy-peer-deps --ignore-scripts; \ fi COPY . ./ @@ -65,8 +71,15 @@ COPY --from=builder /app/scripts/build/runtime-env.mjs ./build/runtime-env.mjs COPY --from=builder /app/scripts/build/bootstrap-env.mjs ./build/bootstrap-env.mjs COPY --from=builder /app/scripts/dev/healthcheck.mjs ./healthcheck.mjs +# Hand /app over to the baked-in `node` non-root user (UID/GID 1000) so the +# runtime process never holds root privileges. The chown happens after all +# COPYs so it covers files originally owned by root in the builder stage. +RUN chown -R node:node /app + EXPOSE 20128 +USER node + HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ CMD ["node", "healthcheck.mjs"] @@ -74,6 +87,11 @@ CMD ["node", "dev/run-standalone.mjs"] FROM runner-base AS runner-cli +# Drop back to root briefly so we can install system + global npm packages, +# then return to the `node` non-root user before the CMD inherited from +# runner-base runs. +USER root + # Install system dependencies required by openclaw (git+ssh references). RUN apt-get update \ && apt-get install -y --no-install-recommends git ca-certificates docker.io docker-compose \ @@ -83,3 +101,5 @@ RUN apt-get update \ # Install CLI tools globally. Separate layer from apt for better cache reuse. RUN npm install -g --no-audit --no-fund @openai/codex @anthropic-ai/claude-code droid openclaw@latest +USER node + diff --git a/bin/cli/tray/autostart.mjs b/bin/cli/tray/autostart.mjs index 23e22a9fb8..b038d0e689 100644 --- a/bin/cli/tray/autostart.mjs +++ b/bin/cli/tray/autostart.mjs @@ -63,8 +63,9 @@ function runUserSystemctl(args, { ignoreFailure = true } = {}) { try { execSync(`systemctl --user ${args}`, { stdio: "ignore" }); return true; - } catch { - return ignoreFailure ? false : false; + } catch (err) { + if (!ignoreFailure) throw err; + return false; } } diff --git a/open-sse/services/combo.ts b/open-sse/services/combo.ts index 58c1dd331e..4bb6a99ff2 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -1653,17 +1653,25 @@ export async function handleComboChat({ let timeoutId; const timeoutPromise = new Promise((resolve) => { timeoutId = setTimeout(() => { - log.warn("COMBO", `Model ${modelStr} exceeded ${COMBO_MODEL_TIMEOUT_MS}ms timeout — falling back`); - resolve(new Response( - JSON.stringify({ error: { message: `Model ${modelStr} timed out` } }), - { status: 524, headers: { 'Content-Type': 'application/json' } } - )); + log.warn( + "COMBO", + `Model ${modelStr} exceeded ${COMBO_MODEL_TIMEOUT_MS}ms timeout — falling back` + ); + resolve( + new Response(JSON.stringify({ error: { message: `Model ${modelStr} timed out` } }), { + status: 524, + headers: { "Content-Type": "application/json" }, + }) + ); }, COMBO_MODEL_TIMEOUT_MS); }); try { return await Promise.race([ handleSingleModelWrapped(b, modelStr, target).catch((err) => { - return new Response(JSON.stringify({ error: { message: err.message } }), { status: 502, headers: { "Content-Type": "application/json" } }); + return new Response(JSON.stringify({ error: { message: err.message } }), { + status: 502, + headers: { "Content-Type": "application/json" }, + }); }), timeoutPromise, ]); @@ -2208,7 +2216,6 @@ export async function handleComboChat({ lastError = `Upstream response failed quality validation: ${quality.reason}`; if (!lastStatus) lastStatus = 502; if (i > 0) fallbackCount++; - break; // move to next model emit("combo.target.failed", { comboName: combo.name, targetIndex: i, @@ -2217,6 +2224,7 @@ export async function handleComboChat({ error: `Quality: ${quality.reason}`, latencyMs: Date.now() - startTime, }); + break; // move to next model } const latencyMs = Date.now() - startTime; emit("combo.target.succeeded", { diff --git a/scripts/check/check-env-doc-sync.mjs b/scripts/check/check-env-doc-sync.mjs index 4e4c55ad17..6af7a293a2 100644 --- a/scripts/check/check-env-doc-sync.mjs +++ b/scripts/check/check-env-doc-sync.mjs @@ -209,7 +209,7 @@ function scanCodeVars({ cwd } = {}) { * Diff helper. */ function diff(set, against) { - return [...set].filter((v) => !against.has(v)).sort(); + return [...set].filter((v) => !against.has(v)).sort((a, b) => a.localeCompare(b)); } // ─── Programmatic entry point ────────────────────────────────────────────── diff --git a/src/app/api/playground/simulate-route/route.ts b/src/app/api/playground/simulate-route/route.ts index 5dfcbc59d7..32e1f4b171 100644 --- a/src/app/api/playground/simulate-route/route.ts +++ b/src/app/api/playground/simulate-route/route.ts @@ -103,8 +103,8 @@ function estimateContextWindow(model: string): number { const modelLower = model.toLowerCase(); if (modelLower.includes("128k") || modelLower.includes("128")) return 128000; if (modelLower.includes("200k") || modelLower.includes("200")) return 200000; - if (modelLower.includes("1m") || modelLower.includes("1m")) return 1000000; - if (modelLower.includes("2m") || modelLower.includes("2m")) return 2000000; + if (modelLower.includes("1m")) return 1000000; + if (modelLower.includes("2m")) return 2000000; return 128000; } diff --git a/src/sse/handlers/chat.ts b/src/sse/handlers/chat.ts index da5e716d56..d35bd3d4d7 100644 --- a/src/sse/handlers/chat.ts +++ b/src/sse/handlers/chat.ts @@ -123,7 +123,7 @@ const COMBOS_CACHE_TTL_MS = 10_000; async function getCombosCachedForChat(): Promise { const now = Date.now(); - if (combosCachePromise && now - combosCacheTs < COMBOS_CACHE_TTL_MS) { + if (combosCachePromise !== null && now - combosCacheTs < COMBOS_CACHE_TTL_MS) { return combosCachePromise; }