fix(sonar): clear SonarCloud reliability + security ratings on release/v3.8.4

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.
This commit is contained in:
diegosouzapw
2026-05-26 07:39:37 -03:00
parent 92ee9f87dd
commit 3214dc6be6
6 changed files with 44 additions and 15 deletions

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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", {

View File

@@ -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 ──────────────────────────────────────────────

View File

@@ -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;
}

View File

@@ -123,7 +123,7 @@ const COMBOS_CACHE_TTL_MS = 10_000;
async function getCombosCachedForChat(): Promise<unknown[]> {
const now = Date.now();
if (combosCachePromise && now - combosCacheTs < COMBOS_CACHE_TTL_MS) {
if (combosCachePromise !== null && now - combosCacheTs < COMBOS_CACHE_TTL_MS) {
return combosCachePromise;
}