From 03bb4d6859e17e45856624c2ad47160a5fd57a2a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 25 Oct 2025 20:00:10 +0000 Subject: [PATCH 1/6] Initial plan From 8a6d8bf9a522185f17ee2485368a0911466707af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 25 Oct 2025 20:12:50 +0000 Subject: [PATCH 2/6] Add github-token schema validation with pattern matching - Added JSON schema pattern validation for all github-token fields - Created reusable $defs/github_token definition - Pattern enforces secret expressions: ${{ secrets.* }} - Supports fallback syntax: ${{ secrets.A || secrets.B }} - Added comprehensive test suite (26 test cases) - Tests cover valid and invalid tokens in all contexts - All existing tests continue to pass Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/schemas/main_workflow_schema.json | 95 +++-- pkg/workflow/github_token_validation_test.go | 409 +++++++++++++++++++ 2 files changed, 477 insertions(+), 27 deletions(-) create mode 100644 pkg/workflow/github_token_validation_test.go diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 5747d61c67d..a7699f5f69e 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -58,7 +58,16 @@ { "type": "string", "description": "Single event name or '*' for all events. Use GitHub Actions event names: 'issues', 'issue_comment', 'pull_request_comment', 'pull_request', 'pull_request_review_comment', 'discussion', 'discussion_comment'.", - "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"] + "enum": [ + "*", + "issues", + "issue_comment", + "pull_request_comment", + "pull_request", + "pull_request_review_comment", + "discussion", + "discussion_comment" + ] }, { "type": "array", @@ -66,7 +75,16 @@ "items": { "type": "string", "description": "GitHub Actions event name.", - "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"] + "enum": [ + "*", + "issues", + "issue_comment", + "pull_request_comment", + "pull_request", + "pull_request_review_comment", + "discussion", + "discussion_comment" + ] } } ] @@ -1279,11 +1297,13 @@ "description": "The name of the environment configured in the repo" }, "url": { - "type": "string", + "type": "string", "description": "A deployment URL" } }, - "required": ["name"], + "required": [ + "name" + ], "additionalProperties": false } ] @@ -1349,7 +1369,9 @@ "description": "Additional Docker container options" } }, - "required": ["image"], + "required": [ + "image" + ], "additionalProperties": false } ] @@ -1417,7 +1439,9 @@ "description": "Additional Docker container options" } }, - "required": ["image"], + "required": [ + "image" + ], "additionalProperties": false } ] @@ -1458,7 +1482,9 @@ }, { "type": "string", - "enum": ["disable"], + "enum": [ + "disable" + ], "description": "Disable AWF firewall (triggers warning if allowed != *, error in strict mode if allowed is not * or engine does not support firewall)" }, { @@ -1585,7 +1611,10 @@ }, "mode": { "type": "string", - "enum": ["local", "remote"], + "enum": [ + "local", + "remote" + ], "description": "MCP server mode: 'local' (Docker-based, default) or 'remote' (hosted at api.githubcopilot.com)" }, "version": { @@ -1604,7 +1633,7 @@ "description": "Enable read-only mode to restrict GitHub MCP server to read-only operations only" }, "github-token": { - "type": "string", + "$ref": "#/$defs/github_token", "description": "Optional custom GitHub token (e.g., '${{ secrets.CUSTOM_PAT }}'). For 'remote' type, defaults to GH_AW_GITHUB_TOKEN if not specified." }, "toolset": { @@ -2033,7 +2062,7 @@ "description": "Target repository in format 'owner/repo' for cross-repository issue creation. Takes precedence over trial target repo settings." }, "github-token": { - "type": "string", + "$ref": "#/$defs/github_token", "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." } }, @@ -2072,7 +2101,7 @@ "description": "Target repository in format 'owner/repo' for cross-repository agent task creation. Takes precedence over trial target repo settings." }, "github-token": { - "type": "string", + "$ref": "#/$defs/github_token", "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." } }, @@ -2124,7 +2153,7 @@ "description": "Target repository in format 'owner/repo' for cross-repository discussion creation. Takes precedence over trial target repo settings." }, "github-token": { - "type": "string", + "$ref": "#/$defs/github_token", "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." } }, @@ -2163,7 +2192,7 @@ "description": "Target repository in format 'owner/repo' for cross-repository comments. Takes precedence over trial target repo settings." }, "github-token": { - "type": "string", + "$ref": "#/$defs/github_token", "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." }, "discussion": { @@ -2231,7 +2260,7 @@ "description": "Target repository in format 'owner/repo' for cross-repository pull request creation. Takes precedence over trial target repo settings." }, "github-token": { - "type": "string", + "$ref": "#/$defs/github_token", "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." } }, @@ -2278,7 +2307,7 @@ "description": "Target repository in format 'owner/repo' for cross-repository PR review comments. Takes precedence over trial target repo settings." }, "github-token": { - "type": "string", + "$ref": "#/$defs/github_token", "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." } }, @@ -2312,7 +2341,7 @@ "description": "Driver name for SARIF tool.driver.name field (default: 'GitHub Agentic Workflows Security Scanner')" }, "github-token": { - "type": "string", + "$ref": "#/$defs/github_token", "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." } }, @@ -2361,7 +2390,7 @@ "description": "Target repository in format 'owner/repo' for cross-repository label addition. Takes precedence over trial target repo settings." }, "github-token": { - "type": "string", + "$ref": "#/$defs/github_token", "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." } }, @@ -2408,7 +2437,7 @@ "description": "Target repository in format 'owner/repo' for cross-repository issue updates. Takes precedence over trial target repo settings." }, "github-token": { - "type": "string", + "$ref": "#/$defs/github_token", "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." } }, @@ -2463,7 +2492,7 @@ "description": "Optional suffix to append to generated commit titles (e.g., ' [skip ci]' to prevent triggering CI on the commit)" }, "github-token": { - "type": "string", + "$ref": "#/$defs/github_token", "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." } }, @@ -2489,7 +2518,7 @@ "maximum": 100 }, "github-token": { - "type": "string", + "$ref": "#/$defs/github_token", "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." } }, @@ -2545,7 +2574,7 @@ "maximum": 100 }, "github-token": { - "type": "string", + "$ref": "#/$defs/github_token", "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." } }, @@ -2573,7 +2602,7 @@ "additionalProperties": false }, "github-token": { - "type": "string", + "$ref": "#/$defs/github_token", "description": "GitHub token to use for safe output jobs. Typically a secret reference like ${{ secrets.GITHUB_TOKEN }} or ${{ secrets.CUSTOM_PAT }}" }, "max-patch-size": { @@ -2672,7 +2701,7 @@ "$ref": "#/properties/permissions" }, "github-token": { - "type": "string", + "$ref": "#/$defs/github_token", "description": "GitHub token for this specific job" }, "output": { @@ -2744,14 +2773,14 @@ "additionalProperties": false }, "roles": { - "description": "Repository access roles required to trigger agentic workflows. Defaults to ['admin', 'maintainer', 'write'] for security. Use 'all' to allow any authenticated user (⚠️ security consideration).", + "description": "Repository access roles required to trigger agentic workflows. Defaults to ['admin', 'maintainer', 'write'] for security. Use 'all' to allow any authenticated user (\u26a0\ufe0f security consideration).", "oneOf": [ { "type": "string", "enum": [ "all" ], - "description": "Allow any authenticated user to trigger the workflow (⚠️ disables permission checking entirely - use with caution)" + "description": "Allow any authenticated user to trigger the workflow (\u26a0\ufe0f disables permission checking entirely - use with caution)" }, { "type": "array", @@ -2880,7 +2909,7 @@ "additionalProperties": false }, "github-token": { - "type": "string", + "$ref": "#/$defs/github_token", "description": "GitHub token expression to use for all steps that require GitHub authentication. Typically a secret reference like ${{ secrets.GITHUB_TOKEN }} or ${{ secrets.CUSTOM_PAT }}. If not specified, defaults to ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}. This value can be overridden by safe-outputs github-token or individual safe-output github-token fields." } }, @@ -2943,7 +2972,9 @@ "description": "Whether to cancel in-progress runs of the same concurrency group. Defaults to false for agentic workflow runs." } }, - "required": ["group"], + "required": [ + "group" + ], "additionalProperties": false } ], @@ -3190,6 +3221,16 @@ "url" ], "additionalProperties": false + }, + "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 (e.g., ${{ secrets.GITHUB_TOKEN }} or ${{ secrets.CUSTOM_PAT }})", + "examples": [ + "${{ secrets.GITHUB_TOKEN }}", + "${{ secrets.CUSTOM_PAT }}", + "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}" + ] } } } diff --git a/pkg/workflow/github_token_validation_test.go b/pkg/workflow/github_token_validation_test.go new file mode 100644 index 00000000000..9ec4537aa01 --- /dev/null +++ b/pkg/workflow/github_token_validation_test.go @@ -0,0 +1,409 @@ +//go:build !integration + +package workflow + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestGitHubTokenValidation(t *testing.T) { + tests := []struct { + name string + token string + expectError bool + errorMsg string + }{ + // Valid cases + { + name: "valid secret expression - GITHUB_TOKEN", + token: "${{ secrets.GITHUB_TOKEN }}", + expectError: false, + }, + { + name: "valid secret expression - custom PAT", + token: "${{ secrets.CUSTOM_PAT }}", + expectError: false, + }, + { + name: "valid secret expression - with fallback", + token: "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}", + expectError: false, + }, + { + name: "valid secret expression - with spaces", + token: "${{ secrets.MY_TOKEN }}", + expectError: false, + }, + { + name: "valid secret expression - underscore prefix", + token: "${{ secrets._PRIVATE_TOKEN }}", + expectError: false, + }, + { + name: "valid secret expression - numbers in name", + token: "${{ secrets.TOKEN_V2 }}", + expectError: false, + }, + { + name: "valid secret expression - multiple fallbacks", + token: "${{ secrets.TOKEN1 || secrets.TOKEN2 }}", + expectError: false, + }, + // Invalid cases - plaintext secrets + { + name: "invalid - plaintext GitHub PAT", + token: "ghp_1234567890abcdefghijklmnopqrstuvwxyz", + expectError: true, + errorMsg: "github-token", + }, + { + name: "invalid - plaintext classic token", + token: "github_pat_11AAAAAA", + expectError: true, + errorMsg: "github-token", + }, + { + name: "invalid - plaintext string", + token: "my-secret-token", + expectError: true, + errorMsg: "github-token", + }, + { + name: "invalid - empty string", + token: "", + expectError: true, + errorMsg: "github-token", + }, + { + name: "invalid - partial expression without secrets", + token: "${{ env.GITHUB_TOKEN }}", + expectError: true, + errorMsg: "github-token", + }, + { + name: "invalid - missing closing braces", + token: "${{ secrets.GITHUB_TOKEN", + expectError: true, + errorMsg: "github-token", + }, + { + name: "invalid - missing opening braces", + token: "secrets.GITHUB_TOKEN }}", + expectError: true, + errorMsg: "github-token", + }, + { + name: "invalid - just the word secrets", + token: "secrets.GITHUB_TOKEN", + expectError: true, + errorMsg: "github-token", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "github-token-validation-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + testContent := `--- +name: Test GitHub Token Validation +on: + workflow_dispatch: +engine: copilot +github-token: ` + tt.token + ` +tools: + github: + allowed: [list_issues] +--- + +# Test GitHub Token Validation +` + + testFile := filepath.Join(tmpDir, "test-token.md") + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatal(err) + } + + compiler := NewCompiler(false, "", "test") + err = compiler.CompileWorkflow(testFile) + + if tt.expectError { + if err == nil { + t.Errorf("Expected error for token %q, but got none", tt.token) + } else if !strings.Contains(err.Error(), tt.errorMsg) { + t.Errorf("Expected error to contain %q, got: %v", tt.errorMsg, err) + } + } else { + if err != nil { + t.Errorf("Expected no error for token %q, but got: %v", tt.token, err) + } + } + }) + } +} + +func TestGitHubTokenValidationInSafeOutputs(t *testing.T) { + tests := []struct { + name string + token string + expectError bool + }{ + { + name: "valid token in safe-outputs", + token: "${{ secrets.SAFE_OUTPUTS_PAT }}", + expectError: false, + }, + { + name: "invalid token in safe-outputs", + token: "ghp_plaintext_token", + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "safe-outputs-token-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + testContent := `--- +name: Test Safe-Outputs Token Validation +on: + issues: + types: [opened] +engine: copilot +safe-outputs: + github-token: ` + tt.token + ` + create-issue: +--- + +# Test Safe-Outputs Token +` + + testFile := filepath.Join(tmpDir, "test-safe-outputs.md") + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatal(err) + } + + compiler := NewCompiler(false, "", "test") + err = compiler.CompileWorkflow(testFile) + + if tt.expectError { + if err == nil { + t.Errorf("Expected error for token %q, but got none", tt.token) + } + } else { + if err != nil { + t.Errorf("Expected no error for token %q, but got: %v", tt.token, err) + } + } + }) + } +} + +func TestGitHubTokenValidationInIndividualSafeOutput(t *testing.T) { + tests := []struct { + name string + token string + expectError bool + }{ + { + name: "valid token in individual safe-output", + token: "${{ secrets.INDIVIDUAL_PAT }}", + expectError: false, + }, + { + name: "invalid token in individual safe-output", + token: "github_pat_plaintext", + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "individual-token-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + testContent := `--- +name: Test Individual Safe-Output Token +on: + issues: + types: [opened] +engine: copilot +safe-outputs: + create-issue: + github-token: ` + tt.token + ` +--- + +# Test Individual Token +` + + testFile := filepath.Join(tmpDir, "test-individual.md") + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatal(err) + } + + compiler := NewCompiler(false, "", "test") + err = compiler.CompileWorkflow(testFile) + + if tt.expectError { + if err == nil { + t.Errorf("Expected error for token %q, but got none", tt.token) + } + } else { + if err != nil { + t.Errorf("Expected no error for token %q, but got: %v", tt.token, err) + } + } + }) + } +} + +func TestGitHubTokenValidationInGitHubTool(t *testing.T) { + tests := []struct { + name string + token string + expectError bool + }{ + { + name: "valid token in github tool", + token: "${{ secrets.GITHUB_TOOL_PAT }}", + expectError: false, + }, + { + name: "invalid token in github tool", + token: "plaintext_secret", + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "github-tool-token-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + testContent := `--- +name: Test GitHub Tool Token +on: + workflow_dispatch: +engine: copilot +tools: + github: + github-token: ` + tt.token + ` + allowed: [list_issues] +--- + +# Test GitHub Tool Token +` + + testFile := filepath.Join(tmpDir, "test-github-tool.md") + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatal(err) + } + + compiler := NewCompiler(false, "", "test") + err = compiler.CompileWorkflow(testFile) + + if tt.expectError { + if err == nil { + t.Errorf("Expected error for token %q, but got none", tt.token) + } + } else { + if err != nil { + t.Errorf("Expected no error for token %q, but got: %v", tt.token, err) + } + } + }) + } +} + +func TestGitHubTokenValidationErrorMessage(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "error-message-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + testContent := `--- +name: Test Error Message +on: + workflow_dispatch: +engine: copilot +github-token: ghp_actualSecretInPlainText +--- + +# Test Error Message +` + + testFile := filepath.Join(tmpDir, "test-error.md") + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatal(err) + } + + compiler := NewCompiler(false, "", "test") + err = compiler.CompileWorkflow(testFile) + + if err == nil { + t.Fatal("Expected validation error, got none") + } + + // The error should be clear and helpful + errorMsg := err.Error() + if !strings.Contains(errorMsg, "github-token") { + t.Errorf("Error message should mention 'github-token', got: %s", errorMsg) + } +} + +func TestMultipleGitHubTokenValidations(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "multiple-tokens-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + // Test that validation catches errors in any of the token locations + testContent := `--- +name: Test Multiple Tokens +on: + workflow_dispatch: +engine: copilot +github-token: ${{ secrets.TOPLEVEL_TOKEN }} +tools: + github: + github-token: plaintext_token_in_github_tool + allowed: [list_issues] +safe-outputs: + create-issue: + github-token: ${{ secrets.SAFE_OUTPUT_TOKEN }} +--- + +# Test Multiple Tokens +` + + testFile := filepath.Join(tmpDir, "test-multiple.md") + if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { + t.Fatal(err) + } + + compiler := NewCompiler(false, "", "test") + err = compiler.CompileWorkflow(testFile) + + // Should fail due to plaintext token in github tool + if err == nil { + t.Fatal("Expected validation error for invalid github tool token, got none") + } +} From 003d9bb68136d27ff873d03b17a27ede2f7136ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 25 Oct 2025 20:15:25 +0000 Subject: [PATCH 3/6] Add documentation for github-token validation - Added caution callout in frontmatter reference - Added security tip in security guide - Documents valid and invalid token formats - Explains automatic validation during compilation Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- docs/src/content/docs/guides/security.md | 6 ++++++ docs/src/content/docs/reference/frontmatter.md | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/docs/src/content/docs/guides/security.md b/docs/src/content/docs/guides/security.md index 57c05f8cacb..fa32555f2cf 100644 --- a/docs/src/content/docs/guides/security.md +++ b/docs/src/content/docs/guides/security.md @@ -249,6 +249,12 @@ Workflows use a hierarchical token precedence system that allows you to configur This allows you to set a default token for the entire workflow while still allowing specific safe-outputs to use different tokens when needed. +:::tip[Automatic Secret Validation] +All `github-token` fields are automatically validated during compilation to ensure they use GitHub Actions secret expressions (e.g., `${{ secrets.CUSTOM_PAT }}`). This prevents accidental plaintext secret leakage in workflow files. + +If you specify a plaintext token like `ghp_1234...` or use an environment variable like `${{ env.MY_TOKEN }}`, compilation will fail with a clear error message. +::: + #### Token Configuration Examples **Top-level token for entire workflow:** diff --git a/docs/src/content/docs/reference/frontmatter.md b/docs/src/content/docs/reference/frontmatter.md index adcd354d073..d5346e7ecff 100644 --- a/docs/src/content/docs/reference/frontmatter.md +++ b/docs/src/content/docs/reference/frontmatter.md @@ -68,6 +68,20 @@ The `github-token:` field configures the default GitHub token for the entire wor github-token: ${{ secrets.CUSTOM_PAT }} ``` +:::caution[Secret Expression Required] +The `github-token` field **must** use a GitHub Actions secret expression (e.g., `${{ secrets.CUSTOM_PAT }}`). Plaintext tokens are rejected during compilation to prevent accidental secret leakage. + +**Valid formats:** +- `${{ secrets.GITHUB_TOKEN }}` +- `${{ secrets.CUSTOM_PAT }}` +- `${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}` + +**Invalid formats:** +- `ghp_1234567890...` (plaintext token) +- `${{ env.MY_TOKEN }}` (environment variable) +- `my-secret-token` (plaintext string) +::: + The token precedence hierarchy allows fine-grained control: 1. **Individual safe-output `github-token`** (highest priority) - e.g., `create-issue.github-token` From 904c78d365c99dbb73be247c08bedcc2a5529957 Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Sat, 25 Oct 2025 13:20:27 -0700 Subject: [PATCH 4/6] Update main_workflow_schema.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/parser/schemas/main_workflow_schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index a7699f5f69e..ed40de944f2 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -3225,7 +3225,7 @@ "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 (e.g., ${{ secrets.GITHUB_TOKEN }} or ${{ secrets.CUSTOM_PAT }})", + "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 }}", From 8dd6d9cafd5d5b4f34ac51164e8ae7ee020456a0 Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Sat, 25 Oct 2025 13:20:35 -0700 Subject: [PATCH 5/6] Update frontmatter.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/src/content/docs/reference/frontmatter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/content/docs/reference/frontmatter.md b/docs/src/content/docs/reference/frontmatter.md index d5346e7ecff..656cf0fc42a 100644 --- a/docs/src/content/docs/reference/frontmatter.md +++ b/docs/src/content/docs/reference/frontmatter.md @@ -78,7 +78,7 @@ The `github-token` field **must** use a GitHub Actions secret expression (e.g., **Invalid formats:** - `ghp_1234567890...` (plaintext token) -- `${{ env.MY_TOKEN }}` (environment variable) +- `${{ env.MY_TOKEN }}` (environment variable; not protected like secrets) - `my-secret-token` (plaintext string) ::: From d39c2a12db087de0f3c3fd8c1f5e766f7d1a6cca Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 25 Oct 2025 20:20:28 +0000 Subject: [PATCH 6/6] Add changeset for github-token secret validation [skip-ci] --- .changeset/patch-add-github-token-secret-validation.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/patch-add-github-token-secret-validation.md diff --git a/.changeset/patch-add-github-token-secret-validation.md b/.changeset/patch-add-github-token-secret-validation.md new file mode 100644 index 00000000000..d14d496aae0 --- /dev/null +++ b/.changeset/patch-add-github-token-secret-validation.md @@ -0,0 +1,5 @@ +--- +"gh-aw": patch +--- + +Add github-token secret validation