-
Notifications
You must be signed in to change notification settings - Fork 0
fix(ci): prevent branch-selected installed-gate workflows #603
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,59 +7,26 @@ name: desktop installed-artifact gate | |
| # publication wiring into release.yml is a separate change. | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| version: | ||
| description: Release version whose desktop artifacts the gate installs | ||
| required: true | ||
| type: string | ||
| from-version: | ||
| description: Older release used for the staged npm runtime and the Linux update phases | ||
| required: true | ||
| type: string | ||
| # Hook inputs are FILE NAMES, never command text. The runner's operator installs | ||
| # audited executables in a hooks directory (vars.OPENCODEX_GATE_HOOKS_DIR) and a | ||
| # dispatch picks among them by name; the gate executes the file directly, so this | ||
| # workflow can never become an arbitrary-shell surface on a persistent runner. | ||
| consent-hook: | ||
| description: Name of the runner hook that answers the takeover consent prompt | ||
| required: false | ||
| type: string | ||
| tray-click-hook: | ||
| description: Name of the runner hook that left-clicks the tray icon | ||
| required: false | ||
| type: string | ||
| tray-quit-hook: | ||
| description: Name of the runner hook that opens the tray menu and chooses Quit | ||
| required: false | ||
| type: string | ||
| tray-check-hook: | ||
| description: Name of the runner hook that chooses Check for Updates in the tray | ||
| required: false | ||
| type: string | ||
| tray-install-hook: | ||
| description: Name of the runner hook that chooses Install update in the tray | ||
| required: false | ||
| type: string | ||
| elevate-accept-hook: | ||
| description: Name of the runner hook that answers the deb update's elevation prompt (drives the accept path) | ||
| required: false | ||
| type: string | ||
| # repository_dispatch always loads this workflow from the default branch. Unlike | ||
| # workflow_dispatch, a caller cannot select a branch-owned workflow definition and | ||
| # thereby route arbitrary steps onto these privileged self-hosted runners. | ||
| repository_dispatch: | ||
| types: [desktop-installed-gate] | ||
|
Comment on lines
+13
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟨 Unvalidated dispatch payloads reach privileged self-hosted runners Repository dispatches no longer validate required versions before scheduling. Malformed events can occupy every privileged gate runner and trigger failing artifact operations after approval. Was this helpful? React with 👍 or 👎 to provide feedback.
Comment on lines
+13
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When this commit lands on AGENTS.md reference: AGENTS.md:L339-L341 Useful? React with 👍 / 👎. |
||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: desktop-installed-gate-${{ inputs.version }} | ||
| group: desktop-installed-gate-${{ github.event.client_payload.version }} | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| macos: | ||
| runs-on: [self-hosted, opencodex-gate-macos] | ||
| timeout-minutes: 60 | ||
| # Required-review environment: no run reaches the GUI runner without a maintainer | ||
| # approval, and the checkout below pins the driver to the protected dev branch, so a | ||
| # dispatched ref cannot smuggle modified gate code onto the machine. | ||
| # approval. repository_dispatch loads the workflow from the default branch, and the | ||
| # checkout below pins the driver itself to the protected dev branch. | ||
| environment: opencodex-desktop-gate | ||
| defaults: | ||
| run: | ||
|
|
@@ -77,7 +44,7 @@ jobs: | |
| - name: Download the release artifact | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| RELEASE_VERSION: ${{ inputs.version }} | ||
| RELEASE_VERSION: ${{ github.event.client_payload['version'] }} | ||
| GATE_ARTIFACTS: ${{ runner.temp }}/gate-artifacts | ||
| run: | | ||
| mkdir -p "$GATE_ARTIFACTS" | ||
|
|
@@ -88,11 +55,11 @@ jobs: | |
|
|
||
| - name: Run the installed-artifact gate | ||
| env: | ||
| RELEASE_VERSION: ${{ inputs.version }} | ||
| FROM_VERSION: ${{ inputs.from-version }} | ||
| CONSENT_HOOK: ${{ inputs.consent-hook }} | ||
| TRAY_CLICK_HOOK: ${{ inputs.tray-click-hook }} | ||
| TRAY_QUIT_HOOK: ${{ inputs.tray-quit-hook }} | ||
| RELEASE_VERSION: ${{ github.event.client_payload['version'] }} | ||
| FROM_VERSION: ${{ github.event.client_payload['from-version'] }} | ||
| CONSENT_HOOK: ${{ github.event.client_payload['consent-hook'] }} | ||
| TRAY_CLICK_HOOK: ${{ github.event.client_payload['tray-click-hook'] }} | ||
| TRAY_QUIT_HOOK: ${{ github.event.client_payload['tray-quit-hook'] }} | ||
| GATE_HOOKS_DIR: ${{ vars.OPENCODEX_GATE_HOOKS_DIR }} | ||
| GATE_ARTIFACTS: ${{ runner.temp }}/gate-artifacts | ||
| GATE_WORK: ${{ runner.temp }}/installed-gate | ||
|
|
@@ -143,7 +110,7 @@ jobs: | |
| - name: Download the release artifact | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| RELEASE_VERSION: ${{ inputs.version }} | ||
| RELEASE_VERSION: ${{ github.event.client_payload['version'] }} | ||
| GATE_ARTIFACTS: ${{ runner.temp }}/gate-artifacts | ||
| run: | | ||
| mkdir -p "$GATE_ARTIFACTS" | ||
|
|
@@ -154,11 +121,11 @@ jobs: | |
|
|
||
| - name: Run the installed-artifact gate | ||
| env: | ||
| RELEASE_VERSION: ${{ inputs.version }} | ||
| FROM_VERSION: ${{ inputs.from-version }} | ||
| CONSENT_HOOK: ${{ inputs.consent-hook }} | ||
| TRAY_CLICK_HOOK: ${{ inputs.tray-click-hook }} | ||
| TRAY_QUIT_HOOK: ${{ inputs.tray-quit-hook }} | ||
| RELEASE_VERSION: ${{ github.event.client_payload['version'] }} | ||
| FROM_VERSION: ${{ github.event.client_payload['from-version'] }} | ||
| CONSENT_HOOK: ${{ github.event.client_payload['consent-hook'] }} | ||
| TRAY_CLICK_HOOK: ${{ github.event.client_payload['tray-click-hook'] }} | ||
| TRAY_QUIT_HOOK: ${{ github.event.client_payload['tray-quit-hook'] }} | ||
| GATE_HOOKS_DIR: ${{ vars.OPENCODEX_GATE_HOOKS_DIR }} | ||
| GATE_ARTIFACTS: ${{ runner.temp }}/gate-artifacts | ||
| GATE_WORK: ${{ runner.temp }}/installed-gate | ||
|
|
@@ -218,8 +185,8 @@ jobs: | |
| - name: Download the release artifacts | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| RELEASE_VERSION: ${{ inputs.version }} | ||
| FROM_VERSION: ${{ inputs.from-version }} | ||
| RELEASE_VERSION: ${{ github.event.client_payload['version'] }} | ||
| FROM_VERSION: ${{ github.event.client_payload['from-version'] }} | ||
| ARTIFACT_SUFFIX: ${{ matrix.suffix }} | ||
| GATE_ARTIFACTS: ${{ runner.temp }}/gate-artifacts | ||
| run: | | ||
|
|
@@ -235,16 +202,16 @@ jobs: | |
|
|
||
| - name: Run the installed-artifact gate | ||
| env: | ||
| RELEASE_VERSION: ${{ inputs.version }} | ||
| FROM_VERSION: ${{ inputs.from-version }} | ||
| RELEASE_VERSION: ${{ github.event.client_payload['version'] }} | ||
| FROM_VERSION: ${{ github.event.client_payload['from-version'] }} | ||
| GATE_FORMAT: ${{ matrix.format }} | ||
| ARTIFACT_SUFFIX: ${{ matrix.suffix }} | ||
| CONSENT_HOOK: ${{ inputs.consent-hook }} | ||
| TRAY_CLICK_HOOK: ${{ inputs.tray-click-hook }} | ||
| TRAY_QUIT_HOOK: ${{ inputs.tray-quit-hook }} | ||
| TRAY_CHECK_HOOK: ${{ inputs.tray-check-hook }} | ||
| TRAY_INSTALL_HOOK: ${{ inputs.tray-install-hook }} | ||
| ELEVATE_ACCEPT_HOOK: ${{ inputs.elevate-accept-hook }} | ||
| CONSENT_HOOK: ${{ github.event.client_payload['consent-hook'] }} | ||
| TRAY_CLICK_HOOK: ${{ github.event.client_payload['tray-click-hook'] }} | ||
| TRAY_QUIT_HOOK: ${{ github.event.client_payload['tray-quit-hook'] }} | ||
| TRAY_CHECK_HOOK: ${{ github.event.client_payload['tray-check-hook'] }} | ||
| TRAY_INSTALL_HOOK: ${{ github.event.client_payload['tray-install-hook'] }} | ||
| ELEVATE_ACCEPT_HOOK: ${{ github.event.client_payload['elevate-accept-hook'] }} | ||
| GATE_HOOKS_DIR: ${{ vars.OPENCODEX_GATE_HOOKS_DIR }} | ||
| GATE_ARTIFACTS: ${{ runner.temp }}/gate-artifacts | ||
| GATE_WORK: ${{ runner.temp }}/installed-gate | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -463,7 +463,10 @@ describe("installed-artifact gate workflow", () => { | |
|
|
||
| test("it is dispatch-only: a stateful GUI machine must never run because a push happened", () => { | ||
| const triggers = Array.isArray(workflow.on) ? workflow.on : Object.keys(workflow.on ?? {}); | ||
| expect(triggers).toEqual(["workflow_dispatch"]); | ||
| expect(triggers).toEqual(["repository_dispatch"]); | ||
| expect((workflow.on as Record<string, { types?: string[] }>).repository_dispatch?.types).toEqual([ | ||
| "desktop-installed-gate", | ||
| ]); | ||
| }); | ||
|
|
||
| test("least privilege: read-only contents and nothing else", () => { | ||
|
|
@@ -508,8 +511,7 @@ describe("installed-artifact gate workflow", () => { | |
| }); | ||
|
|
||
| test("GUI automation inputs are hook names, never command text", () => { | ||
| const dispatch = (workflow.on as Record<string, { inputs?: Record<string, unknown> }>).workflow_dispatch; | ||
| const inputNames = Object.keys(dispatch?.inputs ?? {}); | ||
| const inputNames = [...text.matchAll(/client_payload\[['"]([^'"]+)['"]\]/g)].map(match => match[1]); | ||
| expect(inputNames).toContain("consent-hook"); | ||
| expect(inputNames).toContain("tray-quit-hook"); | ||
| expect(inputNames).toContain("elevate-accept-hook"); | ||
|
Comment on lines
+514
to
517
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔍 Payload-key test no longer verifies the dispatch contract The regex collects duplicated payload references from the workflow body, not declared inputs. It cannot detect missing required fields or distinguish accepted keys from incidental references. (Refers to this code) Was this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔍 Explicit security review remains required
This change affects a GitHub Actions security boundary targeting privileged self-hosted runners. Repository policy requires explicit security review before merge.
Was this helpful? React with 👍 or 👎 to provide feedback.