test: harden restart sentinel and host env coverage
This commit is contained in:
@@ -120,6 +120,39 @@ describe("sanitizeHostExecEnv", () => {
|
||||
expect(env[" BAD KEY"]).toBeUndefined();
|
||||
expect(env["NOT-PORTABLE"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("can allow PATH overrides when explicitly opted out of blocking", () => {
|
||||
const env = sanitizeHostExecEnv({
|
||||
baseEnv: {
|
||||
PATH: "/usr/bin:/bin",
|
||||
},
|
||||
overrides: {
|
||||
PATH: "/custom/bin",
|
||||
},
|
||||
blockPathOverrides: false,
|
||||
});
|
||||
|
||||
expect(env.PATH).toBe("/custom/bin");
|
||||
expect(env.OPENCLAW_CLI).toBe(OPENCLAW_CLI_ENV_VALUE);
|
||||
});
|
||||
|
||||
it("drops non-string inherited values and non-portable inherited keys", () => {
|
||||
const env = sanitizeHostExecEnv({
|
||||
baseEnv: {
|
||||
PATH: "/usr/bin:/bin",
|
||||
GOOD: "1",
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
BAD_NUMBER: 1 as any,
|
||||
"NOT-PORTABLE": "x",
|
||||
},
|
||||
});
|
||||
|
||||
expect(env).toEqual({
|
||||
OPENCLAW_CLI: OPENCLAW_CLI_ENV_VALUE,
|
||||
PATH: "/usr/bin:/bin",
|
||||
GOOD: "1",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("isDangerousHostEnvOverrideVarName", () => {
|
||||
@@ -174,6 +207,33 @@ describe("sanitizeSystemRunEnvOverrides", () => {
|
||||
LC_ALL: "C",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns undefined when no shell-wrapper overrides survive", () => {
|
||||
expect(
|
||||
sanitizeSystemRunEnvOverrides({
|
||||
shellWrapper: true,
|
||||
overrides: {
|
||||
TOKEN: "abc",
|
||||
},
|
||||
}),
|
||||
).toBeUndefined();
|
||||
expect(sanitizeSystemRunEnvOverrides({ shellWrapper: true })).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps allowlisted shell-wrapper overrides case-insensitively", () => {
|
||||
expect(
|
||||
sanitizeSystemRunEnvOverrides({
|
||||
shellWrapper: true,
|
||||
overrides: {
|
||||
lang: "C",
|
||||
ColorTerm: "truecolor",
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
lang: "C",
|
||||
ColorTerm: "truecolor",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("shell wrapper exploit regression", () => {
|
||||
|
||||
@@ -5,9 +5,11 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { captureEnv } from "../test-utils/env.js";
|
||||
import {
|
||||
consumeRestartSentinel,
|
||||
formatDoctorNonInteractiveHint,
|
||||
formatRestartSentinelMessage,
|
||||
readRestartSentinel,
|
||||
resolveRestartSentinelPath,
|
||||
summarizeRestartSentinel,
|
||||
trimLogTail,
|
||||
writeRestartSentinel,
|
||||
} from "./restart-sentinel.js";
|
||||
@@ -59,6 +61,15 @@ describe("restart sentinel", () => {
|
||||
await expect(fs.stat(filePath)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("drops structurally invalid sentinel payloads", async () => {
|
||||
const filePath = resolveRestartSentinelPath();
|
||||
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
||||
await fs.writeFile(filePath, JSON.stringify({ version: 2, payload: null }), "utf-8");
|
||||
|
||||
await expect(readRestartSentinel()).resolves.toBeNull();
|
||||
await expect(fs.stat(filePath)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("formatRestartSentinelMessage uses custom message when present", () => {
|
||||
const payload = {
|
||||
kind: "config-apply" as const,
|
||||
@@ -93,6 +104,26 @@ describe("restart sentinel", () => {
|
||||
expect(result).toContain("Gateway restart");
|
||||
});
|
||||
|
||||
it("formats summary, distinct reason, and doctor hint together", () => {
|
||||
const payload = {
|
||||
kind: "config-patch" as const,
|
||||
status: "error" as const,
|
||||
ts: Date.now(),
|
||||
message: "Patch failed",
|
||||
doctorHint: "Run openclaw doctor",
|
||||
stats: { mode: "patch", reason: "validation failed" },
|
||||
};
|
||||
|
||||
expect(formatRestartSentinelMessage(payload)).toBe(
|
||||
[
|
||||
"Gateway restart config-patch error (patch)",
|
||||
"Patch failed",
|
||||
"Reason: validation failed",
|
||||
"Run openclaw doctor",
|
||||
].join("\n"),
|
||||
);
|
||||
});
|
||||
|
||||
it("trims log tails", () => {
|
||||
const text = "a".repeat(9000);
|
||||
const trimmed = trimLogTail(text, 8000);
|
||||
@@ -115,6 +146,18 @@ describe("restart sentinel", () => {
|
||||
expect(textA).toContain("Gateway restart restart ok");
|
||||
expect(textA).not.toContain('"ts"');
|
||||
});
|
||||
|
||||
it("summarizes restart payloads and trims log tails without trailing whitespace", () => {
|
||||
expect(
|
||||
summarizeRestartSentinel({
|
||||
kind: "update",
|
||||
status: "skipped",
|
||||
ts: 1,
|
||||
}),
|
||||
).toBe("Gateway restart update skipped");
|
||||
expect(trimLogTail("hello\n")).toBe("hello");
|
||||
expect(trimLogTail(undefined)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("restart sentinel message dedup", () => {
|
||||
@@ -145,4 +188,10 @@ describe("restart sentinel message dedup", () => {
|
||||
expect(result).toContain("Restart requested by /restart");
|
||||
expect(result).toContain("Reason: /restart");
|
||||
});
|
||||
|
||||
it("formats the non-interactive doctor command", () => {
|
||||
expect(formatDoctorNonInteractiveHint({ PATH: "/usr/bin:/bin" })).toContain(
|
||||
"openclaw doctor --non-interactive",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user