Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/service-manager-probe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,19 @@ function inspectWindows(
}

if (task === "absent") {
return schedulerRegistered
if (!schedulerRegistered) return { kind: "absent" };
if (!registration.registeredXml.trim()) {
return unknown("Task Scheduler is registered but its definition XML could not be read");
}
const launcherArg = windowsTaskArguments(registration.registeredXml);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject multiple task actions before declaring the task foreign

When the on-disk task XML is absent and the registered task contains multiple valid <Exec> actions, this reads only the first <Arguments> element. If that first action names a launcher outside configDir while a later action launches the current home's wrapper, the branch returns absent; inspectNativeCodexOwnership can then authorize unattended Codex writes despite an unaccounted current-home scheduler registration. Fail closed unless the registered XML has exactly one structurally valid executable action (as windowsTaskRegistrationHealthy already requires), then classify its launcher path.

Useful? React with 👍 / 👎.

if (!launcherArg) {
return unknown("Task Scheduler holds opencodex-proxy but its task XML is missing");
}
const launcherPath = /"([^"]+)"/.exec(launcherArg)?.[1];
if (!launcherPath) {
return unknown("Task Scheduler holds opencodex-proxy but its task XML is missing");
}
return windowsPathInsideConfigDir(launcherPath, configDir)
? unknown("Task Scheduler holds opencodex-proxy but its task XML is missing")
: { kind: "absent" };
}
Expand Down
55 changes: 55 additions & 0 deletions tests/codex-service-manager-probe-hardening.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,61 @@ function taskAbsentRunner(calls: Array<{ file: string; args: readonly string[] }
}

describe("Windows ownership probe hardening regressions", () => {
test("a scheduler registered for another OpenCodex home does not claim the current home (#2800)", () => {
const foreignConfigDir = join(home, "foreign-opencodex");
const foreignLauncher = join(foreignConfigDir, "opencodex-service-launcher.vbs");
const runRaw: RawProbeRunner = (file, args) => {
if (file.toLowerCase().endsWith("sc.exe")) return raw(1, "", "1060");
if (args.includes("/xml")) return raw(0, schedulerXml(foreignLauncher));
return raw(1, "", "unexpected query");
};

const result = inspectNativeCodexOwnership({
platform: "win32",
home,
configDir,
runRaw,
winswStatus: () => "nonexistent",
statePaths: [],
currentHomes: {
codexHome: join(home, "current-codex"),
opencodexHome: configDir,
},
});

expect(result).toEqual({
ownership: "owned",
reason: "no service state and no service manager claim",
});
});

test("a current-home scheduler with missing local task XML remains unproven", () => {
const localLauncher = join(configDir, "opencodex-service-launcher.vbs");
const runRaw: RawProbeRunner = (file, args) => {
if (file.toLowerCase().endsWith("sc.exe")) return raw(1, "", "1060");
if (args.includes("/xml")) return raw(0, schedulerXml(localLauncher));
return raw(1, "", "unexpected query");
};

const result = inspectNativeCodexOwnership({
platform: "win32",
home,
configDir,
runRaw,
winswStatus: () => "nonexistent",
statePaths: [],
currentHomes: {
codexHome: join(home, "current-codex"),
opencodexHome: configDir,
},
});

expect(result).toEqual({
ownership: "unknown",
reason: "Task Scheduler holds opencodex-proxy but its task XML is missing",
});
});

test("registered CP949 task XML preserves a Korean profile path", () => {
const koreanConfigDir = join(home, "한글", ".opencodex");
const codexHome = join(home, "한글", ".codex");
Expand Down
Loading