diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 62d5ac4d202..17af615ce61 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -9657,9 +9657,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 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": { "type": "object", diff --git a/pkg/workflow/github_token_validation_test.go b/pkg/workflow/github_token_validation_test.go index 40bbf48b074..63ff6c9bc47 100644 --- a/pkg/workflow/github_token_validation_test.go +++ b/pkg/workflow/github_token_validation_test.go @@ -54,6 +54,17 @@ 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 - with spaces", + token: "${{ needs.auth.outputs.token }}", + expectError: false, + }, // Invalid cases - plaintext secrets { name: "invalid - plaintext GitHub PAT", @@ -158,6 +169,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 +231,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 +293,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 }}` ---