fix: audit windows task managed env drift

This commit is contained in:
Peter Steinberger
2026-04-27 10:19:50 +01:00
parent cb9955dd5c
commit 13f9deb619
4 changed files with 44 additions and 1 deletions

View File

@@ -433,7 +433,7 @@ That stages grounded durable candidates into the short-term dreaming store while
- `openclaw doctor --repair --force` overwrites custom supervisor configs.
- `OPENCLAW_SERVICE_REPAIR_POLICY=external` keeps doctor read-only for gateway service lifecycle. It still reports service health and runs non-service repairs, but skips service install/start/restart/bootstrap, supervisor config rewrites, and legacy service cleanup because an external supervisor owns that lifecycle.
- If token auth requires a token and `gateway.auth.token` is SecretRef-managed, doctor service install/repair validates the SecretRef but does not persist resolved plaintext token values into supervisor service environment metadata.
- Doctor detects managed `.env`/SecretRef-backed service environment values that older LaunchAgent/systemd installs embedded inline and rewrites the service metadata so those values load from the runtime source instead of the supervisor definition.
- Doctor detects managed `.env`/SecretRef-backed service environment values that older LaunchAgent, systemd, or Windows Scheduled Task installs embedded inline and rewrites the service metadata so those values load from the runtime source instead of the supervisor definition.
- If token auth requires a token and the configured token SecretRef is unresolved, doctor blocks the install/repair path with actionable guidance.
- If both `gateway.auth.token` and `gateway.auth.password` are configured and `gateway.auth.mode` is unset, doctor blocks install/repair until mode is set explicitly.
- For Linux user-systemd units, doctor token drift checks now include both `Environment=` and `EnvironmentFile=` sources when comparing service auth metadata.

View File

@@ -4,6 +4,7 @@ import path from "node:path";
import { PassThrough } from "node:stream";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { installScheduledTask, readScheduledTaskCommand } from "./schtasks.js";
import { auditGatewayServiceConfig, SERVICE_AUDIT_CODES } from "./service-audit.js";
const schtasksCalls: string[][] = [];
const schtasksResponses: { code: number; stdout: string; stderr: string }[] = [];
@@ -243,4 +244,35 @@ describe("installScheduledTask", () => {
expect(script).toContain('set "OPENCLAW_GATEWAY_PORT=18789"');
});
});
it("exposes Windows task script env values as inline for managed-env drift audit", async () => {
await withUserProfileDir(async (_tmpDir, env) => {
const { scriptPath } = await installScheduledTask({
env,
stdout: new PassThrough(),
programArguments: ["node", "gateway.js"],
environment: {
OPENCLAW_SERVICE_MANAGED_ENV_KEYS: "TAVILY_API_KEY",
TAVILY_API_KEY: "old-inline-value",
},
});
const command = await readScheduledTaskCommand(env);
expect(command?.environmentValueSources).toMatchObject({
OPENCLAW_SERVICE_MANAGED_ENV_KEYS: "inline",
TAVILY_API_KEY: "inline",
});
expect(command?.sourcePath).toBe(scriptPath);
const audit = await auditGatewayServiceConfig({
env,
platform: "win32",
command,
expectedManagedServiceEnvKeys: ["TAVILY_API_KEY"],
});
expect(
audit.issues.some((issue) => issue.code === SERVICE_AUDIT_CODES.gatewayManagedEnvEmbedded),
).toBe(true);
});
});
});

View File

@@ -237,6 +237,10 @@ describe("readScheduledTaskCommand", () => {
NODE_ENV: "production",
OPENCLAW_PORT: "18789",
},
environmentValueSources: {
NODE_ENV: "inline",
OPENCLAW_PORT: "inline",
},
sourcePath: resolveTaskScriptPath(env),
});
},

View File

@@ -150,6 +150,13 @@ export async function readScheduledTaskCommand(
programArguments: parseCmdScriptCommandLine(commandLine),
...(workingDirectory ? { workingDirectory } : {}),
...(Object.keys(environment).length > 0 ? { environment } : {}),
...(Object.keys(environment).length > 0
? {
environmentValueSources: Object.fromEntries(
Object.keys(environment).map((key) => [key, "inline"]),
),
}
: {}),
sourcePath: scriptPath,
};
} catch {