From 13f9deb619796f128a2bc93b45b9cd6375be2a2a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 27 Apr 2026 10:19:50 +0100 Subject: [PATCH] fix: audit windows task managed env drift --- docs/gateway/doctor.md | 2 +- src/daemon/schtasks.install.test.ts | 32 +++++++++++++++++++++++++++++ src/daemon/schtasks.test.ts | 4 ++++ src/daemon/schtasks.ts | 7 +++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/docs/gateway/doctor.md b/docs/gateway/doctor.md index 5459f5558a8..f7e5a6b7f56 100644 --- a/docs/gateway/doctor.md +++ b/docs/gateway/doctor.md @@ -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. diff --git a/src/daemon/schtasks.install.test.ts b/src/daemon/schtasks.install.test.ts index 1bd3b6b0dce..7cef5c81095 100644 --- a/src/daemon/schtasks.install.test.ts +++ b/src/daemon/schtasks.install.test.ts @@ -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); + }); + }); }); diff --git a/src/daemon/schtasks.test.ts b/src/daemon/schtasks.test.ts index 469ca584a7d..8b41421c158 100644 --- a/src/daemon/schtasks.test.ts +++ b/src/daemon/schtasks.test.ts @@ -237,6 +237,10 @@ describe("readScheduledTaskCommand", () => { NODE_ENV: "production", OPENCLAW_PORT: "18789", }, + environmentValueSources: { + NODE_ENV: "inline", + OPENCLAW_PORT: "inline", + }, sourcePath: resolveTaskScriptPath(env), }); }, diff --git a/src/daemon/schtasks.ts b/src/daemon/schtasks.ts index 5e85514e553..4674cfbe71e 100644 --- a/src/daemon/schtasks.ts +++ b/src/daemon/schtasks.ts @@ -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 {