From 71c57ea647fbc376d1207f11d851c09504c9c02d Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Tue, 25 Aug 2026 10:37:05 +0900 Subject: [PATCH 1/4] release: v2.32.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f73ed2d0e5..063ecfe73e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitkyc08/opencodex", - "version": "2.32.0", + "version": "2.32.1", "description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code", "type": "module", "main": "./bin/package-main.mjs", From ec51e42d745d2645bcb22cb67855fa053ba1778e Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Tue, 25 Aug 2026 20:25:22 +0900 Subject: [PATCH 2/4] release: v2.33.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 063ecfe73e..6f8499ffbf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitkyc08/opencodex", - "version": "2.32.1", + "version": "2.33.0", "description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code", "type": "module", "main": "./bin/package-main.mjs", From aaa9eaf37058965373dc42d1ca344e987950b6b6 Mon Sep 17 00:00:00 2001 From: JUN Date: Wed, 2 Sep 2026 18:43:29 +0900 Subject: [PATCH 3/4] fix(release): pass the bump job's permissions through the reusable-workflow call (#3262) Both v2.40.0 release dispatches (33615174183 preview, 33615177849 main) died at startup_failure: a workflow_call cannot grant its callee more than the calling job holds, and dev-version-bump.yml's job declares contents+pull- requests write. #3129 wired the call but never dispatched a release, so this is its first live run. The caller job now declares exactly the callee's two permissions; no other job in release.yml gains anything. Co-authored-by: jun (cherry picked from commit 7ce0ba51834740d7b4d5ec4793f6572d84624409) --- .github/workflows/release.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 458bb67e0a..261aece1d1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,6 +67,14 @@ jobs: bump-dev-version: needs: publish if: ${{ inputs.dry-run != true }} + # A reusable-workflow CALL cannot grant the callee more than the calling job holds, + # and GitHub refuses the whole run at startup when the called workflow's own job + # declares permissions the caller did not pass down ("startup_failure", runs + # 33615174183 / 33615177849 — the first dispatches since #3129 wired this call). + # The callee's job declares exactly these two; nothing else in this file gains them. + permissions: + contents: write + pull-requests: write uses: ./.github/workflows/dev-version-bump.yml with: released-version: v${{ inputs.version }} From 75867f01509e40f5656a60aed82a4c008ee6493e Mon Sep 17 00:00:00 2001 From: luvs01 Date: Thu, 3 Sep 2026 14:55:08 +0900 Subject: [PATCH 4/4] fix(service): bind scheduler action principal to current user --- src/service.ts | 19 ++++++++++++++++++- tests/service.test.ts | 15 +++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/service.ts b/src/service.ts index 84b7da3817..8e1e5aae28 100644 --- a/src/service.ts +++ b/src/service.ts @@ -1871,7 +1871,7 @@ export function buildWindowsTaskXml( - InteractiveToken + ${sessionTriggerUserId ? `${taskXmlString(sessionTriggerUserId)}\n ` : ""}InteractiveToken LeastPrivilege @@ -2136,6 +2136,18 @@ function windowsTaskTriggerScopeAcceptable( return expectedValues.some(value => taskXmlDecodedValueEquals(element, "UserId", value)); } +/** The account whose interactive token executes the action must be the current account. */ +function windowsTaskPrincipalScopeAcceptable( + principal: string, + expectedUserId: ExpectedWindowsTaskUserId | undefined, +): boolean { + if (taskXmlHasPrefixedTag(principal, "UserId")) return false; + if (taskXmlElementCount(principal, "UserId") !== 1) return false; + if (expectedUserId === undefined) return false; + const expectedValues = typeof expectedUserId === "string" ? [expectedUserId] : expectedUserId; + return expectedValues.some(value => taskXmlDecodedValueEquals(principal, "UserId", value)); +} + /** Validate the stable OpenCodex action, principal, settings, and logon trigger. */ function windowsTaskRegistrationBaseHealthy( xml: string, @@ -2187,7 +2199,12 @@ export function windowsTaskRegistrationHealthy( ): boolean { const scrubbed = taskXmlWithoutCommentsAndCdata(xml); const triggers = taskXmlSection(scrubbed, "Triggers"); + const principal = taskXmlSection(scrubbed, "Principal"); return windowsTaskRegistrationBaseHealthy(xml, wscript, launcher) + // The trigger scope says whose session event fires the task; Principal/UserId + // independently says whose interactive token executes its action. Both are + // identity boundaries and must match exactly, even when action paths are lossy. + && windowsTaskPrincipalScopeAcceptable(principal, expectedUserId ?? undefined) // Without these the task can only recover at the next logon, so a disconnected session // leaves the proxy down indefinitely. Treating their absence as unhealthy is what lets // an already-registered task from an older install get repaired instead of staying broken. diff --git a/tests/service.test.ts b/tests/service.test.ts index fca80972cc..aa37c764fe 100644 --- a/tests/service.test.ts +++ b/tests/service.test.ts @@ -574,6 +574,21 @@ describe("Windows service task", () => { expect(healthy("C:\\Users\\Admin\\.opencodex\\service-launcher.vbs")).toBe(false); }); + test("rejects a lossy foreign action even when its triggers name the current account", () => { + const victimSid = "S-1-5-21-111-222-333-1001"; + const attackerSid = "S-1-5-21-111-222-333-1002"; + const foreign = buildWindowsTaskXml( + "ignored.cmd", + "C:\\Users\\???\\.opencodex\\service-launcher.vbs", + undefined, + victimSid, + ) + .replace(/.*?<\/Command>/, `${wscript}`) + .replace(`${victimSid}\n `, `${attackerSid}\n `); + + expect(windowsTaskRegistrationHealthy(foreign, wscript, launcher, victimSid)).toBe(false); + }); + test("rejects a path whose ASCII structure differs", () => { expect(healthy("C:\\Users\\???\\.opencodex\\other-launcher.vbs")).toBe(false); expect(healthy("D:\\Users\\???\\.opencodex\\service-launcher.vbs")).toBe(false);