From b2fdf05aecb78b3989eba66fec3622abc92b29c4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 02:24:45 +0000 Subject: [PATCH 1/3] Initial plan From 45dfe09b8ceb3454b6eb6ad6cbbd5fd69e7545b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 02:42:08 +0000 Subject: [PATCH 2/3] feat: support expression-based github-token from auth job outputs Relax the JSON schema validation for `github-token` fields to also allow `${{ needs.JOB.outputs.OUTPUT }}` expressions in addition to the existing `${{ secrets.NAME }}` pattern. This enables secure short-lived credential flows (e.g. Octo STS or actions/create-github-app-token) without requiring a custom safe-outputs.jobs wrapper. Updated files: - pkg/parser/schemas/main_workflow_schema.json: extend github_token pattern to accept needs.*.outputs.* expressions - specs/security-architecture-spec.md: update OI-10, PM-13, PM-15 to mention job output expressions as valid token formats - pkg/workflow/github_token_validation_test.go: add integration test cases for ${{ needs.auth.outputs.token }} Agent-Logs-Url: https://github.com/github/gh-aw/sessions/9fb17f81-0912-417c-914b-268bc1098fb3 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/schemas/main_workflow_schema.json | 6 ++-- pkg/workflow/github_token_validation_test.go | 31 ++++++++++++++++++++ specs/security-architecture-spec.md | 5 ++-- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 474e46cb63c..a7e380c4393 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -9651,9 +9651,9 @@ }, "github_token": { "type": "string", - "pattern": "^\\$\\{\\{\\s*secrets\\.[A-Za-z_][A-Za-z0-9_]*(\\s*\\|\\|\\s*secrets\\.[A-Za-z_][A-Za-z0-9_]*)*\\s*\\}\\}$", - "description": "GitHub token expression using secrets. Pattern details: `[A-Za-z_][A-Za-z0-9_]*` matches a valid secret name (starts with a letter or underscore, followed by letters, digits, or underscores). The full pattern matches expressions like `${{ secrets.NAME }}` or `${{ secrets.NAME1 || secrets.NAME2 }}`.", - "examples": ["${{ secrets.GITHUB_TOKEN }}", "${{ secrets.CUSTOM_PAT }}", "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"] + "pattern": "^\\$\\{\\{\\s*(secrets\\.[A-Za-z_][A-Za-z0-9_]*(\\s*\\|\\|\\s*secrets\\.[A-Za-z_][A-Za-z0-9_]*)*|needs\\.[A-Za-z_][A-Za-z0-9_-]*\\.outputs\\.[A-Za-z_][A-Za-z0-9_-]*)\\s*\\}\\}$", + "description": "GitHub token expression. Accepts a secrets expression (e.g., `${{ secrets.NAME }}` or `${{ secrets.NAME1 || secrets.NAME2 }}`) or a job output expression (e.g., `${{ needs.auth.outputs.token }}`). Pattern details: secret names match `[A-Za-z_][A-Za-z0-9_]*`; job IDs and output names match `[A-Za-z_][A-Za-z0-9_-]*`.", + "examples": ["${{ secrets.GITHUB_TOKEN }}", "${{ secrets.CUSTOM_PAT }}", "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}", "${{ needs.auth.outputs.token }}"] }, "github_app": { "type": "object", diff --git a/pkg/workflow/github_token_validation_test.go b/pkg/workflow/github_token_validation_test.go index 40bbf48b074..d9d7aaa610c 100644 --- a/pkg/workflow/github_token_validation_test.go +++ b/pkg/workflow/github_token_validation_test.go @@ -54,6 +54,22 @@ func TestGitHubTokenValidation(t *testing.T) { token: "${{ secrets.TOKEN1 || secrets.TOKEN2 }}", expectError: false, }, + // Valid cases - job output expressions + { + name: "valid job output expression - needs.auth.outputs.token", + token: "${{ needs.auth.outputs.token }}", + expectError: false, + }, + { + name: "valid job output expression - hyphenated job name", + token: "${{ needs.mint-token.outputs.access_token }}", + expectError: false, + }, + { + name: "valid job output expression - with spaces", + token: "${{ needs.auth.outputs.token }}", + expectError: false, + }, // Invalid cases - plaintext secrets { name: "invalid - plaintext GitHub PAT", @@ -158,6 +174,11 @@ func TestGitHubTokenValidationInSafeOutputs(t *testing.T) { token: "${{ secrets.SAFE_OUTPUTS_PAT }}", expectError: false, }, + { + name: "valid job output token in safe-outputs", + token: "${{ needs.auth.outputs.token }}", + expectError: false, + }, { name: "invalid token in safe-outputs", token: "ghp_plaintext_token", @@ -215,6 +236,11 @@ func TestGitHubTokenValidationInIndividualSafeOutput(t *testing.T) { token: "${{ secrets.INDIVIDUAL_PAT }}", expectError: false, }, + { + name: "valid job output token in individual safe-output", + token: "${{ needs.auth.outputs.token }}", + expectError: false, + }, { name: "invalid token in individual safe-output", token: "github_pat_plaintext", @@ -272,6 +298,11 @@ func TestGitHubTokenValidationInGitHubTool(t *testing.T) { token: "${{ secrets.GITHUB_TOOL_PAT }}", expectError: false, }, + { + name: "valid job output token in github tool", + token: "${{ needs.auth.outputs.token }}", + expectError: false, + }, { name: "invalid token in github tool", token: "plaintext_secret", diff --git a/specs/security-architecture-spec.md b/specs/security-architecture-spec.md index a043bf5599b..faa3ef8fdbd 100644 --- a/specs/security-architecture-spec.md +++ b/specs/security-architecture-spec.md @@ -437,7 +437,7 @@ The output isolation layer enforces separation between AI agent operations (read 3. Workflow-level token 4. Default token -**OI-10**: Tokens MUST be GitHub Actions secret expressions (e.g., `${{ secrets.TOKEN_NAME }}`). Plaintext tokens MUST cause compilation failure. +**OI-10**: Tokens MUST be GitHub Actions expressions referencing secrets or job outputs (e.g., `${{ secrets.TOKEN_NAME }}` or `${{ needs.auth.outputs.token }}`). Plaintext tokens MUST cause compilation failure. ### 5.6 Output Isolation Guarantees @@ -621,13 +621,14 @@ roles: all # Least restrictive ### 7.7 Token Validation -**PM-13**: The implementation MUST validate that `github-token` fields contain GitHub Actions secret expressions. +**PM-13**: The implementation MUST validate that `github-token` fields contain GitHub Actions expressions referencing secrets or job outputs. **PM-14**: Plaintext tokens or environment variables MUST cause compilation failure. **PM-15**: Valid token formats: - `${{ secrets.TOKEN_NAME }}` - `${{ secrets.ORG_TOKEN || secrets.FALLBACK_TOKEN }}` +- `${{ needs.auth.outputs.token }}` --- From d31b3ab1d8495af99819db0205d45225b9c5f6e7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Apr 2026 04:41:44 +0000 Subject: [PATCH 3/3] fix: restrict needs.*.outputs.* pattern to identifier-only names (no hyphens in dot notation) Job IDs and output names with hyphens cannot be reliably accessed via dot notation in GitHub Actions expressions (they require bracket syntax like `needs['job-id'].outputs.name`). Restrict the github_token pattern to `[A-Za-z_][A-Za-z0-9_]*` for both job IDs and output names to avoid accepting expressions that would fail at runtime. Remove the hyphenated job ID integration test case that would have validated an expression that fails at Actions evaluation time. Agent-Logs-Url: https://github.com/github/gh-aw/sessions/b92b7d32-d754-414f-8528-cb10edf68454 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/schemas/main_workflow_schema.json | 4 ++-- pkg/workflow/github_token_validation_test.go | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index a7e380c4393..c4bd8f3af60 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -9651,8 +9651,8 @@ }, "github_token": { "type": "string", - "pattern": "^\\$\\{\\{\\s*(secrets\\.[A-Za-z_][A-Za-z0-9_]*(\\s*\\|\\|\\s*secrets\\.[A-Za-z_][A-Za-z0-9_]*)*|needs\\.[A-Za-z_][A-Za-z0-9_-]*\\.outputs\\.[A-Za-z_][A-Za-z0-9_-]*)\\s*\\}\\}$", - "description": "GitHub token expression. Accepts a secrets expression (e.g., `${{ secrets.NAME }}` or `${{ secrets.NAME1 || secrets.NAME2 }}`) or a job output expression (e.g., `${{ needs.auth.outputs.token }}`). Pattern details: secret names match `[A-Za-z_][A-Za-z0-9_]*`; job IDs and output names match `[A-Za-z_][A-Za-z0-9_-]*`.", + "pattern": "^\\$\\{\\{\\s*(secrets\\.[A-Za-z_][A-Za-z0-9_]*(\\s*\\|\\|\\s*secrets\\.[A-Za-z_][A-Za-z0-9_]*)*|needs\\.[A-Za-z_][A-Za-z0-9_]*\\.outputs\\.[A-Za-z_][A-Za-z0-9_]*)\\s*\\}\\}$", + "description": "GitHub token expression. Accepts a secrets expression (e.g., `${{ secrets.NAME }}` or `${{ secrets.NAME1 || secrets.NAME2 }}`) or a job output expression (e.g., `${{ needs.auth.outputs.token }}`). Pattern details: secret names match `[A-Za-z_][A-Za-z0-9_]*`; job IDs and output names in dot notation match `[A-Za-z_][A-Za-z0-9_]*` (identifiers without hyphens).", "examples": ["${{ secrets.GITHUB_TOKEN }}", "${{ secrets.CUSTOM_PAT }}", "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}", "${{ needs.auth.outputs.token }}"] }, "github_app": { diff --git a/pkg/workflow/github_token_validation_test.go b/pkg/workflow/github_token_validation_test.go index d9d7aaa610c..63ff6c9bc47 100644 --- a/pkg/workflow/github_token_validation_test.go +++ b/pkg/workflow/github_token_validation_test.go @@ -60,11 +60,6 @@ func TestGitHubTokenValidation(t *testing.T) { token: "${{ needs.auth.outputs.token }}", expectError: false, }, - { - name: "valid job output expression - hyphenated job name", - token: "${{ needs.mint-token.outputs.access_token }}", - expectError: false, - }, { name: "valid job output expression - with spaces", token: "${{ needs.auth.outputs.token }}",