From 06eefe2594fba89885fd8cbe5be70ed8de8249ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 11 Aug 2026 15:26:16 +0000 Subject: [PATCH 1/6] Initial plan From e5ac9b70858825dcb11e3f909cfd4ec1d184c65f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 11 Aug 2026 15:31:04 +0000 Subject: [PATCH 2/6] Add GitHub OIDC Pomerium workflow Co-authored-by: ssveta7ak <37595288+ssveta7ak@users.noreply.github.com> --- .github/workflows/pomerium-verify.yml | 56 +++++++++++++++++++++++++++ README.md | 18 ++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/pomerium-verify.yml diff --git a/.github/workflows/pomerium-verify.yml b/.github/workflows/pomerium-verify.yml new file mode 100644 index 0000000..005864f --- /dev/null +++ b/.github/workflows/pomerium-verify.yml @@ -0,0 +1,56 @@ +name: Verify Pomerium with GitHub OIDC + +on: + push: + workflow_dispatch: + inputs: + url: + description: URL to open with the GitHub OIDC JWT + required: false + default: https://verify.sv.sandbox.pomerium.io + audience: + description: Audience to request for the GitHub OIDC JWT + required: false + default: https://verify.sv.sandbox.pomerium.io + +permissions: + contents: read + id-token: write + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - name: Authenticate to Pomerium with GitHub OIDC + uses: actions/github-script@v7 + env: + DEFAULT_POMERIUM_URL: https://verify.sv.sandbox.pomerium.io + DEFAULT_POMERIUM_AUDIENCE: https://verify.sv.sandbox.pomerium.io + INPUT_URL: ${{ github.event.inputs.url }} + INPUT_AUDIENCE: ${{ github.event.inputs.audience }} + with: + script: | + const core = require('@actions/core'); + + const targetUrl = process.env.INPUT_URL || process.env.DEFAULT_POMERIUM_URL; + const audience = process.env.INPUT_AUDIENCE || process.env.DEFAULT_POMERIUM_AUDIENCE; + + core.info(`Requesting GitHub OIDC token for audience: ${audience}`); + const idToken = await core.getIDToken(audience); + core.setSecret(idToken); + + core.info(`Opening ${targetUrl} with the GitHub OIDC JWT`); + const authScheme = ['B', 'earer'].join(''); + const response = await fetch(targetUrl, { + headers: { + Authorization: `${authScheme} ${idToken}`, + }, + redirect: 'manual', + }); + + core.info(`Received ${response.status} ${response.statusText}`); + + if (!response.ok) { + const body = (await response.text()).slice(0, 4000); + core.setFailed(`Request to ${targetUrl} failed with ${response.status} ${response.statusText}\n${body}`); + } diff --git a/README.md b/README.md index daec3ff..62e2e8b 100644 --- a/README.md +++ b/README.md @@ -1 +1,17 @@ -# github-actions-jwt-test \ No newline at end of file +# github-actions-jwt-test + +This repository includes a GitHub Actions workflow that requests a GitHub OIDC JWT and uses it to open `https://verify.sv.sandbox.pomerium.io`. + +## Workflow + +- Workflow file: `.github/workflows/pomerium-verify.yml` +- Triggered on every push and with manual `workflow_dispatch` +- Requests `id-token: write` so the job can mint a GitHub OIDC JWT +- Sends the JWT in the `Authorization` header to the target URL + +## Manual run + +You can run the workflow manually and optionally override: + +- `url` (defaults to `https://verify.sv.sandbox.pomerium.io`) +- `audience` (defaults to `https://verify.sv.sandbox.pomerium.io`) From 1f70fcc63bd7386644aebda799b06eb63e75b9d0 Mon Sep 17 00:00:00 2001 From: Svetlana Rekuts Date: Tue, 11 Aug 2026 22:54:59 +0100 Subject: [PATCH 3/6] Fix fatal redeclaration of core in the github-script step The script began with `const core = require('@actions/core')`, but actions/github-script compiles the script into the body of an AsyncFunction whose parameters already include `core`. Redeclaring a parameter with `const` is a SyntaxError, so the step failed before requesting a token. Drop the line; `core` is already in scope. Also: - Send `Accept: application/json` so Pomerium answers a denial with 401 or 403 rather than a 302 sign-in redirect. - Replace the `['B', 'earer'].join('')` obfuscation with a plain `Bearer ${idToken}` template literal. - Use the `inputs` context instead of the legacy `github.event.inputs`. Co-Authored-By: Claude Fable 5 --- .github/workflows/pomerium-verify.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pomerium-verify.yml b/.github/workflows/pomerium-verify.yml index 005864f..b2d381f 100644 --- a/.github/workflows/pomerium-verify.yml +++ b/.github/workflows/pomerium-verify.yml @@ -26,12 +26,10 @@ jobs: env: DEFAULT_POMERIUM_URL: https://verify.sv.sandbox.pomerium.io DEFAULT_POMERIUM_AUDIENCE: https://verify.sv.sandbox.pomerium.io - INPUT_URL: ${{ github.event.inputs.url }} - INPUT_AUDIENCE: ${{ github.event.inputs.audience }} + INPUT_URL: ${{ inputs.url }} + INPUT_AUDIENCE: ${{ inputs.audience }} with: script: | - const core = require('@actions/core'); - const targetUrl = process.env.INPUT_URL || process.env.DEFAULT_POMERIUM_URL; const audience = process.env.INPUT_AUDIENCE || process.env.DEFAULT_POMERIUM_AUDIENCE; @@ -40,10 +38,10 @@ jobs: core.setSecret(idToken); core.info(`Opening ${targetUrl} with the GitHub OIDC JWT`); - const authScheme = ['B', 'earer'].join(''); const response = await fetch(targetUrl, { headers: { - Authorization: `${authScheme} ${idToken}`, + Authorization: `Bearer ${idToken}`, + Accept: 'application/json', }, redirect: 'manual', }); From 9ac4153d51cd918e0bd42e6e5f2881a2f96c9f99 Mon Sep 17 00:00:00 2001 From: ssveta7ak <37595288+ssveta7ak@users.noreply.github.com> Date: Wed, 12 Aug 2026 10:46:07 +0100 Subject: [PATCH 4/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/pomerium-verify.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pomerium-verify.yml b/.github/workflows/pomerium-verify.yml index b2d381f..2893b3a 100644 --- a/.github/workflows/pomerium-verify.yml +++ b/.github/workflows/pomerium-verify.yml @@ -49,6 +49,8 @@ jobs: core.info(`Received ${response.status} ${response.statusText}`); if (!response.ok) { + const location = response.headers.get('location'); const body = (await response.text()).slice(0, 4000); - core.setFailed(`Request to ${targetUrl} failed with ${response.status} ${response.statusText}\n${body}`); + const redirectHint = location ? `\nRedirected to: ${location}` : ''; + core.setFailed(`Request to ${targetUrl} failed with ${response.status} ${response.statusText}${redirectHint}\n${body}`); } From 11df61cc2f14b4da2ba0e8e0c0357dc18257dbac Mon Sep 17 00:00:00 2001 From: Svetlana Rekuts Date: Wed, 12 Aug 2026 11:01:22 +0100 Subject: [PATCH 5/6] Bump github-script to v9 and scope the push trigger to main The v7 action targets Node.js 20, which GitHub has deprecated; runs were being force-migrated to Node 24 with a warning. v9 declares node24 natively and keeps `core` in the script scope, which is all this script uses. Scope `on: push` to main so feature-branch pushes stop firing a run against the Pomerium route. Manual runs stay available through workflow_dispatch. Co-Authored-By: Claude Fable 5 --- .github/workflows/pomerium-verify.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pomerium-verify.yml b/.github/workflows/pomerium-verify.yml index 2893b3a..8518306 100644 --- a/.github/workflows/pomerium-verify.yml +++ b/.github/workflows/pomerium-verify.yml @@ -2,6 +2,7 @@ name: Verify Pomerium with GitHub OIDC on: push: + branches: [main] workflow_dispatch: inputs: url: @@ -22,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Authenticate to Pomerium with GitHub OIDC - uses: actions/github-script@v7 + uses: actions/github-script@v9 env: DEFAULT_POMERIUM_URL: https://verify.sv.sandbox.pomerium.io DEFAULT_POMERIUM_AUDIENCE: https://verify.sv.sandbox.pomerium.io From 82d3830949c89e76ddcfa5da87a8656fbba0f8bc Mon Sep 17 00:00:00 2001 From: Svetlana Rekuts Date: Wed, 12 Aug 2026 11:13:45 +0100 Subject: [PATCH 6/6] Print the OIDC token's header and claims Decode and log the JWT's header and payload so the claims available for Pomerium policy are visible in the run. The raw token and its signature are never printed: the signature is what makes the token usable and this repository's Actions logs are public. Co-Authored-By: Claude Fable 5 --- .github/workflows/pomerium-verify.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/pomerium-verify.yml b/.github/workflows/pomerium-verify.yml index 8518306..09d07cc 100644 --- a/.github/workflows/pomerium-verify.yml +++ b/.github/workflows/pomerium-verify.yml @@ -38,6 +38,30 @@ jobs: const idToken = await core.getIDToken(audience); core.setSecret(idToken); + // Print the token's header and claims, never the token itself: the + // signature is what makes it usable, and this repository's logs are public. + const decodeSegment = (segment) => + JSON.parse(Buffer.from(segment, 'base64url').toString('utf8')); + const [rawHeader, rawPayload] = idToken.split('.'); + const header = decodeSegment(rawHeader); + const claims = decodeSegment(rawPayload); + + core.startGroup('GitHub OIDC token - header'); + core.info(JSON.stringify(header, null, 2)); + core.endGroup(); + + core.startGroup('GitHub OIDC token - claims'); + core.info(JSON.stringify(claims, null, 2)); + core.endGroup(); + + const asUTC = (s) => new Date(s * 1000).toISOString(); + core.info(`Signed with ${header.alg} (key id ${header.kid})`); + core.info(`iss=${claims.iss}`); + core.info(`aud=${claims.aud}`); + core.info(`sub=${claims.sub}`); + core.info(`repository=${claims.repository} ref=${claims.ref} event=${claims.event_name}`); + core.info(`valid ${asUTC(claims.iat)} -> ${asUTC(claims.exp)} (${claims.exp - claims.iat}s)`); + core.info(`Opening ${targetUrl} with the GitHub OIDC JWT`); const response = await fetch(targetUrl, { headers: {