diff --git a/.github/aw/syntax.md b/.github/aw/syntax.md index fdf2939aaff..38a72891d5d 100644 --- a/.github/aw/syntax.md +++ b/.github/aw/syntax.md @@ -333,11 +333,10 @@ The YAML frontmatter supports these fields: if: "hashFiles('go.mod') != ''" # Only install Go when go.mod exists ``` -- **`run-install-scripts:`** - Allow npm pre/post install scripts to execute during package installation (boolean, default: `false`) +- **`runtimes.node.run-install-scripts:`** - Allow npm pre/post install scripts to execute during package installation for the Node.js runtime (boolean, default: `false`) - By default, `--ignore-scripts` is added to all generated npm install commands to prevent supply chain attacks via malicious install hooks - - When `true`, disables this protection globally for all runtimes that generate `npm install` commands + - Set `run-install-scripts: true` under `runtimes.node` to allow scripts for Node.js installs - A supply chain security warning is emitted at compile time; in strict mode this is an error - - Per-runtime control is also available via `runtimes.node.run-install-scripts: true` to limit scope to a specific runtime - **`checkout:`** - Override how the repository is checked out in the agent job (object, array, or `false`) - By default, the workflow automatically checks out the repository. Use this field to customize checkout behavior. diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index 16ca0f1c461..941eb203dba 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"847860af3924384aa2f4bd0188a573e31264f6225f10ed1ff1dd8cb0ca014fe5","body_hash":"55ac3a789fdbd8eca450f17c99cb9b78e5eafede9392b9e5444dce5e97f9cbee","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"8d41a250f29248203bc447b1d39fe538173aa7b849fdbc6d8c2dadeb04b5d229","body_hash":"55ac3a789fdbd8eca450f17c99cb9b78e5eafede9392b9e5444dce5e97f9cbee","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN","TAVILY_API_KEY"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/setup-python","sha":"a309ff8b426b58ec0e2a45f0f869d46889d02405","version":"v6.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.58"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.22"},{"image":"ghcr.io/github/github-mcp-server:v1.1.0"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -246,24 +246,24 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_91827a5740df86de_EOF' + cat << 'GH_AW_PROMPT_559c1a7d5e97c891_EOF' - GH_AW_PROMPT_91827a5740df86de_EOF + GH_AW_PROMPT_559c1a7d5e97c891_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/repo_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_91827a5740df86de_EOF' + cat << 'GH_AW_PROMPT_559c1a7d5e97c891_EOF' Tools: create_discussion, upload_asset(max:5), missing_tool, missing_data, noop upload_asset: provide a file path; returns a URL; assets are published after the workflow completes (safeoutputs). - GH_AW_PROMPT_91827a5740df86de_EOF + GH_AW_PROMPT_559c1a7d5e97c891_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_91827a5740df86de_EOF' + cat << 'GH_AW_PROMPT_559c1a7d5e97c891_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -292,9 +292,9 @@ jobs: {{/if}} - GH_AW_PROMPT_91827a5740df86de_EOF + GH_AW_PROMPT_559c1a7d5e97c891_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_91827a5740df86de_EOF' + cat << 'GH_AW_PROMPT_559c1a7d5e97c891_EOF' {{#runtime-import .github/workflows/shared/mcp/tavily.md}} {{#runtime-import .github/skills/jqschema/SKILL.md}} @@ -305,7 +305,7 @@ jobs: {{#runtime-import .github/shared/editorial.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/daily-news.md}} - GH_AW_PROMPT_91827a5740df86de_EOF + GH_AW_PROMPT_559c1a7d5e97c891_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -671,9 +671,9 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs/upload-artifacts" - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << GH_AW_SAFE_OUTPUTS_CONFIG_141cb3c981409d53_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << GH_AW_SAFE_OUTPUTS_CONFIG_951003e9b0d04979_EOF {"create_discussion":{"category":"daily-news","close_older_discussions":true,"expires":72,"fallback_to_issue":true,"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]},"report_incomplete":{},"upload_artifact":{"max-size-bytes":104857600,"max-uploads":3,"retention-days":30,"skip-archive":true},"upload_asset":{"allowed-exts":[".png",".jpg",".jpeg",".svg"],"branch":"assets/${GITHUB_WORKFLOW}","max":5,"max-size":10240}} - GH_AW_SAFE_OUTPUTS_CONFIG_141cb3c981409d53_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_951003e9b0d04979_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -888,7 +888,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_e0ef50bab4d0afa5_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_4f87f7c49c163b5d_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -954,7 +954,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_e0ef50bab4d0afa5_EOF + GH_AW_MCP_CONFIG_4f87f7c49c163b5d_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true diff --git a/docs/public/editor/autocomplete-data.json b/docs/public/editor/autocomplete-data.json index 48dc23326ba..d0a7a4b968a 100644 --- a/docs/public/editor/autocomplete-data.json +++ b/docs/public/editor/autocomplete-data.json @@ -59,7 +59,10 @@ "inlined-imports": { "type": "boolean", "desc": "If true, inline all imports (including those without inputs) at compilation time in the generated lock.yml instead of...", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "on": { @@ -240,7 +243,9 @@ "skip-if-check-failing": { "type": "null|boolean|object", "desc": "Skip workflow execution if any CI checks on the target branch are failing or pending.", - "enum": [true], + "enum": [ + true + ], "leaf": true }, "skip-roles": { @@ -262,7 +267,15 @@ "roles": { "type": "string|array", "desc": "Repository access roles required to trigger agentic workflows.", - "enum": ["admin", "maintainer", "maintain", "write", "triage", "read", "all"], + "enum": [ + "admin", + "maintainer", + "maintain", + "write", + "triage", + "read", + "all" + ], "leaf": true, "array": true }, @@ -280,7 +293,10 @@ "allow-bot-authored-trigger-comment": { "type": "boolean", "desc": "Allow the bot-posted-menu / user-checks-box pattern: when a workflow posts a checkbox-menu comment as a GitHub App bo...", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "manual-approval": { @@ -291,7 +307,17 @@ "reaction": { "type": "string|integer|object", "desc": "AI reaction to add/remove on triggering item.", - "enum": ["+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", "eyes", "none"], + "enum": [ + "+1", + "-1", + "laugh", + "confused", + "heart", + "hooray", + "rocket", + "eyes", + "none" + ], "leaf": true }, "status-comment": { @@ -325,7 +351,9 @@ "stale-check": { "type": "boolean|string", "desc": "Controls the stale lock file check in the activation job.", - "enum": ["full"], + "enum": [ + "full" + ], "leaf": true } } @@ -333,120 +361,204 @@ "permissions": { "type": "string|object", "desc": "GitHub token permissions for the workflow.", - "enum": ["read-all", "write-all"], + "enum": [ + "read-all", + "write-all" + ], "children": { "actions": { "type": "string", "desc": "Permission for GitHub Actions workflows and runs (read: view workflows, write: manage workflows, none: no access)", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "leaf": true }, "attestations": { "type": "string", "desc": "Permission for artifact attestations (read: view attestations, write: create attestations, none: no access)", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "leaf": true }, "checks": { "type": "string", "desc": "Permission for repository checks and status checks (read: view checks, write: create/update checks, none: no access)", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], + "leaf": true + }, + "copilot-requests": { + "type": "string", + "desc": "Permission level for Copilot requests (write/none only).", + "enum": [ + "write", + "none" + ], "leaf": true }, "contents": { "type": "string", "desc": "Permission for repository contents (read: view files, write: modify files/branches, none: no access)", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "leaf": true }, "deployments": { "type": "string", "desc": "Permission for repository deployments (read: view deployments, write: create/update deployments, none: no access)", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "leaf": true }, "discussions": { "type": "string", "desc": "Permission for repository discussions (read: view discussions, write: create/update discussions, none: no access)", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "leaf": true }, "id-token": { "type": "string", "desc": "Permission level for OIDC token requests (write/none only - read is not supported).", - "enum": ["write", "none"], + "enum": [ + "write", + "none" + ], "leaf": true }, "issues": { "type": "string", "desc": "Permission for repository issues (read: view issues, write: create/update/close issues, none: no access)", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "leaf": true }, "models": { "type": "string", "desc": "Permission for GitHub Copilot models (read: access AI models for agentic workflows, none: no access)", - "enum": ["read", "none"], + "enum": [ + "read", + "none" + ], "leaf": true }, "metadata": { "type": "string", "desc": "Permission for repository metadata (read: view repository information, write: update repository metadata, none: no ac...", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "leaf": true }, "packages": { "type": "string", "desc": "Permission level for GitHub Packages (read/write/none).", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "leaf": true }, "pages": { "type": "string", "desc": "Permission level for GitHub Pages (read/write/none).", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "leaf": true }, "pull-requests": { "type": "string", "desc": "Permission level for pull requests (read/write/none).", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "leaf": true }, "repository-projects": { "type": "string", "desc": "Permission level for repository projects (read/write/none).", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "leaf": true }, "organization-projects": { "type": "string", "desc": "Permission level for organization projects (read/write/none).", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "leaf": true }, "security-events": { "type": "string", "desc": "Permission level for security events (read/write/none).", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "leaf": true }, "statuses": { "type": "string", "desc": "Permission level for commit statuses (read/write/none).", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "leaf": true }, "vulnerability-alerts": { "type": "string", "desc": "Permission level for Dependabot vulnerability alerts (read/write/none).", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "leaf": true }, "all": { "type": "string", "desc": "Permission shorthand that applies read access to all permission scopes.", - "enum": ["read"], + "enum": [ + "read" + ], "leaf": true } } @@ -499,13 +611,19 @@ "cancel-in-progress": { "type": "boolean", "desc": "Whether to cancel in-progress workflows in the same concurrency group when a new one starts.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "queue": { "type": "string", "desc": "Pending run queue behavior for this concurrency group.", - "enum": ["single", "max"], + "enum": [ + "single", + "max" + ], "leaf": true }, "job-discriminator": { @@ -523,7 +641,10 @@ "inline-sub-agents": { "type": "boolean", "desc": "Deprecated switch for inline sub-agent support.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "features": { @@ -541,7 +662,10 @@ "storage": { "type": "string", "desc": "Storage backend for experiment state.", - "enum": ["cache", "repo"], + "enum": [ + "cache", + "repo" + ], "leaf": true } } @@ -549,7 +673,10 @@ "disable-model-invocation": { "type": "boolean", "desc": "Controls whether the custom agent should disable model invocation.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "secrets": { @@ -625,7 +752,9 @@ "network": { "type": "string|object", "desc": "Network access control for AI engines using ecosystem identifiers and domain allowlists.", - "enum": ["defaults"], + "enum": [ + "defaults" + ], "children": { "allowed": { "type": "array", @@ -635,7 +764,10 @@ "allowed-input": { "type": "boolean", "desc": "When true and the workflow uses workflow_call, expose a network_allowed string input on the compiled lock file.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "blocked": { @@ -648,29 +780,41 @@ "sandbox": { "type": "string|object", "desc": "Sandbox configuration for AI engines.", - "enum": ["default", "awf"], + "enum": [ + "default", + "awf" + ], "children": { "type": { "type": "string", "desc": "Legacy sandbox type field (use agent instead).", - "enum": ["default", "awf"], + "enum": [ + "default", + "awf" + ], "leaf": true }, "agent": { "type": "boolean|string|object", "desc": "Agent sandbox type: 'awf' uses AWF (Agent Workflow Firewall), or false to disable agent sandbox.", - "enum": ["awf"], + "enum": [ + "awf" + ], "children": { "id": { "type": "string", "desc": "Agent identifier (replaces 'type' field in new format): 'awf' for Agent Workflow Firewall", - "enum": ["awf"], + "enum": [ + "awf" + ], "leaf": true }, "type": { "type": "string", "desc": "Legacy: Sandbox type to use (use 'id' instead)", - "enum": ["awf"], + "enum": [ + "awf" + ], "leaf": true }, "version": { @@ -722,10 +866,27 @@ "enableWeakerNestedSandbox": { "type": "boolean", "desc": "Enable weaker nested sandbox mode (recommended: true for Docker access)", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true } } + }, + "targets": { + "type": "object", + "desc": "Per-provider API proxy target overrides.", + "children": { + "openai": { + "type": "object", + "desc": "AWF API proxy target configuration for a single LLM provider." + }, + "anthropic": { + "type": "object", + "desc": "AWF API proxy target configuration for a single LLM provider." + } + } } } }, @@ -761,7 +922,10 @@ "enableWeakerNestedSandbox": { "type": "boolean", "desc": "When true, allows nested sandbox processes to run with relaxed restrictions.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true } } @@ -817,7 +981,10 @@ "domain": { "type": "string", "desc": "Gateway domain for URL generation (default: 'host.docker.internal' when agent is enabled, 'localhost' when disabled)", - "enum": ["localhost", "host.docker.internal"], + "enum": [ + "localhost", + "host.docker.internal" + ], "leaf": true }, "keepalive-interval": { @@ -876,7 +1043,12 @@ "permission-mode": { "type": "string", "desc": "Claude permission mode override.", - "enum": ["auto", "acceptEdits", "plan", "bypassPermissions"], + "enum": [ + "auto", + "acceptEdits", + "plan", + "bypassPermissions" + ], "leaf": true }, "max-turns": { @@ -901,13 +1073,19 @@ "cancel-in-progress": { "type": "boolean", "desc": "Whether to cancel in-progress runs of the same concurrency group.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "queue": { "type": "string", "desc": "Pending run queue behavior for this concurrency group.", - "enum": ["single", "max"], + "enum": [ + "single", + "max" + ], "leaf": true } } @@ -938,7 +1116,9 @@ "type": { "type": "string", "desc": "Authentication type.", - "enum": ["github-oidc"], + "enum": [ + "github-oidc" + ], "leaf": true }, "audience": { @@ -965,6 +1145,31 @@ "type": "string", "desc": "Optional Azure cloud name (for example, public, usgovernment, china).", "leaf": true + }, + "provider": { + "type": "string", + "desc": "Optional WIF provider discriminator.", + "leaf": true + }, + "federation-rule-id": { + "type": "string", + "desc": "Anthropic WIF federation rule ID (e.g., fdrl_...).", + "leaf": true + }, + "organization-id": { + "type": "string", + "desc": "Anthropic WIF organization ID (e.g., org_...).", + "leaf": true + }, + "service-account-id": { + "type": "string", + "desc": "Anthropic WIF service account ID (e.g., svac_...).", + "leaf": true + }, + "workspace-id": { + "type": "string", + "desc": "Anthropic WIF workspace ID (e.g., ws_...).", + "leaf": true } } }, @@ -1032,7 +1237,10 @@ "bare": { "type": "boolean", "desc": "When true, disables automatic loading of context and custom instructions by the AI engine.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "mcp": { @@ -1050,6 +1258,15 @@ "leaf": true } } + }, + "copilot-sdk": { + "type": "boolean", + "desc": "Enables the experimental GitHub Copilot SDK integration (copilot engine only).", + "enum": [ + true, + false + ], + "leaf": true } } }, @@ -1083,13 +1300,20 @@ "mode": { "type": "string", "desc": "GitHub access mode.", - "enum": ["gh-proxy", "local", "remote"], + "enum": [ + "gh-proxy", + "local", + "remote" + ], "leaf": true }, "type": { "type": "string", "desc": "GitHub MCP transport type: 'local' (Docker-based, default) or 'remote' (hosted at api.githubcopilot.com)", - "enum": ["local", "remote"], + "enum": [ + "local", + "remote" + ], "leaf": true }, "version": { @@ -1105,19 +1329,28 @@ "read-only": { "type": "boolean", "desc": "Enable read-only mode to restrict GitHub MCP server to read-only operations only", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "lockdown": { "type": "boolean", "desc": "Enable lockdown mode to limit content surfaced from public repositories (only items authored by users with push access).", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "integrity-proxy": { "type": "boolean", "desc": "Controls DIFC proxy injection for pre-agent gh CLI steps when guard policies (min-integrity) are configured.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "github-token": { @@ -1163,21 +1396,34 @@ "allowed-repos": { "type": "string|array", "desc": "Guard policy: repository access configuration.", - "enum": ["all", "public", "${{ github.repository }}"], + "enum": [ + "all", + "public", + "${{ github.repository }}" + ], "leaf": true, "array": true }, "repos": { "type": "string|array", "desc": "Deprecated.", - "enum": ["all", "public", "${{ github.repository }}"], + "enum": [ + "all", + "public", + "${{ github.repository }}" + ], "leaf": true, "array": true }, "min-integrity": { "type": "string", "desc": "Guard policy: minimum required integrity level for repository access.", - "enum": ["none", "unapproved", "approved", "merged"], + "enum": [ + "none", + "unapproved", + "approved", + "merged" + ], "leaf": true }, "blocked-users": { @@ -1211,13 +1457,22 @@ "disapproval-integrity": { "type": "string", "desc": "Guard policy: integrity level assigned when a disapproval reaction is present.", - "enum": ["none", "unapproved", "approved", "merged"], + "enum": [ + "none", + "unapproved", + "approved", + "merged" + ], "leaf": true }, "endorser-min-integrity": { "type": "string", "desc": "Guard policy: minimum integrity level required for an endorser (reactor) to promote content.", - "enum": ["unapproved", "approved", "merged"], + "enum": [ + "unapproved", + "approved", + "merged" + ], "leaf": true }, "github-app": { @@ -1242,7 +1497,10 @@ "ignore-if-missing": { "type": "boolean", "desc": "If true, skip token minting when client-id/private-key resolve to empty strings at runtime.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "owner": { @@ -1306,7 +1564,10 @@ "mode": { "type": "string", "desc": "Integration mode: 'cli' (recommended) installs @playwright/cli via npm for token-efficient CLI invocations — use play...", - "enum": ["cli", "mcp"], + "enum": [ + "cli", + "mcp" + ], "leaf": true } } @@ -1338,13 +1599,19 @@ "restore-only": { "type": "boolean", "desc": "If true, only restore the cache without saving it back.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "scope": { "type": "string", "desc": "Cache restore key scope: 'workflow' (default, only restores from same workflow) or 'repo' (restores from any workflow...", - "enum": ["workflow", "repo"], + "enum": [ + "workflow", + "repo" + ], "leaf": true }, "allowed-extensions": { @@ -1387,7 +1654,10 @@ "footer": { "type": "boolean", "desc": "Controls whether AI-generated footer is added to the managed comment.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "github-token": { @@ -1398,7 +1668,10 @@ "staged": { "type": "boolean", "desc": "If true, emit step summary messages instead of making GitHub API calls for this specific output type (preview mode)", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true } } @@ -1416,7 +1689,10 @@ "cli-proxy": { "type": "boolean", "desc": "When true, each user-facing MCP server is mounted as a standalone CLI tool on PATH.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "serena": { @@ -1470,13 +1746,19 @@ "create-orphan": { "type": "boolean", "desc": "Create orphaned branch if it doesn't exist (default: true)", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "wiki": { "type": "boolean", "desc": "Use the GitHub Wiki git repository instead of the regular repository.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "allowed-extensions": { @@ -1518,13 +1800,19 @@ "fail-on-cache-miss": { "type": "boolean", "desc": "Fail the workflow if cache entry is not found", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "lookup-only": { "type": "boolean", "desc": "If true, only checks if cache entry exists and skips download", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "name": { @@ -1776,7 +2064,10 @@ "staged": { "type": "boolean", "desc": "If true, emit step summary messages instead of making GitHub API calls (preview mode)", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "env": { @@ -1827,7 +2118,10 @@ "footer": { "type": "boolean", "desc": "Global footer control for all safe outputs.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "activation-comments": { @@ -1838,13 +2132,19 @@ "group-reports": { "type": "boolean", "desc": "When true, creates a parent '[aw] Failed runs' issue that tracks all workflow failures as sub-issues.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "report-failure-as-issue": { "type": "boolean", "desc": "When false, disables creating failure tracking issues when workflows fail.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "failure-issue-repo": { @@ -1860,7 +2160,10 @@ "id-token": { "type": "string", "desc": "Override the id-token permission for the safe-outputs job.", - "enum": ["write", "none"], + "enum": [ + "write", + "none" + ], "leaf": true }, "concurrency-group": { @@ -1942,7 +2245,11 @@ "if-missing": { "type": "string", "desc": "How to handle missing OTLP endpoint/header values at runtime (for example from unset secrets).", - "enum": ["error", "warn", "ignore"], + "enum": [ + "error", + "warn", + "ignore" + ], "leaf": true }, "github-app": { @@ -1967,7 +2274,10 @@ "ignore-if-missing": { "type": "boolean", "desc": "If true, skip token minting when client-id/private-key resolve to empty strings at runtime.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true } } @@ -2036,25 +2346,28 @@ "strict": { "type": "boolean", "desc": "Enable strict mode validation for enhanced security and compliance.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "private": { "type": "boolean", "desc": "Mark the workflow as private, preventing it from being added to other repositories via 'gh aw add'.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "check-for-updates": { "type": "boolean", "desc": "Control whether the compile-agentic version update check runs in the activation job.", - "enum": [true, false], - "leaf": true - }, - "run-install-scripts": { - "type": "boolean", - "desc": "Allow npm pre/post install scripts to execute during package installation.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "mcp-scripts": { @@ -2068,7 +2381,9 @@ "checkout": { "type": "object|array|boolean", "desc": "Checkout configuration for the agent job.", - "enum": [false], + "enum": [ + false + ], "children": { "repository": { "type": "string", @@ -2098,13 +2413,20 @@ "submodules": { "type": "string|boolean", "desc": "Controls submodule checkout.", - "enum": ["recursive", "true", "false"], + "enum": [ + "recursive", + "true", + "false" + ], "leaf": true }, "lfs": { "type": "boolean", "desc": "Whether to download Git LFS objects.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "token": { @@ -2139,7 +2461,10 @@ "ignore-if-missing": { "type": "boolean", "desc": "If true, skip token minting when client-id/private-key resolve to empty strings at runtime.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "owner": { @@ -2159,181 +2484,300 @@ "administration": { "type": "string", "desc": "Permission level for repository administration (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "codespaces": { "type": "string", "desc": "Permission level for Codespaces (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "codespaces-lifecycle-admin": { "type": "string", "desc": "Permission level for Codespaces lifecycle administration (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "codespaces-metadata": { "type": "string", "desc": "Permission level for Codespaces metadata (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "email-addresses": { "type": "string", "desc": "Permission level for user email addresses (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "environments": { "type": "string", "desc": "Permission level for repository environments (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "git-signing": { "type": "string", "desc": "Permission level for git signing (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "members": { "type": "string", "desc": "Permission level for organization members (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-administration": { "type": "string", "desc": "Permission level for organization administration (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-announcement-banners": { "type": "string", "desc": "Permission level for organization announcement banners (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-codespaces": { "type": "string", "desc": "Permission level for organization Codespaces (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-copilot": { "type": "string", "desc": "Permission level for organization Copilot (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-custom-org-roles": { "type": "string", "desc": "Permission level for organization custom org roles (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-custom-properties": { "type": "string", "desc": "Permission level for organization custom properties (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-custom-repository-roles": { "type": "string", "desc": "Permission level for organization custom repository roles (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-events": { "type": "string", "desc": "Permission level for organization events (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-hooks": { "type": "string", "desc": "Permission level for organization webhooks (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-members": { "type": "string", "desc": "Permission level for organization members management (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-packages": { "type": "string", "desc": "Permission level for organization packages (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-personal-access-token-requests": { "type": "string", "desc": "Permission level for organization personal access token requests (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-personal-access-tokens": { "type": "string", "desc": "Permission level for organization personal access tokens (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-plan": { "type": "string", "desc": "Permission level for organization plan (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-self-hosted-runners": { "type": "string", "desc": "Permission level for organization self-hosted runners (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-user-blocking": { "type": "string", "desc": "Permission level for organization user blocking (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "repository-custom-properties": { "type": "string", "desc": "Permission level for repository custom properties (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "repository-hooks": { "type": "string", "desc": "Permission level for repository webhooks (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "single-file": { "type": "string", "desc": "Permission level for single file access (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "team-discussions": { "type": "string", "desc": "Permission level for team discussions (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "vulnerability-alerts": { "type": "string", "desc": "Permission level for Dependabot vulnerability alerts (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none"], + "enum": [ + "read", + "none" + ], "leaf": true }, "workflows": { "type": "string", "desc": "Permission level for GitHub Actions workflow files (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true } } @@ -2343,7 +2787,10 @@ "current": { "type": "boolean", "desc": "Marks this checkout as the logical current repository for the workflow.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "fetch": { @@ -2355,13 +2802,19 @@ "wiki": { "type": "boolean", "desc": "When true, clones the repository's wiki git instead of the regular repository.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "force-clean-git-credentials": { "type": "boolean", "desc": "When true, persist credentials during checkout, then immediately run a post-checkout cleanup step that removes creden...", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true } }, @@ -2389,7 +2842,10 @@ "ignore-if-missing": { "type": "boolean", "desc": "If true, skip token minting when client-id/private-key resolve to empty strings at runtime.", - "enum": [true, false], + "enum": [ + true, + false + ], "leaf": true }, "owner": { @@ -2409,181 +2865,300 @@ "administration": { "type": "string", "desc": "Permission level for repository administration (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "codespaces": { "type": "string", "desc": "Permission level for Codespaces (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "codespaces-lifecycle-admin": { "type": "string", "desc": "Permission level for Codespaces lifecycle administration (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "codespaces-metadata": { "type": "string", "desc": "Permission level for Codespaces metadata (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "email-addresses": { "type": "string", "desc": "Permission level for user email addresses (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "environments": { "type": "string", "desc": "Permission level for repository environments (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "git-signing": { "type": "string", "desc": "Permission level for git signing (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "members": { "type": "string", "desc": "Permission level for organization members (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-administration": { "type": "string", "desc": "Permission level for organization administration (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-announcement-banners": { "type": "string", "desc": "Permission level for organization announcement banners (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-codespaces": { "type": "string", "desc": "Permission level for organization Codespaces (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-copilot": { "type": "string", "desc": "Permission level for organization Copilot (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-custom-org-roles": { "type": "string", "desc": "Permission level for organization custom org roles (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-custom-properties": { "type": "string", "desc": "Permission level for organization custom properties (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-custom-repository-roles": { "type": "string", "desc": "Permission level for organization custom repository roles (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-events": { "type": "string", "desc": "Permission level for organization events (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-hooks": { "type": "string", "desc": "Permission level for organization webhooks (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-members": { "type": "string", "desc": "Permission level for organization members management (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-packages": { "type": "string", "desc": "Permission level for organization packages (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-personal-access-token-requests": { "type": "string", "desc": "Permission level for organization personal access token requests (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-personal-access-tokens": { "type": "string", "desc": "Permission level for organization personal access tokens (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-plan": { "type": "string", "desc": "Permission level for organization plan (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-self-hosted-runners": { "type": "string", "desc": "Permission level for organization self-hosted runners (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "organization-user-blocking": { "type": "string", "desc": "Permission level for organization user blocking (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "repository-custom-properties": { "type": "string", "desc": "Permission level for repository custom properties (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "repository-hooks": { "type": "string", "desc": "Permission level for repository webhooks (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "single-file": { "type": "string", "desc": "Permission level for single file access (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "team-discussions": { "type": "string", "desc": "Permission level for team discussions (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true }, "vulnerability-alerts": { "type": "string", "desc": "Permission level for Dependabot vulnerability alerts (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none"], + "enum": [ + "read", + "none" + ], "leaf": true }, "workflows": { "type": "string", "desc": "Permission level for GitHub Actions workflow files (read/none; \"write\" is rejected by the compiler).", - "enum": ["read", "none", "write"], + "enum": [ + "read", + "none", + "write" + ], "leaf": true } } @@ -2636,4 +3211,4 @@ "runtimes", "jobs" ] -} +} \ No newline at end of file diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index 6a8247ba1b4..0b4e214e020 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -1408,6 +1408,11 @@ permissions: # (optional) checks: "read" + # Permission level for Copilot requests (write/none only). Set to write to allow + # Copilot inference via the GitHub Actions token. + # (optional) + copilot-requests: "write" + # Permission for repository contents (read: view files, write: modify # files/branches, none: no access) # (optional) @@ -1669,7 +1674,7 @@ experiments: # Storage backend for experiment state. 'repo' (default) persists state to a git # branch named 'experiments/{sanitizedWorkflowID}' (workflow ID lowercased with # hyphens removed, e.g. 'my-workflow' -> 'experiments/myworkflow') for durability - # across cache evictions. 'cache' uses GitHub Actions cache (legacy behavior). + # across cache evictions. 'cache' uses GitHub Actions cache (legacy behaviour). # Repo storage is recommended because experiment data is valuable and more durable # than cache. # (optional) @@ -1906,6 +1911,28 @@ sandbox: # (optional) enableWeakerNestedSandbox: true + # Per-provider API proxy target overrides. Settings are compiled into the AWF + # config JSON. + # (optional) + targets: + # AWF API proxy target configuration for a single LLM provider. + # (optional) + openai: + # Custom authentication header name to use when forwarding requests to this + # provider's API. Overrides the provider default ("Authorization" for OpenAI, + # "x-api-key" for Anthropic). Example: "api-key" for Azure OpenAI gateways. + # (optional) + authHeader: "example-value" + + # AWF API proxy target configuration for a single LLM provider. + # (optional) + anthropic: + # Custom authentication header name to use when forwarding requests to this + # provider's API. Overrides the provider default ("Authorization" for OpenAI, + # "x-api-key" for Anthropic). Example: "api-key" for Azure OpenAI gateways. + # (optional) + authHeader: "example-value" + # Legacy custom Sandbox Runtime configuration (use agent.config instead). Note: # Network configuration is controlled by the top-level 'network' field, not here. # (optional) @@ -2171,6 +2198,27 @@ engine: # (optional) azure-cloud: "example-value" + # Optional WIF provider discriminator. Recognized values are 'azure' and + # 'anthropic'. + # (optional) + provider: "example-value" + + # Anthropic WIF federation rule ID (e.g., fdrl_...). + # (optional) + federation-rule-id: "example-value" + + # Anthropic WIF organization ID (e.g., org_...). + # (optional) + organization-id: "example-value" + + # Anthropic WIF service account ID (e.g., svac_...). + # (optional) + service-account-id: "example-value" + + # Anthropic WIF workspace ID (e.g., ws_...). + # (optional) + workspace-id: "example-value" + # Additional TOML configuration text that will be appended to the generated # config.toml in the action (codex engine only) # (optional) @@ -2259,6 +2307,12 @@ engine: # (optional) tool-timeout: "example-value" + # Enables the experimental GitHub Copilot SDK integration (copilot engine only). + # When true, the harness starts a separate headless Copilot CLI sidecar on the + # configured localhost port and sets COPILOT_SDK_URI on child processes. + # (optional) + copilot-sdk: true + # Format 3: Inline engine definition: specifies a runtime adapter and optional # provider settings directly in the workflow frontmatter, without requiring a # named catalog entry @@ -4569,7 +4623,7 @@ safe-outputs: # Controls protected-file protection. String form: request_review (default), # blocked, allowed, or fallback-to-issue — or a GitHub Actions expression for - # reusable workflows. Object form: { policy, exclude } to customize the + # reusable workflows. Object form: { policy, exclude } to customise the # protected-file set. # (optional) # Accepted formats: @@ -4585,7 +4639,7 @@ safe-outputs: # Format 2: GitHub Actions expression that resolves to 'blocked', 'allowed', # 'fallback-to-issue', or 'request_review' at runtime. Use in reusable - # workflow_call workflows to parameterize the policy per caller. + # workflow_call workflows to parameterise the policy per caller. protected-files: "example-value" # Format 3: Object form for granular control over the protected-file set. Use the @@ -4665,7 +4719,7 @@ safe-outputs: patch-format: "am" # Format 2: GitHub Actions expression that resolves to 'am' or 'bundle' at - # runtime. Use in reusable workflow_call workflows to parameterize the transport + # runtime. Use in reusable workflow_call workflows to parameterise the transport # format per caller. patch-format: "example-value" @@ -6201,7 +6255,7 @@ safe-outputs: # Controls protected-file protection. String form: blocked (default), allowed, or # fallback-to-issue — or a GitHub Actions expression for reusable workflows. - # Object form: { policy, exclude } to customize the protected-file set. + # Object form: { policy, exclude } to customise the protected-file set. # (optional) # Accepted formats: @@ -6214,7 +6268,7 @@ safe-outputs: # Format 2: GitHub Actions expression that resolves to 'blocked', 'allowed', or # 'fallback-to-issue' at runtime. Use in reusable workflow_call workflows to - # parameterize the policy per caller. + # parameterise the policy per caller. protected-files: "example-value" # Format 3: Object form for granular control over the protected-file set. Use the @@ -6276,7 +6330,7 @@ safe-outputs: patch-format: "am" # Format 2: GitHub Actions expression that resolves to 'am' or 'bundle' at - # runtime. Use in reusable workflow_call workflows to parameterize the transport + # runtime. Use in reusable workflow_call workflows to parameterise the transport # format per caller. patch-format: "example-value" @@ -6846,7 +6900,7 @@ safe-outputs: # Default values injected when the model omits a field # (optional) defaults: - # Behavior when no files match: 'error' (default) or 'ignore' + # Behaviour when no files match: 'error' (default) or 'ignore' # (optional) if-no-files: "error" @@ -7729,17 +7783,6 @@ private: true # (optional) check-for-updates: true -# Allow npm pre/post install scripts to execute during package installation. By -# default, --ignore-scripts is added to all generated npm install commands to -# prevent supply chain attacks via malicious install hooks. Setting -# run-install-scripts: true disables this protection globally (all runtimes). A -# supply chain security warning is emitted at compile time; in strict mode this is -# an error. Per-runtime control is also available via -# runtimes..run-install-scripts. See: -# https://github.github.com/gh-aw/reference/frontmatter/#run-install-scripts -# (optional) -run-install-scripts: true - # MCP Scripts configuration for defining custom lightweight MCP tools as # JavaScript, shell scripts, or Python scripts. Tools are mounted in an MCP server # and have access to secrets specified by the user. Only one of 'script' diff --git a/pkg/cli/codemod_run_install_scripts.go b/pkg/cli/codemod_run_install_scripts.go new file mode 100644 index 00000000000..2667782491e --- /dev/null +++ b/pkg/cli/codemod_run_install_scripts.go @@ -0,0 +1,179 @@ +package cli + +import ( + "fmt" + "strings" + + "github.com/github/gh-aw/pkg/logger" +) + +var runInstallScriptsCodemodLog = logger.New("cli:codemod_run_install_scripts") + +// getRunInstallScriptsToRuntimesNodeCodemod creates a codemod that moves a top-level +// run-install-scripts field into runtimes.node.run-install-scripts. +func getRunInstallScriptsToRuntimesNodeCodemod() Codemod { + return Codemod{ + ID: "run-install-scripts-to-runtimes-node", + Name: "Move run-install-scripts under runtimes.node", + Description: "Moves the deprecated top-level 'run-install-scripts' field to 'runtimes.node.run-install-scripts', which is the only runtime that generates npm install commands.", + IntroducedIn: "1.5.0", + Apply: func(content string, frontmatter map[string]any) (string, bool, error) { + // Check if top-level run-install-scripts exists + risValue, hasTopLevel := frontmatter["run-install-scripts"] + if !hasTopLevel { + return content, false, nil + } + + // Check if runtimes.node.run-install-scripts already exists (idempotent) + alreadyNested := false + if runtimesVal, ok := frontmatter["runtimes"]; ok { + if runtimesMap, ok := runtimesVal.(map[string]any); ok { + if nodeVal, ok := runtimesMap["node"]; ok { + if nodeMap, ok := nodeVal.(map[string]any); ok { + if _, ok := nodeMap["run-install-scripts"]; ok { + alreadyNested = true + } + } + } + } + } + + // Determine the string representation of the value + risStr := "true" + switch v := risValue.(type) { + case bool: + if !v { + risStr = "false" + } + case string: + if strings.EqualFold(v, "false") { + risStr = "false" + } + } + + newContent, applied, err := applyFrontmatterLineTransform(content, func(lines []string) ([]string, bool) { + return migrateRunInstallScriptsLines(lines, risStr, alreadyNested) + }) + if applied { + runInstallScriptsCodemodLog.Printf("Moved top-level 'run-install-scripts' to 'runtimes.node.run-install-scripts: %s'", risStr) + } + return newContent, applied, err + }, + } +} + +// migrateRunInstallScriptsLines transforms frontmatter lines by removing the top-level +// run-install-scripts key and injecting it under runtimes.node. +func migrateRunInstallScriptsLines(lines []string, risStr string, alreadyNested bool) ([]string, bool) { + // Step 1: Find the top-level run-install-scripts line + topLevelIdx := -1 + for i, line := range lines { + if isTopLevelKey(line) && strings.HasPrefix(strings.TrimSpace(line), "run-install-scripts:") { + topLevelIdx = i + break + } + } + if topLevelIdx == -1 { + return lines, false + } + + // Remove the top-level line + result := make([]string, 0, len(lines)) + for i, line := range lines { + if i != topLevelIdx { + result = append(result, line) + } + } + runInstallScriptsCodemodLog.Printf("Removed top-level 'run-install-scripts' on line %d", topLevelIdx+1) + + if alreadyNested { + // runtimes.node.run-install-scripts already exists; just remove the top-level duplicate. + runInstallScriptsCodemodLog.Print("'runtimes.node.run-install-scripts' already present – removed top-level duplicate only") + return result, true + } + + // Step 2: Detect the primary indentation unit used in the frontmatter + indent := detectFrontmatterIndent(result) + + // Step 3: Find the runtimes: block (top-level key) + runtimesIdx := -1 + for i, line := range result { + if isTopLevelKey(line) && strings.HasPrefix(strings.TrimSpace(line), "runtimes:") { + runtimesIdx = i + break + } + } + + if runtimesIdx == -1 { + // No runtimes block – append a new one at the end + newLines := []string{ + "runtimes:", + fmt.Sprintf("%snode:", indent), + fmt.Sprintf("%s%srun-install-scripts: %s", indent, indent, risStr), + } + runInstallScriptsCodemodLog.Print("No 'runtimes:' block found – appending new block") + return append(result, newLines...), true + } + + // Step 4: Find node: as a direct child of runtimes: + runtimesIndent := getIndentation(result[runtimesIdx]) + nodeIdx := -1 + for i := runtimesIdx + 1; i < len(result); i++ { + line := result[i] + trimmed := strings.TrimSpace(line) + if trimmed == "" { + continue + } + lineIndent := getIndentation(line) + if len(lineIndent) <= len(runtimesIndent) { + // Exited the runtimes block without finding node: + break + } + if strings.HasPrefix(trimmed, "node:") { + nodeIdx = i + runInstallScriptsCodemodLog.Printf("Found 'node:' sub-block at line %d", i+1) + break + } + } + + if nodeIdx == -1 { + // No node: sub-block – insert one right after runtimes: + nodeIndent := runtimesIndent + indent + nodeLines := []string{ + fmt.Sprintf("%snode:", nodeIndent), + fmt.Sprintf("%s%srun-install-scripts: %s", nodeIndent, indent, risStr), + } + newResult := make([]string, 0, len(result)+2) + newResult = append(newResult, result[:runtimesIdx+1]...) + newResult = append(newResult, nodeLines...) + newResult = append(newResult, result[runtimesIdx+1:]...) + runInstallScriptsCodemodLog.Print("No 'node:' sub-block found – inserting new one under 'runtimes:'") + return newResult, true + } + + // Step 5: node: exists – inject run-install-scripts right after node: + nodeIndent := getIndentation(result[nodeIdx]) + fieldLine := fmt.Sprintf("%s%srun-install-scripts: %s", nodeIndent, indent, risStr) + newResult := make([]string, 0, len(result)+1) + newResult = append(newResult, result[:nodeIdx+1]...) + newResult = append(newResult, fieldLine) + newResult = append(newResult, result[nodeIdx+1:]...) + runInstallScriptsCodemodLog.Printf("Injected 'run-install-scripts: %s' under existing 'node:' sub-block", risStr) + return newResult, true +} + +// detectFrontmatterIndent returns the indentation unit used in the YAML frontmatter +// by examining the first indented non-empty, non-comment line. Defaults to two spaces. +func detectFrontmatterIndent(lines []string) string { + for _, line := range lines { + trimmed := strings.TrimSpace(line) + if trimmed == "" || strings.HasPrefix(trimmed, "#") { + continue + } + ind := getIndentation(line) + if len(ind) > 0 { + return ind + } + } + return " " +} diff --git a/pkg/cli/codemod_run_install_scripts_test.go b/pkg/cli/codemod_run_install_scripts_test.go new file mode 100644 index 00000000000..3d9712bc179 --- /dev/null +++ b/pkg/cli/codemod_run_install_scripts_test.go @@ -0,0 +1,250 @@ +//go:build !integration + +package cli + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetRunInstallScriptsToRuntimesNodeCodemod_Metadata(t *testing.T) { + codemod := getRunInstallScriptsToRuntimesNodeCodemod() + + assert.Equal(t, "run-install-scripts-to-runtimes-node", codemod.ID) + assert.Equal(t, "Move run-install-scripts under runtimes.node", codemod.Name) + assert.NotEmpty(t, codemod.Description) + assert.NotEmpty(t, codemod.IntroducedIn) + require.NotNil(t, codemod.Apply) +} + +func TestRunInstallScriptsToRuntimesNode_NoOp(t *testing.T) { + tests := []struct { + name string + content string + frontmatter map[string]any + }{ + { + name: "no run-install-scripts field", + content: `--- +on: workflow_dispatch +--- + +# Test`, + frontmatter: map[string]any{ + "on": "workflow_dispatch", + }, + }, + { + name: "only runtimes.node.run-install-scripts (already nested)", + content: `--- +on: workflow_dispatch +runtimes: + node: + run-install-scripts: true +--- + +# Test`, + frontmatter: map[string]any{ + "on": "workflow_dispatch", + "runtimes": map[string]any{ + "node": map[string]any{ + "run-install-scripts": true, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + codemod := getRunInstallScriptsToRuntimesNodeCodemod() + result, applied, err := codemod.Apply(tt.content, tt.frontmatter) + require.NoError(t, err) + assert.False(t, applied, "Should not apply") + assert.Equal(t, tt.content, result, "Content should be unchanged") + }) + } +} + +func TestRunInstallScriptsToRuntimesNode_AppendNewRuntimesBlock(t *testing.T) { + codemod := getRunInstallScriptsToRuntimesNodeCodemod() + + content := `--- +on: workflow_dispatch +run-install-scripts: true +--- + +# Test` + + frontmatter := map[string]any{ + "on": "workflow_dispatch", + "run-install-scripts": true, + } + + result, applied, err := codemod.Apply(content, frontmatter) + + require.NoError(t, err) + assert.True(t, applied) + assert.NotContains(t, result, "\nrun-install-scripts: true\n") + assert.Contains(t, result, "runtimes:") + assert.Contains(t, result, " node:") + assert.Contains(t, result, " run-install-scripts: true") +} + +func TestRunInstallScriptsToRuntimesNode_FalseValue(t *testing.T) { + codemod := getRunInstallScriptsToRuntimesNodeCodemod() + + content := `--- +on: workflow_dispatch +run-install-scripts: false +--- + +# Test` + + frontmatter := map[string]any{ + "on": "workflow_dispatch", + "run-install-scripts": false, + } + + result, applied, err := codemod.Apply(content, frontmatter) + + require.NoError(t, err) + assert.True(t, applied) + assert.NotContains(t, result, "\nrun-install-scripts: false\n") + assert.Contains(t, result, " run-install-scripts: false") +} + +func TestRunInstallScriptsToRuntimesNode_ExistingRuntimesNoNode(t *testing.T) { + codemod := getRunInstallScriptsToRuntimesNodeCodemod() + + content := `--- +on: workflow_dispatch +run-install-scripts: true +runtimes: + python: + version: "3.11" +--- + +# Test` + + frontmatter := map[string]any{ + "on": "workflow_dispatch", + "run-install-scripts": true, + "runtimes": map[string]any{ + "python": map[string]any{ + "version": "3.11", + }, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + + require.NoError(t, err) + assert.True(t, applied) + assert.NotContains(t, result, "\nrun-install-scripts: true\n") + assert.Contains(t, result, "runtimes:") + assert.Contains(t, result, " node:") + assert.Contains(t, result, " run-install-scripts: true") + assert.Contains(t, result, " python:") +} + +func TestRunInstallScriptsToRuntimesNode_ExistingRuntimesWithNode(t *testing.T) { + codemod := getRunInstallScriptsToRuntimesNodeCodemod() + + content := `--- +on: workflow_dispatch +run-install-scripts: true +runtimes: + node: + version: "20" +--- + +# Test` + + frontmatter := map[string]any{ + "on": "workflow_dispatch", + "run-install-scripts": true, + "runtimes": map[string]any{ + "node": map[string]any{ + "version": "20", + }, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + + require.NoError(t, err) + assert.True(t, applied) + assert.NotContains(t, result, "\nrun-install-scripts: true\n") + assert.Contains(t, result, " node:") + assert.Contains(t, result, " run-install-scripts: true") + assert.Contains(t, result, " version: \"20\"") +} + +func TestRunInstallScriptsToRuntimesNode_IdempotentBothPresent(t *testing.T) { + codemod := getRunInstallScriptsToRuntimesNodeCodemod() + + // Top-level AND nested both present: remove top-level, keep nested. + content := `--- +on: workflow_dispatch +run-install-scripts: true +runtimes: + node: + run-install-scripts: true +--- + +# Test` + + frontmatter := map[string]any{ + "on": "workflow_dispatch", + "run-install-scripts": true, + "runtimes": map[string]any{ + "node": map[string]any{ + "run-install-scripts": true, + }, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + + require.NoError(t, err) + assert.True(t, applied) + // Top-level line should be gone + assert.NotContains(t, result, "\nrun-install-scripts: true\n") + // Nested line should still be there + assert.Contains(t, result, " run-install-scripts: true") +} + + +func TestRunInstallScriptsToRuntimesNode_PreservesOtherFields(t *testing.T) { + codemod := getRunInstallScriptsToRuntimesNodeCodemod() + + content := `--- +on: workflow_dispatch +run-install-scripts: true +permissions: + contents: read +--- + +# Test` + + frontmatter := map[string]any{ + "on": "workflow_dispatch", + "run-install-scripts": true, + "permissions": map[string]any{ + "contents": "read", + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + + require.NoError(t, err) + assert.True(t, applied) + assert.Contains(t, result, "permissions:") + assert.Contains(t, result, " contents: read") + assert.Contains(t, result, "runtimes:") + assert.Contains(t, result, " node:") + assert.Contains(t, result, " run-install-scripts: true") +} diff --git a/pkg/cli/fix_codemods.go b/pkg/cli/fix_codemods.go index 98ed45b11d5..3833a18fb8a 100644 --- a/pkg/cli/fix_codemods.go +++ b/pkg/cli/fix_codemods.go @@ -73,7 +73,8 @@ func GetAllCodemods() []Codemod { getSandboxMCPContainerRemovalCodemod(), // Remove deprecated sandbox.mcp.container (now managed internally) getSandboxMCPVersionRemovalCodemod(), // Remove deprecated sandbox.mcp.version (now managed internally) getSandboxAgentFalseRemovalCodemod(), // Remove deprecated sandbox.agent: false (rejected in strict mode) - getInferToDisableModelInvocationCodemod(), // Migrate deprecated 'infer' to 'disable-model-invocation' + getInferToDisableModelInvocationCodemod(), // Migrate deprecated 'infer' to 'disable-model-invocation' + getRunInstallScriptsToRuntimesNodeCodemod(), // Move top-level run-install-scripts under runtimes.node } fixCodemodsLog.Printf("Loaded codemod registry: %d codemods available", len(codemods)) return codemods diff --git a/pkg/cli/fix_codemods_test.go b/pkg/cli/fix_codemods_test.go index 7e332eeff0d..9878c1fa7ba 100644 --- a/pkg/cli/fix_codemods_test.go +++ b/pkg/cli/fix_codemods_test.go @@ -185,5 +185,6 @@ func expectedCodemodOrder() []string { "sandbox-mcp-version-removal", "sandbox-agent-false-removal", "infer-to-disable-model-invocation", + "run-install-scripts-to-runtimes-node", } } diff --git a/pkg/parser/import_field_extractor.go b/pkg/parser/import_field_extractor.go index 72971f8fe8d..dc76bf9b27c 100644 --- a/pkg/parser/import_field_extractor.go +++ b/pkg/parser/import_field_extractor.go @@ -639,26 +639,12 @@ func (acc *importAccumulator) extractRunInstallScripts(fm map[string]any, fullPa if acc.runInstallScripts { return } - if hasTopLevelRunInstallScripts(fm) { - acc.runInstallScripts = true - parserLog.Printf("Extracted run-install-scripts: true from import: %s", fullPath) - return - } if hasNodeRuntimeRunInstallScripts(fm) { acc.runInstallScripts = true parserLog.Printf("Extracted runtimes.node.run-install-scripts: true from import: %s", fullPath) } } -func hasTopLevelRunInstallScripts(fm map[string]any) bool { - rsAny, hasRS := fm["run-install-scripts"] - if !hasRS { - return false - } - rsBool, ok := rsAny.(bool) - return ok && rsBool -} - func hasNodeRuntimeRunInstallScripts(fm map[string]any) bool { runtimesAny, hasRuntimes := fm["runtimes"] if !hasRuntimes { diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index eb1157ee41a..33ad263f25e 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -11186,15 +11186,6 @@ false ] }, - "run-install-scripts": { - "type": "boolean", - "default": false, - "description": "Allow npm pre/post install scripts to execute during package installation. By default, --ignore-scripts is added to all generated npm install commands to prevent supply chain attacks via malicious install hooks. Setting run-install-scripts: true disables this protection globally (all runtimes). A supply chain security warning is emitted at compile time; in strict mode this is an error. Per-runtime control is also available via runtimes..run-install-scripts. See: https://github.github.com/gh-aw/reference/frontmatter/#run-install-scripts", - "examples": [ - false, - true - ] - }, "mcp-scripts": { "type": "object", "description": "MCP Scripts configuration for defining custom lightweight MCP tools as JavaScript, shell scripts, or Python scripts. Tools are mounted in an MCP server and have access to secrets specified by the user. Only one of 'script' (JavaScript), 'run' (shell), or 'py' (Python) must be specified per tool.", @@ -11481,7 +11472,7 @@ "run-install-scripts": { "type": "boolean", "default": false, - "description": "Allow npm pre/post install scripts to execute for this runtime during package installation. Overrides the global run-install-scripts setting for this specific runtime. Only affects runtimes that generate npm install commands (node). A supply chain security warning is emitted at compile time; in strict mode this is an error.", + "description": "Allow npm pre/post install scripts to execute for this runtime during package installation. Only affects runtimes that generate npm install commands (node). A supply chain security warning is emitted at compile time; in strict mode this is an error. See: https://github.github.com/gh-aw/reference/frontmatter/#run-install-scripts", "examples": [ false, true diff --git a/pkg/workflow/compiler_orchestrator_tools.go b/pkg/workflow/compiler_orchestrator_tools.go index 72a973fb72b..0c95b2ee654 100644 --- a/pkg/workflow/compiler_orchestrator_tools.go +++ b/pkg/workflow/compiler_orchestrator_tools.go @@ -184,9 +184,9 @@ func (c *Compiler) processToolsAndMarkdown(result *parser.FrontmatterResult, cle return nil, fmt.Errorf("failed to merge runtimes: %w", err) } - // Resolve run-install-scripts setting: true if global run-install-scripts is set, or if the node runtime - // has run-install-scripts: true, or if any imported workflow sets run-install-scripts (global or node-level). - runInstallScripts := resolveRunInstallScripts(result.Frontmatter, runtimes, importsResult.MergedRunInstallScripts) + // Resolve run-install-scripts setting: true if runtimes.node has run-install-scripts: true, + // or if any imported workflow sets run-install-scripts at the node-runtime level. + runInstallScripts := resolveRunInstallScripts(runtimes, importsResult.MergedRunInstallScripts) // Warn on deprecated APM configuration fields that are now ignored if importsVal, hasImports := result.Frontmatter["imports"]; hasImports { diff --git a/pkg/workflow/frontmatter_types.go b/pkg/workflow/frontmatter_types.go index 92fe12a603f..24fe04b0c27 100644 --- a/pkg/workflow/frontmatter_types.go +++ b/pkg/workflow/frontmatter_types.go @@ -289,10 +289,9 @@ type FrontmatterConfig struct { TrackerID string `json:"tracker-id,omitempty"` Version string `json:"version,omitempty"` TimeoutMinutes *TemplatableInt32 `json:"timeout-minutes,omitempty"` - Strict *bool `json:"strict,omitempty"` // Pointer to distinguish unset from false - Private *bool `json:"private,omitempty"` // If true, workflow cannot be added to other repositories - RunInstallScripts *bool `json:"run-install-scripts,omitempty"` // If true, allow pre/post install scripts globally (supply chain risk; emits warning or error in strict mode) - Labels []string `json:"labels,omitempty"` + Strict *bool `json:"strict,omitempty"` // Pointer to distinguish unset from false + Private *bool `json:"private,omitempty"` // If true, workflow cannot be added to other repositories + Labels []string `json:"labels,omitempty"` // Configuration sections - using strongly-typed structs Tools *ToolsConfig `json:"tools,omitempty"` diff --git a/pkg/workflow/run_install_scripts_validation.go b/pkg/workflow/run_install_scripts_validation.go index af5e900c3e1..9eb1ebe73cb 100644 --- a/pkg/workflow/run_install_scripts_validation.go +++ b/pkg/workflow/run_install_scripts_validation.go @@ -7,8 +7,8 @@ // chain security measure: malicious packages can use install hooks to exfiltrate // secrets or corrupt the runner environment. // -// Users can opt in to install scripts by setting run-install-scripts: true in their -// workflow frontmatter. This emits a security warning in non-strict mode and +// Users can opt in to install scripts by setting runtimes.node.run-install-scripts: true +// in their workflow frontmatter. This emits a security warning in non-strict mode and // is rejected as an error in strict mode. // // # Supported Flags (by package manager) @@ -19,10 +19,6 @@ // // # Configuration // -// Global (all runtimes): -// -// run-install-scripts: true -// // Per-runtime (node only, since it is the only runtime that generates install commands): // // runtimes: @@ -44,24 +40,15 @@ var runInstallScriptsLog = newValidationLogger("run_install_scripts") // the workflow frontmatter and any merged settings from imported shared workflows. // // Returns true (allow scripts) when any of the following is set: -// - Global run-install-scripts: true in the top-level frontmatter // - runtimes.node.run-install-scripts: true in the frontmatter // - mergedRunInstallScripts is true (any imported shared workflow enables run-install-scripts) -func resolveRunInstallScripts(frontmatter map[string]any, runtimes map[string]any, mergedRunInstallScripts bool) bool { +func resolveRunInstallScripts(runtimes map[string]any, mergedRunInstallScripts bool) bool { // Already enabled by an imported shared workflow if mergedRunInstallScripts { runInstallScriptsLog.Print("run-install-scripts enabled by imported shared workflow") return true } - // Check global run-install-scripts field - if rsAny, ok := frontmatter["run-install-scripts"]; ok { - if rsBool, ok := rsAny.(bool); ok && rsBool { - runInstallScriptsLog.Print("run-install-scripts enabled globally via run-install-scripts: true") - return true - } - } - // Check per-runtime run-install-scripts for node (the only runtime that generates npm install commands) if nodeAny, ok := runtimes["node"]; ok { if nodeMap, ok := nodeAny.(map[string]any); ok { diff --git a/pkg/workflow/run_install_scripts_validation_test.go b/pkg/workflow/run_install_scripts_validation_test.go index 533976b8837..a1f1e4d257c 100644 --- a/pkg/workflow/run_install_scripts_validation_test.go +++ b/pkg/workflow/run_install_scripts_validation_test.go @@ -13,38 +13,18 @@ import ( func TestResolveRunInstallScripts(t *testing.T) { tests := []struct { name string - frontmatter map[string]any runtimes map[string]any mergedRunInstallScripts bool expectedRunScript bool }{ { name: "default: run_scripts is false", - frontmatter: map[string]any{}, runtimes: map[string]any{}, mergedRunInstallScripts: false, expectedRunScript: false, }, { - name: "global run-install-scripts: true", - frontmatter: map[string]any{ - "run-install-scripts": true, - }, - runtimes: map[string]any{}, - mergedRunInstallScripts: false, - expectedRunScript: true, - }, - { - name: "global run-install-scripts: false", - frontmatter: map[string]any{"run-install-scripts": false}, - runtimes: map[string]any{}, - - mergedRunInstallScripts: false, - expectedRunScript: false, - }, - { - name: "per-runtime node run-install-scripts: true", - frontmatter: map[string]any{}, + name: "per-runtime node run-install-scripts: true", runtimes: map[string]any{ "node": map[string]any{ "run-install-scripts": true, @@ -54,8 +34,7 @@ func TestResolveRunInstallScripts(t *testing.T) { expectedRunScript: true, }, { - name: "per-runtime python run-install-scripts: true (no effect for npm installs)", - frontmatter: map[string]any{}, + name: "per-runtime python run-install-scripts: true (no effect for npm installs)", runtimes: map[string]any{ "python": map[string]any{ "run-install-scripts": true, @@ -66,17 +45,17 @@ func TestResolveRunInstallScripts(t *testing.T) { }, { name: "merged run-install-scripts from imported shared workflow", - frontmatter: map[string]any{}, runtimes: map[string]any{}, mergedRunInstallScripts: true, expectedRunScript: true, }, { - name: "merged run-install-scripts OR global: both true", - frontmatter: map[string]any{ - "run-install-scripts": true, + name: "merged and node-level: both true", + runtimes: map[string]any{ + "node": map[string]any{ + "run-install-scripts": true, + }, }, - runtimes: map[string]any{}, mergedRunInstallScripts: true, expectedRunScript: true, }, @@ -84,7 +63,7 @@ func TestResolveRunInstallScripts(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := resolveRunInstallScripts(tt.frontmatter, tt.runtimes, tt.mergedRunInstallScripts) + result := resolveRunInstallScripts(tt.runtimes, tt.mergedRunInstallScripts) assert.Equal(t, tt.expectedRunScript, result, "resolveRunInstallScripts() result mismatch") }) } @@ -181,7 +160,11 @@ func TestValidateRunInstallScripts_NotSet(t *testing.T) { assert.Equal(t, 0, c.GetWarningCount(), "Should not increment warning count") } -func TestFrontmatterConfig_RunInstallScripts(t *testing.T) { +func TestFrontmatterConfig_RunInstallScripts_TopLevel_Ignored(t *testing.T) { + // Top-level run-install-scripts is no longer a supported field; it must be set + // under runtimes.node instead. Unknown top-level keys are silently ignored by + // the JSON unmarshaler, so parsing should succeed without populating any + // run-install-scripts flag on the config itself. frontmatter := map[string]any{ "run-install-scripts": true, "engine": "claude", @@ -190,9 +173,6 @@ func TestFrontmatterConfig_RunInstallScripts(t *testing.T) { config, err := ParseFrontmatterConfig(frontmatter) require.NoError(t, err, "Should parse frontmatter without error") require.NotNil(t, config, "Config should not be nil") - - require.NotNil(t, config.RunInstallScripts, "RunInstallScripts should be set") - assert.True(t, *config.RunInstallScripts, "RunInstallScripts should be true") } func TestRuntimeConfig_RunInstallScripts(t *testing.T) { diff --git a/specs/compiler-threat-detection-spec.md b/specs/compiler-threat-detection-spec.md index c7b78d1fabf..55f180202f0 100644 --- a/specs/compiler-threat-detection-spec.md +++ b/specs/compiler-threat-detection-spec.md @@ -139,7 +139,7 @@ A conforming implementation MUST include detection coverage for at least the fol - **CTR-011 Network Firewall Configuration**: Validate network firewall configuration dependencies and domain patterns; reject configurations that declare firewall rules without required prerequisites (e.g., `allow-urls` without `ssl-bump`); reject wildcard `*` domains in strict mode. - **CTR-012 Safe-Outputs Wildcard Push Scope**: Detect misconfiguration patterns when `safe-outputs.push-to-pull-request-branch: target: "*"` is used; warn when no wildcard fetch pattern is present in checkout (suppressed for public repos) and when no access constraints (`title-prefix` or `labels`) are configured. - **CTR-013 Argument Injection via Package/Image Names**: Detect package or container image names that start with `-` (hyphen) in npm/npx, pip/uv, and Docker frontmatter configurations; reject these names before they are passed to `exec.Command` calls where they would be interpreted as CLI flags, enabling argument injection. -- **CTR-014 Supply Chain Attack via Install Scripts**: Detect when `run-install-scripts: true` is configured in workflow frontmatter (globally or per-runtime); warn in non-strict mode and reject in strict mode to protect against malicious npm pre/post install hooks that can exfiltrate secrets or corrupt the runner environment. +- **CTR-014 Supply Chain Attack via Install Scripts**: Detect when `run-install-scripts: true` is configured under `runtimes.node` in workflow frontmatter; warn in non-strict mode and reject in strict mode to protect against malicious npm pre/post install hooks that can exfiltrate secrets or corrupt the runner environment. - **CTR-015 Allowed Label Glob Scope**: Detect bare `*` wildcard patterns in `safe-outputs.*.allowed-labels` fields (`create-issue`, `create-discussion`, `update-discussion`, `create-pull-request`, `merge-pull-request`); reject compilation when such a pattern is present because it renders the label restriction ineffective and may allow the agent to apply labels that trigger unintended label-driven automation in the repository. - **CTR-016 Compile-Time Manifest Drift**: Detect when recompilation of an existing workflow would introduce new secrets or unapproved action references beyond what was previously approved in the lock file manifest; reject compilation when new restricted secrets or previously-absent action references appear, preventing adversarial workflow sources or prompt-injection from silently expanding the workflow's trust surface during routine updates. - **CTR-017 Secret Leakage via Environment Variables**: Detect secrets expressions (`${{ secrets.* }}`) in the top-level `env:` section, in `engine.env` (excluding allowed engine-required vars), and in custom step fields (`pre-steps`, `steps`, `pre-agent-steps`, `post-steps`) outside controlled `env:` bindings and `with:` inputs for `uses:` action steps; these placements expose secrets to the agent container environment. Warn in non-strict mode; reject in strict mode. @@ -312,7 +312,7 @@ The following test IDs map one-to-one to the CTR rules in Section 5.1. Each test | **T-CTR-011** | CTR-011 Network Firewall Configuration | Workflow declares `network: allowed: [some-domain]` with `ssl-bump: false` (or omits `ssl-bump` when required), or uses a wildcard `*` domain in strict mode | Compilation failure with error identifying the missing prerequisite or disallowed wildcard domain and providing the corrective configuration | `CTR-011` | | **T-CTR-012** | CTR-012 Safe-Outputs Wildcard Push Scope | Workflow uses `safe-outputs.push-to-pull-request-branch: target: "*"` without a wildcard fetch pattern in checkout (for non-public repos) or without `title-prefix` or `labels` access constraints | Compilation warning identifying the unconstrained wildcard scope and the missing checkout fetch pattern or access constraint; suppressed for public repositories | `CTR-012` | | **T-CTR-013** | CTR-013 Argument Injection via Package/Image Names | A workflow frontmatter declares an npm/npx package, a pip/uv package, or a Docker container image name that starts with `-` (e.g., `--privileged`, `-exploit`) | Compilation failure with error identifying the invalid name, the affected tool kind, and instructing the user to fix the package or image name | `CTR-013` | -| **T-CTR-014** | CTR-014 Supply Chain Attack via Install Scripts | A workflow frontmatter sets `run-install-scripts: true` (globally or under `runtimes.node`) | Compilation warning in non-strict mode identifying the supply chain risk and advising removal of `run-install-scripts: true`; compilation failure in strict mode | `CTR-014` | +| **T-CTR-014** | CTR-014 Supply Chain Attack via Install Scripts | A workflow frontmatter sets `runtimes.node.run-install-scripts: true` | Compilation warning in non-strict mode identifying the supply chain risk and advising removal of `run-install-scripts: true`; compilation failure in strict mode | `CTR-014` | | **T-CTR-015** | CTR-015 Allowed Label Glob Scope | A workflow frontmatter sets `safe-outputs.*.allowed-labels` to `["*"]` (bare wildcard) for any safe-output type that supports the field (`create-issue`, `create-discussion`, `update-discussion`, `create-pull-request`, `merge-pull-request`) | Compilation failure with error identifying the field name, explaining that `"*"` disables label restrictions and may permit unintended label-driven automation, and recommending specific names or narrower patterns | `CTR-015` | | **T-CTR-016** | CTR-016 Compile-Time Manifest Drift | An existing workflow lock file has a `gh-aw-manifest` section recording approved secrets and action references; when recompiled, the new workflow body introduces a secret not in the approved manifest (e.g., `MY_NEW_SECRET`) or a new action reference not previously recorded | Compilation failure with error identifying each new restricted secret and each added or removed action reference beyond the previously approved manifest baseline, preventing silent trust-surface expansion | `CTR-016` | | **T-CTR-017** | CTR-017 Secret Leakage via Environment Variables | A workflow frontmatter declares a secrets expression (e.g., `${{ secrets.MY_SECRET }}`) in the top-level `env:` section, in `engine.env` for a non-engine var, or in a custom step's `run:` field | Compilation warning in non-strict mode identifying the secrets expression and the section where it appears; compilation failure in strict mode | `CTR-017` |