Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/src/content/docs/reference/frontmatter-full.md
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,15 @@ env: "example-value"
features:
{}

# Secret values passed to workflow execution. Secrets can be defined as simple
# strings (GitHub Actions expressions) or objects with 'value' and 'description'
# properties. Typically used to provide secrets to MCP servers or custom engines.
# Note: For passing secrets to reusable workflows, use the jobs.<job_id>.secrets
# field instead.
# (optional)
secrets:
{}

# Environment that the job references (for protected environments and deployments)
# (optional)
# This field supports multiple formats (oneOf):
Expand Down
31 changes: 31 additions & 0 deletions docs/src/content/docs/reference/frontmatter.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,37 @@ env:

Environment variables can be defined at multiple scopes (workflow, job, step, engine, safe-outputs, etc.) with clear precedence rules. See [Environment Variables](/gh-aw/reference/environment-variables/) for complete documentation on all 13 env scopes and precedence order.

## Secrets (`secrets:`)

Defines secret values passed to workflow execution. Secrets are typically used to provide sensitive configuration to MCP servers, custom engines, or workflow components. Values must be GitHub Actions expressions that reference secrets (e.g., `${{ secrets.API_KEY }}`).

```yaml wrap
secrets:
API_TOKEN: ${{ secrets.API_TOKEN }}
DATABASE_URL: ${{ secrets.DB_URL }}
```

Secrets can also include descriptions for documentation:

```yaml wrap
secrets:
API_TOKEN:
value: ${{ secrets.API_TOKEN }}
description: "API token for external service"
DATABASE_URL:
value: ${{ secrets.DB_URL }}
description: "Production database connection string"
```

**Security best practices:**

- Always use GitHub Actions secret expressions (`${{ secrets.NAME }}`)
- Never commit plaintext secrets to workflow files
- Use environment-specific secrets when possible (via `environment:` field)
- Limit secret access to only the components that need them

**Note:** For passing secrets to reusable workflows, use the `jobs.<job_id>.secrets` field instead. The top-level `secrets:` field is for workflow-level secret configuration.

## Environment Protection (`environment:`)

Specifies the environment for deployment protection rules and environment-specific secrets. Standard GitHub Actions syntax.
Expand Down
89 changes: 88 additions & 1 deletion pkg/parser/schema_passthrough_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

// TestPassThroughFieldValidation tests that pass-through YAML fields
// (concurrency, container, environment, env, runs-on, services) are
// (concurrency, container, environment, env, secrets, runs-on, services) are
// properly validated by the schema during frontmatter parsing.
//
// These fields are "pass-through" in that they are extracted from
Expand Down Expand Up @@ -244,6 +244,93 @@ func TestPassThroughFieldValidation(t *testing.T) {
errContains: "oneOf",
},

// Secrets field tests
{
name: "valid secrets - simple string values",
frontmatter: map[string]any{
"on": "push",
"secrets": map[string]any{
"API_TOKEN": "${{ secrets.API_TOKEN }}",
"DATABASE_URL": "${{ secrets.DB_URL }}",
},
},
wantErr: false,
},
{
name: "valid secrets - object with value and description",
frontmatter: map[string]any{
"on": "push",
"secrets": map[string]any{
"API_TOKEN": map[string]any{
"value": "${{ secrets.API_TOKEN }}",
"description": "API token for external service",
},
},
},
wantErr: false,
},
{
name: "valid secrets - mixed simple and object values",
frontmatter: map[string]any{
"on": "push",
"secrets": map[string]any{
"API_TOKEN": "${{ secrets.API_TOKEN }}",
"DB_URL": map[string]any{
"value": "${{ secrets.DB_URL }}",
"description": "Database connection string",
},
},
},
wantErr: false,
},
{
name: "invalid secrets - object missing required value field",
frontmatter: map[string]any{
"on": "push",
"secrets": map[string]any{
"API_TOKEN": map[string]any{
"description": "Missing value field",
},
},
},
wantErr: true,
errContains: "missing property 'value'",
},
{
name: "invalid secrets - object with additional properties",
frontmatter: map[string]any{
"on": "push",
"secrets": map[string]any{
"API_TOKEN": map[string]any{
"value": "${{ secrets.API_TOKEN }}",
"invalid": "field",
},
},
},
wantErr: true,
errContains: "additional properties 'invalid' not allowed",
},
{
name: "invalid secrets - array",
frontmatter: map[string]any{
"on": "push",
"secrets": []string{"invalid"},
},
wantErr: true,
errContains: "got array, want object",
},
{
name: "invalid secrets - non-string, non-object value",
frontmatter: map[string]any{
"on": "push",
"secrets": map[string]any{
"API_TOKEN": 123,
},
},
wantErr: true,
errContains: "oneOf",
},
Comment on lines +247 to +332
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new secrets tests cover type/shape errors, but there’s no test asserting that plaintext secret values are rejected (or that secrets.<name>.value must be a ${{ secrets.* }} expression). Once the schema is updated to enforce the expression pattern (e.g., via #/$defs/github_token), add a failing case like "API_TOKEN": "plaintext" (and the metadata form) to ensure the intended validation doesn’t regress.

Copilot uses AI. Check for mistakes.

// Runs-on field tests
{
name: "valid runs-on - simple string",
Expand Down
126 changes: 67 additions & 59 deletions pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1876,6 +1876,50 @@
}
]
},
"secrets": {
"description": "Secret values passed to workflow execution. Secrets can be defined as simple strings (GitHub Actions expressions) or objects with 'value' and 'description' properties. Typically used to provide secrets to MCP servers or custom engines. Note: For passing secrets to reusable workflows, use the jobs.<job_id>.secrets field instead.",
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "string",
"description": "Secret value as a GitHub Actions expression (e.g., ${{ secrets.API_KEY }})"
},
{
"type": "object",
"description": "Secret with metadata",
"required": ["value"],
"properties": {
"value": {
"type": "string",
"description": "Secret value as a GitHub Actions expression"
},
Comment on lines +1883 to +1896
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The schema description (and PR docs) say secret values must be GitHub Actions secret expressions, but the current schema only enforces type: string for both the simple form and the value field. This still allows plaintext (e.g., "my-secret") and other non-secret expressions. Consider reusing the existing #/$defs/github_token (already used for jobs.<job_id>.secrets) for the string variant and for properties.value so schema validation actually enforces ${{ secrets.NAME }} (and || fallback) syntax.

Copilot uses AI. Check for mistakes.
"description": {
"type": "string",
"description": "Description of what this secret is used for"
}
},
"additionalProperties": false
}
]
},
"examples": [
{
"API_TOKEN": "${{ secrets.API_TOKEN }}",
"DATABASE_URL": "${{ secrets.DB_URL }}"
},
{
"API_TOKEN": {
"value": "${{ secrets.API_TOKEN }}",
"description": "API token for external service"
},
"DATABASE_URL": {
"value": "${{ secrets.DB_URL }}",
"description": "Production database connection string"
}
}
]
},
"environment": {
"description": "Environment that the job references (for protected environments and deployments)",
"oneOf": [
Expand Down Expand Up @@ -5263,39 +5307,47 @@
},
"dispatch-workflow": {
"oneOf": [
{
"type": "array",
"minItems": 1,
"items": {
"type": "string"
},
"description": "Shorthand format: array of workflow names to dispatch (without .md extension). Workflows must exist in same directory and support workflow_dispatch trigger. Self-reference not allowed. Max defaults to 1."
},
{
"type": "object",
"description": "Configuration for dispatching other workflows from this workflow. Allows workflows to trigger other workflows via workflow_dispatch events. Includes self-reference prevention and path traversal protection.",
"description": "Configuration for dispatching workflow_dispatch events to other workflows. Orchestrators use this to delegate work to worker workflows.",
"properties": {
"workflows": {
"type": "array",
"minItems": 1,
"description": "List of workflow names (without .md extension) to allow dispatching. Each workflow must exist in .github/workflows/.",
"items": {
"type": "string"
"type": "string",
"minLength": 1
},
"description": "List of workflow names to dispatch (without .md extension). Workflows must exist in same directory and support workflow_dispatch trigger. Self-reference not allowed."
"minItems": 1,
"maxItems": 50
},
"max": {
"type": "integer",
"description": "Maximum number of workflow dispatch operations per run (default: 1, max: 50)",
"minimum": 1,
"maximum": 50,
"description": "Maximum number of concurrent workflow dispatches (default: 1, maximum: 50)"
"default": 1
},
"github-token": {
"$ref": "#/$defs/github_token",
"description": "GitHub token to use for dispatching workflows. Overrides global github-token if specified."
}
},
"required": ["workflows"],
"additionalProperties": false
},
{
"type": "array",
"description": "Shorthand array format: list of workflow names (without .md extension) to allow dispatching",
"items": {
"type": "string",
"minLength": 1
},
"minItems": 1,
"maxItems": 50
}
],
"$comment": "Self-reference prevention: workflow cannot dispatch itself (prevents infinite loops). Path traversal protection: all paths validated with isPathWithinDir(). Validation: pkg/workflow/dispatch_workflow_validation.go",
"description": "Enable dispatching other workflows from this workflow. Allows workflows to trigger other workflows via workflow_dispatch events with security constraints."
"description": "Dispatch workflow_dispatch events to other workflows. Used by orchestrators to delegate work to worker workflows with controlled maximum dispatch count."
},
Comment on lines 5308 to 5351
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR also changes the dispatch-workflow schema (adds maxItems, minLength, introduces github-token, and removes/relocates the shorthand array schema). These changes aren’t mentioned in the PR description and may affect existing configs; please either scope them out of this PR or update the description/changelog to explicitly call out the dispatch-workflow validation changes and rationale.

Copilot uses AI. Check for mistakes.
"missing-tool": {
"oneOf": [
Expand Down Expand Up @@ -5840,50 +5892,6 @@
"runs-on": {
"type": "string",
"description": "Runner specification for all safe-outputs jobs (activation, create-issue, add-comment, etc.). Single runner label (e.g., 'ubuntu-slim', 'ubuntu-latest', 'windows-latest', 'self-hosted'). Defaults to 'ubuntu-slim'. See https://github.blog/changelog/2025-10-28-1-vcpu-linux-runner-now-available-in-github-actions-in-public-preview/"
},
"dispatch-workflow": {
"oneOf": [
{
"type": "object",
"description": "Configuration for dispatching workflow_dispatch events to other workflows. Orchestrators use this to delegate work to worker workflows.",
"properties": {
"workflows": {
"type": "array",
"description": "List of workflow names (without .md extension) to allow dispatching. Each workflow must exist in .github/workflows/.",
"items": {
"type": "string",
"minLength": 1
},
"minItems": 1,
"maxItems": 50
},
"max": {
"type": "integer",
"description": "Maximum number of workflow dispatch operations per run (default: 1, max: 50)",
"minimum": 1,
"maximum": 50,
"default": 1
},
"github-token": {
"$ref": "#/$defs/github_token",
"description": "GitHub token to use for dispatching workflows. Overrides global github-token if specified."
}
},
"required": ["workflows"],
"additionalProperties": false
},
{
"type": "array",
"description": "Shorthand array format: list of workflow names (without .md extension) to allow dispatching",
"items": {
"type": "string",
"minLength": 1
},
"minItems": 1,
"maxItems": 50
}
],
"description": "Dispatch workflow_dispatch events to other workflows. Used by orchestrators to delegate work to worker workflows with controlled maximum dispatch count."
}
},
"additionalProperties": false
Expand Down
Loading