diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 578e677a2b5..5a08e978d57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2976,10 +2976,9 @@ jobs: GH_TOKEN: ${{ github.token }} GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_REPOSITORY: ${{ github.repository }} + DIFC_PROXY_POLICY: '{"allow-only":{"repos":"all","min-integrity":"none"}}' run: | - bash actions/setup/sh/start_difc_proxy.sh \ - '{"allow-only":{"repos":"all","min-integrity":"none"}}' \ - "$DIFC_PROXY_IMAGE" + bash actions/setup/sh/start_difc_proxy.sh - name: Verify DIFC proxy started run: | diff --git a/.github/workflows/contribution-check.lock.yml b/.github/workflows/contribution-check.lock.yml index 9318afd0d22..08ce2d63ff0 100644 --- a/.github/workflows/contribution-check.lock.yml +++ b/.github/workflows/contribution-check.lock.yml @@ -767,6 +767,8 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index 626ff2703d1..3b14088061f 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -929,6 +929,8 @@ jobs: path: | /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl @@ -1244,6 +1246,14 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false + - name: Start DIFC proxy for pre-agent gh calls + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_SERVER_URL: ${{ github.server_url }} + DIFC_PROXY_POLICY: '{"allow-only":{"min-integrity":"approved","repos":"all"}}' + DIFC_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.2.11' + run: | + bash ${RUNNER_TEMP}/gh-aw/actions/start_difc_proxy.sh - name: Restore qmd index from cache id: qmd-cache-restore uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 @@ -1290,6 +1300,10 @@ jobs: with: key: gh-aw-qmd-2.0.1-${{ github.run_id }} path: /tmp/gh-aw/qmd-index/ + - name: Stop DIFC proxy + if: always() + continue-on-error: true + run: bash ${RUNNER_TEMP}/gh-aw/actions/stop_difc_proxy.sh safe_outputs: needs: diff --git a/.github/workflows/daily-issues-report.lock.yml b/.github/workflows/daily-issues-report.lock.yml index feab39ff743..e49e02f07a9 100644 --- a/.github/workflows/daily-issues-report.lock.yml +++ b/.github/workflows/daily-issues-report.lock.yml @@ -329,6 +329,17 @@ jobs: run: bash ${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh env: GH_TOKEN: ${{ github.token }} + - name: Start DIFC proxy for pre-agent gh calls + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_SERVER_URL: ${{ github.server_url }} + DIFC_PROXY_POLICY: '{"allow-only":{"min-integrity":"approved","repos":"all"}}' + DIFC_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.2.11' + run: | + bash ${RUNNER_TEMP}/gh-aw/actions/start_difc_proxy.sh + - name: Set GH_REPO for proxied steps + run: | + echo "GH_REPO=${GITHUB_REPOSITORY}" >> "$GITHUB_ENV" - name: Setup jq utilities directory run: "mkdir -p /tmp/gh-aw\ncat > /tmp/gh-aw/jqschema.sh << 'EOF'\n#!/usr/bin/env bash\n# jqschema.sh\njq -c '\ndef walk(f):\n . as $in |\n if type == \"object\" then\n reduce keys[] as $k ({}; . + {($k): ($in[$k] | walk(f))})\n elif type == \"array\" then\n if length == 0 then [] else [.[0] | walk(f)] end\n else\n type\n end;\nwalk(.)\n'\nEOF\nchmod +x /tmp/gh-aw/jqschema.sh" - name: Install gh CLI @@ -422,6 +433,10 @@ jobs: GH_AW_APPROVAL_LABELS_EXTRA: cookie GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh + - name: Stop DIFC proxy + if: always() + continue-on-error: true + run: bash ${RUNNER_TEMP}/gh-aw/actions/stop_difc_proxy.sh - name: Download container images run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 ghcr.io/github/gh-aw-mcpg:v0.2.11 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine - name: Write Safe Outputs Config @@ -883,6 +898,8 @@ jobs: /tmp/gh-aw/mcp-config/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/discussion-task-miner.lock.yml b/.github/workflows/discussion-task-miner.lock.yml index 3a95f8a262d..448fe37763b 100644 --- a/.github/workflows/discussion-task-miner.lock.yml +++ b/.github/workflows/discussion-task-miner.lock.yml @@ -801,6 +801,8 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/grumpy-reviewer.lock.yml b/.github/workflows/grumpy-reviewer.lock.yml index 617dd0b5735..33561a90bff 100644 --- a/.github/workflows/grumpy-reviewer.lock.yml +++ b/.github/workflows/grumpy-reviewer.lock.yml @@ -861,6 +861,8 @@ jobs: /tmp/gh-aw/mcp-config/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/issue-arborist.lock.yml b/.github/workflows/issue-arborist.lock.yml index c2de07de064..63258f8a763 100644 --- a/.github/workflows/issue-arborist.lock.yml +++ b/.github/workflows/issue-arborist.lock.yml @@ -300,6 +300,17 @@ jobs: run: bash ${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh env: GH_TOKEN: ${{ github.token }} + - name: Start DIFC proxy for pre-agent gh calls + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_SERVER_URL: ${{ github.server_url }} + DIFC_PROXY_POLICY: '{"allow-only":{"min-integrity":"approved","repos":"all"}}' + DIFC_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.2.11' + run: | + bash ${RUNNER_TEMP}/gh-aw/actions/start_difc_proxy.sh + - name: Set GH_REPO for proxied steps + run: | + echo "GH_REPO=${GITHUB_REPOSITORY}" >> "$GITHUB_ENV" - name: Setup jq utilities directory run: "mkdir -p /tmp/gh-aw\ncat > /tmp/gh-aw/jqschema.sh << 'EOF'\n#!/usr/bin/env bash\n# jqschema.sh\njq -c '\ndef walk(f):\n . as $in |\n if type == \"object\" then\n reduce keys[] as $k ({}; . + {($k): ($in[$k] | walk(f))})\n elif type == \"array\" then\n if length == 0 then [] else [.[0] | walk(f)] end\n else\n type\n end;\nwalk(.)\n'\nEOF\nchmod +x /tmp/gh-aw/jqschema.sh" - env: @@ -351,6 +362,10 @@ jobs: GH_AW_APPROVAL_LABELS_EXTRA: cookie GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh + - name: Stop DIFC proxy + if: always() + continue-on-error: true + run: bash ${RUNNER_TEMP}/gh-aw/actions/stop_difc_proxy.sh - name: Download container images run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 ghcr.io/github/gh-aw-mcpg:v0.2.11 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine - name: Write Safe Outputs Config @@ -801,6 +816,8 @@ jobs: /tmp/gh-aw/mcp-config/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index a6a3913c87e..9c68093c739 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -1115,6 +1115,8 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/issue-triage-agent.lock.yml b/.github/workflows/issue-triage-agent.lock.yml index 7743702ab1a..b2a9db7a368 100644 --- a/.github/workflows/issue-triage-agent.lock.yml +++ b/.github/workflows/issue-triage-agent.lock.yml @@ -715,6 +715,8 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/org-health-report.lock.yml b/.github/workflows/org-health-report.lock.yml index 71764afe5a3..6b9a7c39f8b 100644 --- a/.github/workflows/org-health-report.lock.yml +++ b/.github/workflows/org-health-report.lock.yml @@ -815,6 +815,8 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index a446c1b7358..4946c8268f7 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -808,6 +808,8 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/pr-triage-agent.lock.yml b/.github/workflows/pr-triage-agent.lock.yml index 8429b126692..1c760194712 100644 --- a/.github/workflows/pr-triage-agent.lock.yml +++ b/.github/workflows/pr-triage-agent.lock.yml @@ -795,6 +795,8 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index 67756514a36..d1942d9c1da 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -1030,6 +1030,8 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/refiner.lock.yml b/.github/workflows/refiner.lock.yml index 61d1f0bad3e..7959385c02d 100644 --- a/.github/workflows/refiner.lock.yml +++ b/.github/workflows/refiner.lock.yml @@ -778,6 +778,8 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index 454b031d2a5..63b8b015fd0 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -1053,6 +1053,8 @@ jobs: path: | /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/smoke-agent-all-merged.lock.yml b/.github/workflows/smoke-agent-all-merged.lock.yml index 7ca6d1116ae..c4a88cbfcce 100644 --- a/.github/workflows/smoke-agent-all-merged.lock.yml +++ b/.github/workflows/smoke-agent-all-merged.lock.yml @@ -761,6 +761,8 @@ jobs: /tmp/gh-aw/mcp-config/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/smoke-agent-all-none.lock.yml b/.github/workflows/smoke-agent-all-none.lock.yml index 5b1c9c8b567..570982c4c96 100644 --- a/.github/workflows/smoke-agent-all-none.lock.yml +++ b/.github/workflows/smoke-agent-all-none.lock.yml @@ -761,6 +761,8 @@ jobs: /tmp/gh-aw/mcp-config/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/smoke-agent-public-approved.lock.yml b/.github/workflows/smoke-agent-public-approved.lock.yml index 9e43516b76c..26e7eadea3f 100644 --- a/.github/workflows/smoke-agent-public-approved.lock.yml +++ b/.github/workflows/smoke-agent-public-approved.lock.yml @@ -788,6 +788,8 @@ jobs: /tmp/gh-aw/mcp-config/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/smoke-agent-public-none.lock.yml b/.github/workflows/smoke-agent-public-none.lock.yml index 77d9d63c6a0..3538968fc48 100644 --- a/.github/workflows/smoke-agent-public-none.lock.yml +++ b/.github/workflows/smoke-agent-public-none.lock.yml @@ -761,6 +761,8 @@ jobs: /tmp/gh-aw/mcp-config/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/smoke-agent-scoped-approved.lock.yml b/.github/workflows/smoke-agent-scoped-approved.lock.yml index 35037b3bf1b..6f2ac6bd8a0 100644 --- a/.github/workflows/smoke-agent-scoped-approved.lock.yml +++ b/.github/workflows/smoke-agent-scoped-approved.lock.yml @@ -766,6 +766,8 @@ jobs: /tmp/gh-aw/mcp-config/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 17cc40e5491..b9047e66147 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -1742,6 +1742,8 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/mcp-scripts/logs/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ diff --git a/.github/workflows/stale-repo-identifier.lock.yml b/.github/workflows/stale-repo-identifier.lock.yml index 5695c99fcd0..3f9e3993822 100644 --- a/.github/workflows/stale-repo-identifier.lock.yml +++ b/.github/workflows/stale-repo-identifier.lock.yml @@ -331,6 +331,17 @@ jobs: run: bash ${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh env: GH_TOKEN: ${{ github.token }} + - name: Start DIFC proxy for pre-agent gh calls + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_SERVER_URL: ${{ github.server_url }} + DIFC_PROXY_POLICY: '{"allow-only":{"min-integrity":"approved","repos":"all"}}' + DIFC_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.2.11' + run: | + bash ${RUNNER_TEMP}/gh-aw/actions/start_difc_proxy.sh + - name: Set GH_REPO for proxied steps + run: | + echo "GH_REPO=${GITHUB_REPOSITORY}" >> "$GITHUB_ENV" - name: Setup Python environment run: "# Create working directory for Python scripts\nmkdir -p /tmp/gh-aw/python\nmkdir -p /tmp/gh-aw/python/data\nmkdir -p /tmp/gh-aw/python/charts\nmkdir -p /tmp/gh-aw/python/artifacts\n\necho \"Python environment setup complete\"\necho \"Working directory: /tmp/gh-aw/python\"\necho \"Data directory: /tmp/gh-aw/python/data\"\necho \"Charts directory: /tmp/gh-aw/python/charts\"\necho \"Artifacts directory: /tmp/gh-aw/python/artifacts\"\n" - name: Install Python scientific libraries @@ -442,6 +453,10 @@ jobs: GH_AW_APPROVAL_LABELS_EXTRA: cookie GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh + - name: Stop DIFC proxy + if: always() + continue-on-error: true + run: bash ${RUNNER_TEMP}/gh-aw/actions/stop_difc_proxy.sh - name: Download container images run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 ghcr.io/github/gh-aw-mcpg:v0.2.11 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine - name: Write Safe Outputs Config @@ -869,6 +884,8 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/weekly-blog-post-writer.lock.yml b/.github/workflows/weekly-blog-post-writer.lock.yml index 54b454bf4a0..935f97d28ae 100644 --- a/.github/workflows/weekly-blog-post-writer.lock.yml +++ b/.github/workflows/weekly-blog-post-writer.lock.yml @@ -921,6 +921,8 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl @@ -1224,6 +1226,14 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false + - name: Start DIFC proxy for pre-agent gh calls + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_SERVER_URL: ${{ github.server_url }} + DIFC_PROXY_POLICY: '{"allow-only":{"min-integrity":"approved","repos":["github/gh-aw"]}}' + DIFC_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.2.11' + run: | + bash ${RUNNER_TEMP}/gh-aw/actions/start_difc_proxy.sh - name: Restore qmd index from cache id: qmd-cache-restore uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 @@ -1270,6 +1280,10 @@ jobs: with: key: gh-aw-qmd-2.0.1-${{ github.run_id }} path: /tmp/gh-aw/qmd-index/ + - name: Stop DIFC proxy + if: always() + continue-on-error: true + run: bash ${RUNNER_TEMP}/gh-aw/actions/stop_difc_proxy.sh push_repo_memory: needs: diff --git a/.github/workflows/weekly-issue-summary.lock.yml b/.github/workflows/weekly-issue-summary.lock.yml index 1bb18bc9870..967e8a59050 100644 --- a/.github/workflows/weekly-issue-summary.lock.yml +++ b/.github/workflows/weekly-issue-summary.lock.yml @@ -795,6 +795,8 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/weekly-safe-outputs-spec-review.lock.yml b/.github/workflows/weekly-safe-outputs-spec-review.lock.yml index b757c2e90b3..61da63a6506 100644 --- a/.github/workflows/weekly-safe-outputs-spec-review.lock.yml +++ b/.github/workflows/weekly-safe-outputs-spec-review.lock.yml @@ -727,6 +727,8 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/.github/workflows/workflow-generator.lock.yml b/.github/workflows/workflow-generator.lock.yml index 1453471c3ae..79f7e6c6c26 100644 --- a/.github/workflows/workflow-generator.lock.yml +++ b/.github/workflows/workflow-generator.lock.yml @@ -804,6 +804,8 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ /tmp/gh-aw/safeoutputs.jsonl diff --git a/actions/setup/js/handle_noop_message.test.cjs b/actions/setup/js/handle_noop_message.test.cjs index 3edaf72de64..b5eba9408f3 100644 --- a/actions/setup/js/handle_noop_message.test.cjs +++ b/actions/setup/js/handle_noop_message.test.cjs @@ -82,10 +82,17 @@ This issue helps you: }); // Mock core + const mockSummary = { + addRaw: vi.fn(), + write: vi.fn().mockResolvedValue(undefined), + }; + mockSummary.addRaw.mockReturnValue(mockSummary); mockCore = { info: vi.fn(), warning: vi.fn(), error: vi.fn(), + setOutput: vi.fn(), + summary: mockSummary, }; // Mock GitHub API @@ -137,16 +144,25 @@ This issue helps you: vi.clearAllMocks(); }); - it("should skip if no noop message is present", async () => { + it("should skip if no noop items in agent output", async () => { process.env.GH_AW_WORKFLOW_NAME = "Test Workflow"; process.env.GH_AW_RUN_URL = "https://github.com/test-owner/test-repo/actions/runs/123"; - process.env.GH_AW_NOOP_MESSAGE = ""; process.env.GH_AW_AGENT_CONCLUSION = "success"; + // Create agent output file with no noop items + const outputFile = path.join(tempDir, "agent_output.json"); + fs.writeFileSync( + outputFile, + JSON.stringify({ + items: [{ type: "create_issue", title: "Some issue" }], + }) + ); + process.env.GH_AW_AGENT_OUTPUT = outputFile; + const { main } = await import("./handle_noop_message.cjs?t=" + Date.now()); await main(); - expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("No no-op message found, skipping")); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("No noop items found in agent output")); expect(mockGithub.rest.search.issuesAndPullRequests).not.toHaveBeenCalled(); }); @@ -271,9 +287,18 @@ This issue helps you: it("should skip if agent conclusion is cancelled (not success or failure)", async () => { process.env.GH_AW_WORKFLOW_NAME = "Test Workflow"; process.env.GH_AW_RUN_URL = "https://github.com/test-owner/test-repo/actions/runs/123"; - process.env.GH_AW_NOOP_MESSAGE = "Some message"; process.env.GH_AW_AGENT_CONCLUSION = "cancelled"; + // Create agent output file with noop items so we reach the conclusion check + const outputFile = path.join(tempDir, "agent_output.json"); + fs.writeFileSync( + outputFile, + JSON.stringify({ + items: [{ type: "noop", message: "Some message" }], + }) + ); + process.env.GH_AW_AGENT_OUTPUT = outputFile; + const { main } = await import("./handle_noop_message.cjs?t=" + Date.now()); await main(); @@ -290,7 +315,7 @@ This issue helps you: const { main } = await import("./handle_noop_message.cjs?t=" + Date.now()); await main(); - expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("No agent output found, skipping")); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Could not load agent output, skipping")); expect(mockGithub.rest.search.issuesAndPullRequests).not.toHaveBeenCalled(); }); diff --git a/actions/setup/sh/start_difc_proxy.sh b/actions/setup/sh/start_difc_proxy.sh index 5a06ca321a8..1a430128512 100644 --- a/actions/setup/sh/start_difc_proxy.sh +++ b/actions/setup/sh/start_difc_proxy.sh @@ -3,20 +3,18 @@ # This script starts the awmg proxy container that routes gh CLI calls # through DIFC integrity filtering before the agent runs. # -# Arguments: -# $1 - POLICY: JSON guard policy string -# $2 - CONTAINER_IMAGE: Container image to use (e.g., ghcr.io/github/gh-aw-mcpg:v0.2.2) -# # Environment: -# GH_TOKEN - GitHub token passed to the proxy container -# GITHUB_SERVER_URL - GitHub server URL for upstream routing (e.g. https://github.com or https://TENANT.ghe.com) -# GITHUB_REPOSITORY - Repository name (owner/repo) for git remote -# GITHUB_ENV - Path to GitHub Actions environment file +# DIFC_PROXY_POLICY - JSON guard policy string +# DIFC_PROXY_IMAGE - Container image to use (e.g., ghcr.io/github/gh-aw-mcpg:v0.2.2) +# GH_TOKEN - GitHub token passed to the proxy container +# GITHUB_SERVER_URL - GitHub server URL for upstream routing (e.g. https://github.com or https://TENANT.ghe.com) +# GITHUB_REPOSITORY - Repository name (owner/repo) for git remote +# GITHUB_ENV - Path to GitHub Actions environment file set -e -POLICY="$1" -CONTAINER_IMAGE="$2" +POLICY="${DIFC_PROXY_POLICY:-}" +CONTAINER_IMAGE="${DIFC_PROXY_IMAGE:-}" if [ -z "$POLICY" ]; then echo "::warning::DIFC proxy policy not specified, skipping proxy start" diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index 24c9e24741a..47bd5011d6c 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -55,14 +55,235 @@ labels: [] metadata: {} -# Optional array of workflow specifications to import (similar to @include +# Workflow specifications to import. Supports array form (list of paths) or object +# form with 'aw' (agentic workflow paths) and 'apm-packages' (APM packages) +# subfields. +# (optional) +# This field supports multiple formats (oneOf): + +# Option 1: Array of workflow specifications to import (similar to @include # directives but defined in frontmatter). Format: owner/repo/path@ref (e.g., # githubnext/agentics/workflows/shared/common.md@v1.0.0). Can be strings or # objects with path and inputs. Any markdown files under .github/agents directory # are treated as custom agent files and only one agent file is allowed per # workflow. -# (optional) imports: [] + # Array items: undefined + +# Option 2: Object form of imports with 'aw' subfield for shared agentic workflow +# paths and 'apm-packages' subfield for APM packages. +imports: + # Array of shared agentic workflow specifications to import. Format: + # owner/repo/path@ref or relative paths. + # (optional) + aw: [] + + # APM package references to install. Supports array format (list of package slugs) + # or object format with packages and configuration fields. Replaces the top-level + # 'dependencies' field. + # (optional) + # This field supports multiple formats (oneOf): + + # Option 1: Simple array of APM package references. + apm-packages: [] + # Array items: APM package reference in the format 'org/repo' or + # 'org/repo/path/to/skill' + + # Option 2: Object format with packages and optional configuration. + apm-packages: + # List of APM package references to install. + packages: [] + # Array of APM package reference in the format 'org/repo' or + # 'org/repo/path/to/skill' + + # If true, agent restore step clears primitive dirs before unpacking. + # (optional) + isolated: true + + # GitHub App credentials for minting installation access tokens used by APM to + # access cross-org private repositories. + # (optional) + github-app: + # GitHub App ID (e.g., '${{ vars.APP_ID }}'). Required to mint a GitHub App token. + app-id: "example-value" + + # GitHub App private key (e.g., '${{ secrets.APP_PRIVATE_KEY }}'). Required to + # mint a GitHub App token. + private-key: "example-value" + + # Optional owner of the GitHub App installation (defaults to current repository + # owner if not specified) + # (optional) + owner: "example-value" + + # Optional list of repositories to grant access to (defaults to current repository + # if not specified) + # (optional) + repositories: [] + # Array of strings + + # Optional extra GitHub App-only permissions to merge into the minted token. Only + # takes effect for tools.github.github-app; ignored in other github-app contexts. + # (optional) + permissions: + # Permission level for repository administration (read/none; "write" is rejected + # by the compiler). GitHub App-only permission for repository administration. + # (optional) + administration: "read" + + # Permission level for Codespaces (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + codespaces: "read" + + # Permission level for Codespaces lifecycle administration (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + codespaces-lifecycle-admin: "read" + + # Permission level for Codespaces metadata (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + codespaces-metadata: "read" + + # Permission level for user email addresses (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + email-addresses: "read" + + # Permission level for repository environments (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + environments: "read" + + # Permission level for git signing (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + git-signing: "read" + + # Permission level for organization members (read/none; "write" is rejected by the + # compiler). Required for org team membership API calls. + # (optional) + members: "read" + + # Permission level for organization administration (read/none; "write" is rejected + # by the compiler). GitHub App-only permission. + # (optional) + organization-administration: "read" + + # Permission level for organization announcement banners (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-announcement-banners: "read" + + # Permission level for organization Codespaces (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-codespaces: "read" + + # Permission level for organization Copilot (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-copilot: "read" + + # Permission level for organization custom org roles (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-org-roles: "read" + + # Permission level for organization custom properties (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-properties: "read" + + # Permission level for organization custom repository roles (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-repository-roles: "read" + + # Permission level for organization events (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-events: "read" + + # Permission level for organization webhooks (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-hooks: "read" + + # Permission level for organization members management (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-members: "read" + + # Permission level for organization packages (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-packages: "read" + + # Permission level for organization personal access token requests (read/none; + # "write" is rejected by the compiler). GitHub App-only permission. + # (optional) + organization-personal-access-token-requests: "read" + + # Permission level for organization personal access tokens (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-personal-access-tokens: "read" + + # Permission level for organization plan (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-plan: "read" + + # Permission level for organization self-hosted runners (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-self-hosted-runners: "read" + + # Permission level for organization user blocking (read/none; "write" is rejected + # by the compiler). GitHub App-only permission. + # (optional) + organization-user-blocking: "read" + + # Permission level for repository custom properties (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + repository-custom-properties: "read" + + # Permission level for repository webhooks (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + repository-hooks: "read" + + # Permission level for single file access (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + single-file: "read" + + # Permission level for team discussions (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + team-discussions: "read" + + # Permission level for Dependabot vulnerability alerts (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + vulnerability-alerts: "read" + + # Permission level for GitHub Actions workflow files (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + workflows: "read" + + # Environment variables to set on the APM pack step. + # (optional) + env: + {} + + # GitHub token expression to authenticate APM with private package repositories. + # (optional) + github-token: "${{ secrets.GITHUB_TOKEN }}" # Optional list of additional workflow or action files that should be fetched # alongside this workflow when running 'gh aw add'. Entries are relative paths @@ -839,6 +1060,160 @@ on: repositories: [] # Array of strings + # Optional extra GitHub App-only permissions to merge into the minted token. Only + # takes effect for tools.github.github-app; ignored in other github-app contexts. + # (optional) + permissions: + # Permission level for repository administration (read/none; "write" is rejected + # by the compiler). GitHub App-only permission for repository administration. + # (optional) + administration: "read" + + # Permission level for Codespaces (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + codespaces: "read" + + # Permission level for Codespaces lifecycle administration (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + codespaces-lifecycle-admin: "read" + + # Permission level for Codespaces metadata (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + codespaces-metadata: "read" + + # Permission level for user email addresses (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + email-addresses: "read" + + # Permission level for repository environments (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + environments: "read" + + # Permission level for git signing (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + git-signing: "read" + + # Permission level for organization members (read/none; "write" is rejected by the + # compiler). Required for org team membership API calls. + # (optional) + members: "read" + + # Permission level for organization administration (read/none; "write" is rejected + # by the compiler). GitHub App-only permission. + # (optional) + organization-administration: "read" + + # Permission level for organization announcement banners (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-announcement-banners: "read" + + # Permission level for organization Codespaces (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-codespaces: "read" + + # Permission level for organization Copilot (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-copilot: "read" + + # Permission level for organization custom org roles (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-org-roles: "read" + + # Permission level for organization custom properties (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-properties: "read" + + # Permission level for organization custom repository roles (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-repository-roles: "read" + + # Permission level for organization events (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-events: "read" + + # Permission level for organization webhooks (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-hooks: "read" + + # Permission level for organization members management (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-members: "read" + + # Permission level for organization packages (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-packages: "read" + + # Permission level for organization personal access token requests (read/none; + # "write" is rejected by the compiler). GitHub App-only permission. + # (optional) + organization-personal-access-token-requests: "read" + + # Permission level for organization personal access tokens (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-personal-access-tokens: "read" + + # Permission level for organization plan (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-plan: "read" + + # Permission level for organization self-hosted runners (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-self-hosted-runners: "read" + + # Permission level for organization user blocking (read/none; "write" is rejected + # by the compiler). GitHub App-only permission. + # (optional) + organization-user-blocking: "read" + + # Permission level for repository custom properties (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + repository-custom-properties: "read" + + # Permission level for repository webhooks (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + repository-hooks: "read" + + # Permission level for single file access (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + single-file: "read" + + # Permission level for team discussions (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + team-discussions: "read" + + # Permission level for Dependabot vulnerability alerts (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + vulnerability-alerts: "read" + + # Permission level for GitHub Actions workflow files (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + workflows: "read" + # Steps to inject into the pre-activation job. These steps run after all built-in # checks (membership, stop-time, skip-if, etc.) and their results are exposed as # pre-activation outputs. Use 'id' on steps to reference their results via @@ -923,6 +1298,13 @@ on: # (optional) statuses: "read" + # When set to false, disables the frontmatter hash check step in the activation + # job. Default is true (check is enabled). Useful when the workflow source files + # are managed outside the default GitHub repo context (e.g. cross-repo org + # rulesets) and the stale check is not needed. + # (optional) + stale-check: true + # GitHub token permissions for the workflow. Controls what the GITHUB_TOKEN can # access during execution. Use the principle of least privilege - only grant the # minimum permissions needed. @@ -933,107 +1315,7 @@ on: # 'write-all' (all write permissions) permissions: "read-all" -# Option 2: Detailed permissions object with granular control over specific GitHub -# API scopes -permissions: - # Permission for GitHub Actions workflows and runs (read: view workflows, write: - # manage workflows, none: no access) - # (optional) - actions: "read" - - # Permission for artifact attestations (read: view attestations, write: create - # attestations, none: no access) - # (optional) - attestations: "read" - - # Permission for repository checks and status checks (read: view checks, write: - # create/update checks, none: no access) - # (optional) - checks: "read" - - # Permission for repository contents (read: view files, write: modify - # files/branches, none: no access) - # (optional) - contents: "read" - - # Permission for repository deployments (read: view deployments, write: - # create/update deployments, none: no access) - # (optional) - deployments: "read" - - # Permission for repository discussions (read: view discussions, write: - # create/update discussions, none: no access) - # (optional) - discussions: "read" - - # Permission level for OIDC token requests (write/none only - read is not - # supported). Allows workflows to request JWT tokens for cloud provider - # authentication. - # (optional) - id-token: "write" - - # Permission for repository issues (read: view issues, write: create/update/close - # issues, none: no access) - # (optional) - issues: "read" - - # Permission for GitHub Copilot models (read: access AI models for agentic - # workflows, none: no access) - # (optional) - models: "read" - - # Permission for repository metadata (read: view repository information, write: - # update repository metadata, none: no access) - # (optional) - metadata: "read" - - # Permission level for GitHub Packages (read/write/none). Controls access to - # publish, modify, or delete packages. - # (optional) - packages: "read" - - # Permission level for GitHub Pages (read/write/none). Controls access to deploy - # and manage GitHub Pages sites. - # (optional) - pages: "read" - - # Permission level for pull requests (read/write/none). Controls access to create, - # edit, review, and manage pull requests. - # (optional) - pull-requests: "read" - - # Permission level for repository projects (read/write/none). Controls access to - # manage repository-level GitHub Projects boards. - # (optional) - repository-projects: "read" - - # Permission level for organization projects (read/write/none). Controls access to - # manage organization-level GitHub Projects boards. - # (optional) - organization-projects: "read" - - # Permission level for security events (read/write/none). Controls access to view - # and manage code scanning alerts and security findings. - # (optional) - security-events: "read" - - # Permission level for commit statuses (read/write/none). Controls access to - # create and update commit status checks. - # (optional) - statuses: "read" - - # Permission level for Dependabot vulnerability alerts (read/write/none). GitHub - # App-only permission: required to access Dependabot alerts via the GitHub MCP - # server. The GITHUB_TOKEN does not have this permission — a GitHub App must be - # configured. - # (optional) - vulnerability-alerts: "read" - - # Permission shorthand that applies read access to all permission scopes. Can be - # combined with specific write permissions to override individual scopes. 'write' - # is not allowed for all. - # (optional) - all: "read" +# Option 2: undefined # Custom name for workflow runs that appears in the GitHub Actions interface # (supports GitHub expressions like ${{ github.event.issue.title }}) @@ -1081,12 +1363,29 @@ runs-on: labels: [] # Array of strings +# Runner for all framework/generated jobs (activation, pre-activation, +# safe-outputs, unlock, APM, etc.). Provides a compile-stable override for +# generated job runners without requiring a safe-outputs section. Overridden by +# safe-outputs.runs-on when both are set. Defaults to 'ubuntu-slim'. Use this when +# your infrastructure does not provide the default runner or when you need +# consistent runner selection across all jobs. +# (optional) +runs-on-slim: "example-value" + # Workflow timeout in minutes (GitHub Actions standard field). Defaults to 20 # minutes for agentic workflows. Has sensible defaults and can typically be -# omitted. +# omitted. Supports GitHub Actions expressions (e.g. '${{ inputs.timeout }}') for +# reusable workflow_call workflows. # (optional) +# This field supports multiple formats (oneOf): + +# Option 1: integer timeout-minutes: 1 +# Option 2: GitHub Actions expression that resolves to an integer (e.g. '${{ +# inputs.timeout }}') +timeout-minutes: "example-value" + # Concurrency control to limit concurrent workflow runs (GitHub Actions standard # field). Supports two forms: simple string for basic group isolation, or object # with cancel-in-progress option for advanced control. Agentic workflows enhance @@ -1482,7 +1781,9 @@ engine: # Optional version of the AI engine action (e.g., 'beta', 'stable', 20). Has # sensible defaults and can typically be omitted. Numeric values are automatically - # converted to strings at runtime. + # converted to strings at runtime. GitHub Actions expressions (e.g., '${{ + # inputs.engine-version }}') are accepted and compiled with injection-safe env var + # handling. # (optional) version: null @@ -1811,6 +2112,12 @@ tools: # (optional) lockdown: true + # Controls DIFC proxy injection for pre-agent gh CLI steps when guard policies + # (min-integrity) are configured. Default: true (enabled). Set to false to disable + # proxy injection and rely solely on MCP gateway-level filtering. + # (optional) + integrity-proxy: true + # Optional custom GitHub token (e.g., '${{ secrets.CUSTOM_PAT }}'). For 'remote' # type, defaults to GH_AW_GITHUB_TOKEN if not specified. # (optional) @@ -1869,8 +2176,8 @@ tools: blocked-users: "example-value" # Guard policy: GitHub usernames whose content is elevated to 'approved' integrity - # regardless of author_association. Allows specific external contributors to bypass - # 'min-integrity' checks without lowering the global policy. Precedence: + # regardless of author_association. Allows specific external contributors to + # bypass 'min-integrity' checks without lowering the global policy. Precedence: # blocked-users > trusted-users > approval-labels > author_association. Requires # 'min-integrity' to be set. Accepts an array of usernames, a comma-separated # string, a newline-separated string, or a GitHub Actions expression (e.g. '${{ @@ -1928,6 +2235,160 @@ tools: repositories: [] # Array of strings + # Optional extra GitHub App-only permissions to merge into the minted token. Only + # takes effect for tools.github.github-app; ignored in other github-app contexts. + # (optional) + permissions: + # Permission level for repository administration (read/none; "write" is rejected + # by the compiler). GitHub App-only permission for repository administration. + # (optional) + administration: "read" + + # Permission level for Codespaces (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + codespaces: "read" + + # Permission level for Codespaces lifecycle administration (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + codespaces-lifecycle-admin: "read" + + # Permission level for Codespaces metadata (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + codespaces-metadata: "read" + + # Permission level for user email addresses (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + email-addresses: "read" + + # Permission level for repository environments (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + environments: "read" + + # Permission level for git signing (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + git-signing: "read" + + # Permission level for organization members (read/none; "write" is rejected by the + # compiler). Required for org team membership API calls. + # (optional) + members: "read" + + # Permission level for organization administration (read/none; "write" is rejected + # by the compiler). GitHub App-only permission. + # (optional) + organization-administration: "read" + + # Permission level for organization announcement banners (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-announcement-banners: "read" + + # Permission level for organization Codespaces (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-codespaces: "read" + + # Permission level for organization Copilot (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-copilot: "read" + + # Permission level for organization custom org roles (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-org-roles: "read" + + # Permission level for organization custom properties (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-properties: "read" + + # Permission level for organization custom repository roles (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-repository-roles: "read" + + # Permission level for organization events (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-events: "read" + + # Permission level for organization webhooks (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-hooks: "read" + + # Permission level for organization members management (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-members: "read" + + # Permission level for organization packages (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-packages: "read" + + # Permission level for organization personal access token requests (read/none; + # "write" is rejected by the compiler). GitHub App-only permission. + # (optional) + organization-personal-access-token-requests: "read" + + # Permission level for organization personal access tokens (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-personal-access-tokens: "read" + + # Permission level for organization plan (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-plan: "read" + + # Permission level for organization self-hosted runners (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-self-hosted-runners: "read" + + # Permission level for organization user blocking (read/none; "write" is rejected + # by the compiler). GitHub App-only permission. + # (optional) + organization-user-blocking: "read" + + # Permission level for repository custom properties (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + repository-custom-properties: "read" + + # Permission level for repository webhooks (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + repository-hooks: "read" + + # Permission level for single file access (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + single-file: "read" + + # Permission level for team discussions (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + team-discussions: "read" + + # Permission level for Dependabot vulnerability alerts (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + vulnerability-alerts: "read" + + # Permission level for GitHub Actions workflow files (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + workflows: "read" + # Bash shell command execution tool. Supports wildcards: '*' (all commands), # 'command *' (command with any args, e.g., 'date *', 'echo *'). Default safe # commands: echo, ls, pwd, cat, head, tail, grep, wc, sort, uniq, date. @@ -2101,146 +2562,29 @@ tools: # Array items: object # Timeout in seconds for tool/MCP server operations. Applies to all tools and MCP - # servers if supported by the engine. Default varies by engine (Claude: 60s, - # Codex: 120s). + # servers if supported by the engine. Default: 60 seconds (for both Claude and + # Codex). Supports GitHub Actions expressions for reusable workflow_call + # workflows. # (optional) + # This field supports multiple formats (oneOf): + + # Option 1: integer timeout: 1 - # Timeout in seconds for MCP server startup. Applies to MCP server initialization - # if supported by the engine. Default: 120 seconds. - # (optional) - startup-timeout: 1 + # Option 2: GitHub Actions expression (e.g. '${{ inputs.tool-timeout }}') + timeout: "example-value" - # REMOVED: tools.serena has been removed. Use shared/mcp/serena.md instead: - # imports: - # - uses: shared/mcp/serena.md - # with: - # languages: ["go", "typescript"] - # Serena MCP server for AI-powered code intelligence with language service - # integration + # Timeout in seconds for MCP server startup. Applies to MCP server initialization + # if supported by the engine. Default: 120 seconds. Supports GitHub Actions + # expressions for reusable workflow_call workflows. # (optional) # This field supports multiple formats (oneOf): - # Option 1: Enable Serena with default settings - serena: null - - # Option 2: Short syntax: array of language identifiers to enable (e.g., ["go", - # "typescript"]). Note: rust does not generate a runtime setup step. - serena: [] - # Array items: string - - # Option 3: Serena configuration with custom version and language-specific - # settings - serena: - # Optional Serena MCP version. Numeric values are automatically converted to - # strings at runtime. - # (optional) - version: null - - # Serena execution mode (only 'docker' is supported, runs in container) - # (optional) - mode: "docker" - - # Optional additional arguments to append to the generated MCP server command - # (optional) - args: [] - # Array of strings - - # Language-specific configuration for Serena language services - # (optional) - languages: - # Configuration for Go language support in Serena code analysis. Enables - # Go-specific parsing, linting, and security checks. - # (optional) - # This field supports multiple formats (oneOf): - - # Option 1: Enable Go language service with default version - go: null - - # Option 2: object - go: - # Go version (e.g., "1.21", 1.21) - # (optional) - version: null - - # Path to go.mod file for Go version detection (e.g., "go.mod", "backend/go.mod") - # (optional) - go-mod-file: "example-value" - - # Version of gopls to install (e.g., "latest", "v0.14.2") - # (optional) - gopls-version: "example-value" - - # Configuration for TypeScript language support in Serena code analysis. Enables - # TypeScript-specific parsing, linting, and type checking. - # (optional) - # This field supports multiple formats (oneOf): - - # Option 1: Enable TypeScript language service with default version - typescript: null - - # Option 2: object - typescript: - # Node.js version for TypeScript (e.g., "22", 22) - # (optional) - version: null - - # Configuration for Python language support in Serena code analysis. Enables - # Python-specific parsing, linting, and security checks. - # (optional) - # This field supports multiple formats (oneOf): - - # Option 1: Enable Python language service with default version - python: null - - # Option 2: object - python: - # Python version (e.g., "3.12", 3.12) - # (optional) - version: null - - # Configuration for Java language support in Serena code analysis. Enables - # Java-specific parsing, linting, and security checks. - # (optional) - # This field supports multiple formats (oneOf): - - # Option 1: Enable Java language service with default version - java: null - - # Option 2: object - java: - # Java version (e.g., "21", 21) - # (optional) - version: null - - # Configuration for Rust language support in Serena code analysis. Enables - # Rust-specific parsing, linting, and security checks. Note: rust does not - # generate a runtime setup step in GitHub Actions. - # (optional) - # This field supports multiple formats (oneOf): - - # Option 1: Enable Rust language service with default version - rust: null - - # Option 2: object - rust: - # Rust version (e.g., "stable", "1.75") - # (optional) - version: null - - # Configuration for C# language support in Serena code analysis. Enables - # C#-specific parsing, linting, and security checks. - # (optional) - # This field supports multiple formats (oneOf): - - # Option 1: Enable C# language service with default version - csharp: null + # Option 1: integer + startup-timeout: 1 - # Option 2: object - csharp: - # .NET version for C# (e.g., "8.0", 8.0) - # (optional) - version: null + # Option 2: GitHub Actions expression (e.g. '${{ inputs.startup-timeout }}') + startup-timeout: "example-value" # Repo memory configuration for git-based persistent storage # (optional) @@ -3491,6 +3835,15 @@ safe-outputs: # (optional) fallback-as-issue: true + # When true (default), automatically appends a closing keyword ("Fixes #N") to the + # PR description when the workflow is triggered from an issue and no closing + # keyword is already present. This causes GitHub to auto-close the triggering + # issue when the PR is merged. Set to false to prevent this behavior, e.g., for + # partial-work PRs or multi-PR workflows. Accepts a boolean or a GitHub Actions + # expression. + # (optional) + auto-close-issue: null + # Token used to push an empty commit after PR creation to trigger CI events. Works # around the GITHUB_TOKEN limitation where pushes don't trigger workflow runs. # Defaults to the magic secret GH_AW_CI_TRIGGER_TOKEN if set in the repository. @@ -3538,6 +3891,12 @@ safe-outputs: excluded-files: [] # Array of strings + # Transport format for packaging changes. "am" (default) uses git format-patch/git + # am. "bundle" uses git bundle, which preserves merge commit topology, per-commit + # authorship, and merge-resolution-only content. + # (optional) + patch-format: "am" + # If true, emit step summary messages instead of making GitHub API calls for this # specific output type (preview mode) # (optional) @@ -4584,6 +4943,12 @@ safe-outputs: excluded-files: [] # Array of strings + # Transport format for packaging changes. "am" (default) uses git format-patch/git + # am. "bundle" uses git bundle, which preserves merge commit topology, per-commit + # authorship, and merge-resolution-only content. + # (optional) + patch-format: "am" + # Enable AI agents to minimize (hide) comments on issues or pull requests based on # relevance, spam detection, or moderation rules. # (optional) @@ -5043,6 +5408,160 @@ safe-outputs: repositories: [] # Array of strings + # Optional extra GitHub App-only permissions to merge into the minted token. Only + # takes effect for tools.github.github-app; ignored in other github-app contexts. + # (optional) + permissions: + # Permission level for repository administration (read/none; "write" is rejected + # by the compiler). GitHub App-only permission for repository administration. + # (optional) + administration: "read" + + # Permission level for Codespaces (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + codespaces: "read" + + # Permission level for Codespaces lifecycle administration (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + codespaces-lifecycle-admin: "read" + + # Permission level for Codespaces metadata (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + codespaces-metadata: "read" + + # Permission level for user email addresses (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + email-addresses: "read" + + # Permission level for repository environments (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + environments: "read" + + # Permission level for git signing (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + git-signing: "read" + + # Permission level for organization members (read/none; "write" is rejected by the + # compiler). Required for org team membership API calls. + # (optional) + members: "read" + + # Permission level for organization administration (read/none; "write" is rejected + # by the compiler). GitHub App-only permission. + # (optional) + organization-administration: "read" + + # Permission level for organization announcement banners (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-announcement-banners: "read" + + # Permission level for organization Codespaces (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-codespaces: "read" + + # Permission level for organization Copilot (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-copilot: "read" + + # Permission level for organization custom org roles (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-org-roles: "read" + + # Permission level for organization custom properties (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-properties: "read" + + # Permission level for organization custom repository roles (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-repository-roles: "read" + + # Permission level for organization events (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-events: "read" + + # Permission level for organization webhooks (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-hooks: "read" + + # Permission level for organization members management (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-members: "read" + + # Permission level for organization packages (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-packages: "read" + + # Permission level for organization personal access token requests (read/none; + # "write" is rejected by the compiler). GitHub App-only permission. + # (optional) + organization-personal-access-token-requests: "read" + + # Permission level for organization personal access tokens (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-personal-access-tokens: "read" + + # Permission level for organization plan (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-plan: "read" + + # Permission level for organization self-hosted runners (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-self-hosted-runners: "read" + + # Permission level for organization user blocking (read/none; "write" is rejected + # by the compiler). GitHub App-only permission. + # (optional) + organization-user-blocking: "read" + + # Permission level for repository custom properties (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + repository-custom-properties: "read" + + # Permission level for repository webhooks (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + repository-hooks: "read" + + # Permission level for single file access (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + single-file: "read" + + # Permission level for team discussions (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + team-discussions: "read" + + # Permission level for Dependabot vulnerability alerts (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + vulnerability-alerts: "read" + + # Permission level for GitHub Actions workflow files (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + workflows: "read" + # Maximum allowed size for git patches in kilobytes (KB). Defaults to 1024 KB (1 # MB). If patch exceeds this size, the job will fail. # (optional) @@ -5357,6 +5876,14 @@ secret-masking: # (optional) steps: [] +# Optional observability output settings for workflow runs. +# (optional) +observability: + # If set to 'on', append a compact observability section to the GitHub Actions job + # summary. Defaults to off when omitted. + # (optional) + job-summary: "on" + # Allow list of bot identifiers that can trigger the workflow even if they don't # meet the required role permissions. When the actor is in this list, the bot must # be active (installed) on the repository to trigger the workflow. @@ -5422,6 +5949,15 @@ strict: true # (optional) private: true +# Control whether the compile-agentic version update check runs in the activation +# job. When true (default), the activation job downloads config.json from the +# gh-aw repository and verifies the compiled version is not blocked and meets the +# minimum supported version. Set to false to disable the check (not allowed in +# strict mode). See: +# https://github.github.com/gh-aw/reference/frontmatter/#check-for-updates +# (optional) +check-for-updates: 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' @@ -5497,6 +6033,160 @@ dependencies: repositories: [] # Array of strings + # Optional extra GitHub App-only permissions to merge into the minted token. Only + # takes effect for tools.github.github-app; ignored in other github-app contexts. + # (optional) + permissions: + # Permission level for repository administration (read/none; "write" is rejected + # by the compiler). GitHub App-only permission for repository administration. + # (optional) + administration: "read" + + # Permission level for Codespaces (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + codespaces: "read" + + # Permission level for Codespaces lifecycle administration (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + codespaces-lifecycle-admin: "read" + + # Permission level for Codespaces metadata (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + codespaces-metadata: "read" + + # Permission level for user email addresses (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + email-addresses: "read" + + # Permission level for repository environments (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + environments: "read" + + # Permission level for git signing (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + git-signing: "read" + + # Permission level for organization members (read/none; "write" is rejected by the + # compiler). Required for org team membership API calls. + # (optional) + members: "read" + + # Permission level for organization administration (read/none; "write" is rejected + # by the compiler). GitHub App-only permission. + # (optional) + organization-administration: "read" + + # Permission level for organization announcement banners (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-announcement-banners: "read" + + # Permission level for organization Codespaces (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-codespaces: "read" + + # Permission level for organization Copilot (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-copilot: "read" + + # Permission level for organization custom org roles (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-org-roles: "read" + + # Permission level for organization custom properties (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-properties: "read" + + # Permission level for organization custom repository roles (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-repository-roles: "read" + + # Permission level for organization events (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-events: "read" + + # Permission level for organization webhooks (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-hooks: "read" + + # Permission level for organization members management (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-members: "read" + + # Permission level for organization packages (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-packages: "read" + + # Permission level for organization personal access token requests (read/none; + # "write" is rejected by the compiler). GitHub App-only permission. + # (optional) + organization-personal-access-token-requests: "read" + + # Permission level for organization personal access tokens (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-personal-access-tokens: "read" + + # Permission level for organization plan (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-plan: "read" + + # Permission level for organization self-hosted runners (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-self-hosted-runners: "read" + + # Permission level for organization user blocking (read/none; "write" is rejected + # by the compiler). GitHub App-only permission. + # (optional) + organization-user-blocking: "read" + + # Permission level for repository custom properties (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + repository-custom-properties: "read" + + # Permission level for repository webhooks (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + repository-hooks: "read" + + # Permission level for single file access (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + single-file: "read" + + # Permission level for team discussions (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + team-discussions: "read" + + # Permission level for Dependabot vulnerability alerts (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + vulnerability-alerts: "read" + + # Permission level for GitHub Actions workflow files (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + workflows: "read" + # Environment variables to set on the APM pack step (e.g., tokens or registry # URLs). # (optional) @@ -5533,6 +6223,170 @@ github-app: # (optional) repositories: [] # Array of strings + + # Optional extra GitHub App-only permissions to merge into the minted token. Only + # takes effect for tools.github.github-app; ignored in other github-app contexts. + # (optional) + permissions: + # Permission level for repository administration (read/none; "write" is rejected + # by the compiler). GitHub App-only permission for repository administration. + # (optional) + administration: "read" + + # Permission level for Codespaces (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + codespaces: "read" + + # Permission level for Codespaces lifecycle administration (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + codespaces-lifecycle-admin: "read" + + # Permission level for Codespaces metadata (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + codespaces-metadata: "read" + + # Permission level for user email addresses (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + email-addresses: "read" + + # Permission level for repository environments (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + environments: "read" + + # Permission level for git signing (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + git-signing: "read" + + # Permission level for organization members (read/none; "write" is rejected by the + # compiler). Required for org team membership API calls. + # (optional) + members: "read" + + # Permission level for organization administration (read/none; "write" is rejected + # by the compiler). GitHub App-only permission. + # (optional) + organization-administration: "read" + + # Permission level for organization announcement banners (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-announcement-banners: "read" + + # Permission level for organization Codespaces (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-codespaces: "read" + + # Permission level for organization Copilot (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-copilot: "read" + + # Permission level for organization custom org roles (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-org-roles: "read" + + # Permission level for organization custom properties (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-properties: "read" + + # Permission level for organization custom repository roles (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-custom-repository-roles: "read" + + # Permission level for organization events (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-events: "read" + + # Permission level for organization webhooks (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-hooks: "read" + + # Permission level for organization members management (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-members: "read" + + # Permission level for organization packages (read/none; "write" is rejected by + # the compiler). GitHub App-only permission. + # (optional) + organization-packages: "read" + + # Permission level for organization personal access token requests (read/none; + # "write" is rejected by the compiler). GitHub App-only permission. + # (optional) + organization-personal-access-token-requests: "read" + + # Permission level for organization personal access tokens (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-personal-access-tokens: "read" + + # Permission level for organization plan (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + organization-plan: "read" + + # Permission level for organization self-hosted runners (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + organization-self-hosted-runners: "read" + + # Permission level for organization user blocking (read/none; "write" is rejected + # by the compiler). GitHub App-only permission. + # (optional) + organization-user-blocking: "read" + + # Permission level for repository custom properties (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + repository-custom-properties: "read" + + # Permission level for repository webhooks (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + repository-hooks: "read" + + # Permission level for single file access (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + single-file: "read" + + # Permission level for team discussions (read/none; "write" is rejected by the + # compiler). GitHub App-only permission. + # (optional) + team-discussions: "read" + + # Permission level for Dependabot vulnerability alerts (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + vulnerability-alerts: "read" + + # Permission level for GitHub Actions workflow files (read/none; "write" is + # rejected by the compiler). GitHub App-only permission. + # (optional) + workflows: "read" + +# Schema for validating 'with' input values when this workflow is imported by +# another workflow using the 'uses'/'with' syntax. Defines the expected +# parameters, their types, and whether they are required. Scalar inputs are +# accessible via '${{ github.aw.import-inputs. }}' expressions. Object +# inputs (type: object) allow one-level deep sub-fields accessible via '${{ +# github.aw.import-inputs.. }}' expressions. +# (optional) +import-schema: + {} --- ``` diff --git a/docs/src/content/docs/reference/frontmatter.md b/docs/src/content/docs/reference/frontmatter.md index b68d24c0709..39d4a1f4abe 100644 --- a/docs/src/content/docs/reference/frontmatter.md +++ b/docs/src/content/docs/reference/frontmatter.md @@ -418,21 +418,22 @@ Debug workflow using script mode for custom actions. **Note:** The `action-mode` can also be overridden via the CLI flag `--action-mode` or the environment variable `GH_AW_ACTION_MODE`. The precedence is: CLI flag > feature flag > environment variable > auto-detection. -#### DIFC Proxy (`features.difc-proxy`) +#### DIFC Proxy (`tools.github.integrity-proxy`) -Opt in to DIFC (Data Integrity and Flow Control) proxy injection. When enabled alongside `tools.github.min-integrity`, the compiler inserts proxy steps around the agent that enforce integrity-level isolation at the network boundary. +Controls DIFC (Data Integrity and Flow Control) proxy injection. When `tools.github.min-integrity` is configured, the compiler inserts proxy steps around the agent that enforce integrity-level isolation at the network boundary. The proxy is **enabled by default** — set `integrity-proxy: false` to opt out. ```yaml wrap -features: - difc-proxy: true tools: github: min-integrity: approved + # integrity-proxy: false # uncomment to disable proxy injection ``` -Without this flag, configuring `min-integrity` alone performs content filtering at the MCP gateway level but does not inject the additional proxy steps. Enable `difc-proxy` when you need the full proxy-based enforcement. +Without `min-integrity`, `integrity-proxy` has no effect. When both are configured, the proxy enforces network-boundary integrity filtering in addition to the MCP gateway-level filtering. Set `integrity-proxy: false` when you only need gateway-level filtering. -The flag can also be set via the environment variable `GH_AW_FEATURES=difc-proxy`. +:::note[Migration] +The deprecated `features.difc-proxy: true` flag is replaced by this field. Run `gh aw fix` to automatically migrate existing workflows. +::: ### AI Engine (`engine:`) diff --git a/docs/src/content/docs/reference/glossary.md b/docs/src/content/docs/reference/glossary.md index 7c749e4d84f..7053b6225d1 100644 --- a/docs/src/content/docs/reference/glossary.md +++ b/docs/src/content/docs/reference/glossary.md @@ -99,9 +99,9 @@ A guardrail feature that controls which GitHub content an agent can access, filt Two additional fields extend integrity filtering beyond the level threshold: `blocked-users` unconditionally denies content from listed GitHub usernames regardless of level, and `approval-labels` promotes items bearing any listed label to `approved` integrity, enabling human-review workflows. See [Integrity Filtering](/gh-aw/reference/integrity/). -### DIFC Proxy (`features.difc-proxy`) +### DIFC Proxy (`tools.github.integrity-proxy`) -An opt-in feature flag enabling full Data Integrity and Flow Control (DIFC) proxy enforcement. When enabled alongside `tools.github.min-integrity`, the compiler injects proxy steps around the agent job that enforce integrity-level isolation at the network boundary. Without `difc-proxy`, `min-integrity` alone performs content filtering only at the MCP gateway level. Enable it when network-boundary enforcement is required in addition to gateway-level filtering. Filtered content is recorded as `DIFC_FILTERED` events in `gateway.jsonl` for later inspection. Can also be enabled via the `GH_AW_FEATURES=difc-proxy` environment variable. See [Integrity Filtering](/gh-aw/reference/integrity/). +Controls full Data Integrity and Flow Control (DIFC) proxy enforcement. When `tools.github.min-integrity` is configured, the compiler injects proxy steps around the agent job that enforce integrity-level isolation at the network boundary. The proxy is **enabled by default** — set `tools.github.integrity-proxy: false` to disable it and rely solely on MCP gateway-level filtering. Filtered content is recorded as `DIFC_FILTERED` events in `gateway.jsonl` for later inspection. See [Integrity Filtering](/gh-aw/reference/integrity/). ### Status Comment diff --git a/pkg/cli/codemod_difc_proxy.go b/pkg/cli/codemod_difc_proxy.go new file mode 100644 index 00000000000..4a5d3f5a627 --- /dev/null +++ b/pkg/cli/codemod_difc_proxy.go @@ -0,0 +1,176 @@ +package cli + +import ( + "strings" + + "github.com/github/gh-aw/pkg/logger" +) + +var difcProxyCodemodLog = logger.New("cli:codemod_difc_proxy") + +// getDIFCProxyToIntegrityProxyCodemod creates a codemod that migrates the deprecated +// 'features.difc-proxy' flag to the new 'tools.github.integrity-proxy' field. +// +// Migration rules: +// - features.difc-proxy: true → remove from features (proxy is enabled by default) +// - features.difc-proxy: false → remove from features AND add +// tools.github.integrity-proxy: false (to preserve the opt-out intent) +func getDIFCProxyToIntegrityProxyCodemod() Codemod { + return Codemod{ + ID: "features-difc-proxy-to-tools-github", + Name: "Migrate 'features.difc-proxy' to 'tools.github.integrity-proxy'", + Description: "Removes the deprecated 'features.difc-proxy' flag. The DIFC proxy is now enabled by default when guard policies are configured. If the flag was set to false, adds 'tools.github.integrity-proxy: false' to preserve the opt-out.", + IntroducedIn: "1.0.0", + Apply: func(content string, frontmatter map[string]any) (string, bool, error) { + // Check if features.difc-proxy exists + flagValue, hasDIFCProxy := getDIFCProxyFlagValue(frontmatter) + if !hasDIFCProxy { + return content, false, nil + } + + // Determine if we need to add integrity-proxy: false to tools.github + addDisableFlag := !flagValue && hasToolsGithubMap(frontmatter) + + newContent, applied, err := applyFrontmatterLineTransform(content, func(lines []string) ([]string, bool) { + // Step 1: remove features.difc-proxy + result, modified := removeFieldFromBlock(lines, "difc-proxy", "features") + if !modified { + return lines, false + } + difcProxyCodemodLog.Print("Removed features.difc-proxy") + + // Step 2: add integrity-proxy: false to tools.github if needed + if addDisableFlag { + result = addIntegrityProxyFalseToToolsGitHub(result) + } + + return result, true + }) + if applied { + if addDisableFlag { + difcProxyCodemodLog.Print("Migrated features.difc-proxy: false → tools.github.integrity-proxy: false") + } else { + difcProxyCodemodLog.Print("Removed features.difc-proxy: true (proxy is now enabled by default)") + } + } + return newContent, applied, err + }, + } +} + +// getDIFCProxyFlagValue returns the boolean value of features.difc-proxy and whether it exists. +// Note: string values are checked case-insensitively; "false" returns false, any other non-empty +// string returns true. This matches the existing behavior of isFeatureEnabled in features.go. +func getDIFCProxyFlagValue(frontmatter map[string]any) (bool, bool) { + featuresAny, hasFeatures := frontmatter["features"] + if !hasFeatures { + return false, false + } + featuresMap, ok := featuresAny.(map[string]any) + if !ok { + return false, false + } + val, hasFlag := featuresMap["difc-proxy"] + if !hasFlag { + return false, false + } + if boolVal, ok := val.(bool); ok { + return boolVal, true + } + // Handle string values: "false" is treated as false; other non-empty strings as true. + if strVal, ok := val.(string); ok { + if strings.EqualFold(strVal, "false") { + return false, true + } + return strVal != "", true + } + return false, true +} + +// hasToolsGithubMap returns true if frontmatter has tools.github as a map (not just true/false). +func hasToolsGithubMap(frontmatter map[string]any) bool { + toolsAny, hasTools := frontmatter["tools"] + if !hasTools { + return false + } + toolsMap, ok := toolsAny.(map[string]any) + if !ok { + return false + } + githubAny, hasGitHub := toolsMap["github"] + if !hasGitHub { + return false + } + _, ok = githubAny.(map[string]any) + return ok +} + +// addIntegrityProxyFalseToToolsGitHub inserts 'integrity-proxy: false' inside the +// tools.github block. The line is inserted immediately after the 'github:' key line. +// The indentation is derived from the first existing sub-field inside the github block. +func addIntegrityProxyFalseToToolsGitHub(lines []string) []string { + var result []string + var inTools bool + var toolsIndent string + var inGitHub bool + var githubIndent string + var fieldInserted bool + + for _, line := range lines { + trimmed := strings.TrimSpace(line) + + // Track if we're in the tools block + if strings.HasPrefix(trimmed, "tools:") { + inTools = true + toolsIndent = getIndentation(line) + result = append(result, line) + continue + } + + // Check if we've left the tools block + if inTools && len(trimmed) > 0 && !strings.HasPrefix(trimmed, "#") { + if hasExitedBlock(line, toolsIndent) { + inTools = false + inGitHub = false + } + } + + // Detect 'github:' inside tools + if inTools && !inGitHub && strings.HasPrefix(trimmed, "github:") { + inGitHub = true + githubIndent = getIndentation(line) + result = append(result, line) + continue + } + + // Inside github block: inject integrity-proxy: false before the first sub-field + if inGitHub && !fieldInserted && len(trimmed) > 0 && !strings.HasPrefix(trimmed, "#") { + if hasExitedBlock(line, githubIndent) { + // Exited github block without seeing any sub-fields; use default indentation + fieldIndent := githubIndent + " " + result = append(result, fieldIndent+"integrity-proxy: false") + difcProxyCodemodLog.Printf("Added integrity-proxy: false to tools.github (before exit)") + fieldInserted = true + inGitHub = false + } else { + // Use the indentation of the first existing sub-field + fieldIndent := getIndentation(line) + result = append(result, fieldIndent+"integrity-proxy: false") + difcProxyCodemodLog.Printf("Added integrity-proxy: false to tools.github") + fieldInserted = true + inGitHub = false + } + } + + result = append(result, line) + } + + // Edge case: github block was the last entry in the file + if inGitHub && !fieldInserted { + fieldIndent := githubIndent + " " + result = append(result, fieldIndent+"integrity-proxy: false") + difcProxyCodemodLog.Printf("Added integrity-proxy: false to tools.github (end of file)") + } + + return result +} diff --git a/pkg/cli/codemod_difc_proxy_test.go b/pkg/cli/codemod_difc_proxy_test.go new file mode 100644 index 00000000000..bf6bad32968 --- /dev/null +++ b/pkg/cli/codemod_difc_proxy_test.go @@ -0,0 +1,276 @@ +//go:build !integration + +package cli + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDIFCProxyToIntegrityProxyCodemod(t *testing.T) { + codemod := getDIFCProxyToIntegrityProxyCodemod() + + t.Run("removes features.difc-proxy: true (no-op since proxy is now default)", func(t *testing.T) { + content := `--- +engine: copilot +features: + difc-proxy: true +tools: + github: + min-integrity: approved +--- + +# Test Workflow +` + frontmatter := map[string]any{ + "engine": "copilot", + "features": map[string]any{ + "difc-proxy": true, + }, + "tools": map[string]any{ + "github": map[string]any{ + "min-integrity": "approved", + }, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + require.NoError(t, err, "Should not error") + assert.True(t, applied, "Should have applied the codemod") + assert.NotContains(t, result, "difc-proxy", "Should remove features.difc-proxy") + assert.NotContains(t, result, "integrity-proxy", "Should not add integrity-proxy when was true") + assert.Contains(t, result, "min-integrity: approved", "Should preserve min-integrity") + }) + + t.Run("removes features.difc-proxy: false and adds tools.github.integrity-proxy: false", func(t *testing.T) { + content := `--- +engine: copilot +features: + difc-proxy: false +tools: + github: + min-integrity: approved +--- + +# Test Workflow +` + frontmatter := map[string]any{ + "engine": "copilot", + "features": map[string]any{ + "difc-proxy": false, + }, + "tools": map[string]any{ + "github": map[string]any{ + "min-integrity": "approved", + }, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + require.NoError(t, err, "Should not error") + assert.True(t, applied, "Should have applied the codemod") + assert.NotContains(t, result, "difc-proxy", "Should remove features.difc-proxy") + assert.Contains(t, result, "integrity-proxy: false", "Should add integrity-proxy: false to tools.github") + assert.Contains(t, result, "min-integrity: approved", "Should preserve min-integrity") + }) + + t.Run("removes features.difc-proxy: false but does NOT add integrity-proxy when tools.github is absent", func(t *testing.T) { + content := `--- +engine: copilot +features: + difc-proxy: false +--- + +# Test Workflow +` + frontmatter := map[string]any{ + "engine": "copilot", + "features": map[string]any{ + "difc-proxy": false, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + require.NoError(t, err, "Should not error") + assert.True(t, applied, "Should have applied the codemod") + assert.NotContains(t, result, "difc-proxy", "Should remove features.difc-proxy") + assert.NotContains(t, result, "integrity-proxy", "Should not add integrity-proxy when tools.github absent") + }) + + t.Run("removes features.difc-proxy: false but does NOT add integrity-proxy when tools.github is boolean true", func(t *testing.T) { + content := `--- +engine: copilot +features: + difc-proxy: false +tools: + github: true +--- + +# Test Workflow +` + frontmatter := map[string]any{ + "engine": "copilot", + "features": map[string]any{ + "difc-proxy": false, + }, + "tools": map[string]any{ + "github": true, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + require.NoError(t, err, "Should not error") + assert.True(t, applied, "Should have applied the codemod") + assert.NotContains(t, result, "difc-proxy", "Should remove features.difc-proxy") + assert.NotContains(t, result, "integrity-proxy", "Should not add integrity-proxy when tools.github is boolean") + }) + + t.Run("does not modify workflows without features.difc-proxy", func(t *testing.T) { + content := `--- +engine: copilot +tools: + github: + min-integrity: approved +--- + +# Test Workflow +` + frontmatter := map[string]any{ + "engine": "copilot", + "tools": map[string]any{ + "github": map[string]any{ + "min-integrity": "approved", + }, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + require.NoError(t, err, "Should not error") + assert.False(t, applied, "Should not apply when features.difc-proxy absent") + assert.Equal(t, content, result, "Content should be unchanged") + }) + + t.Run("does not modify workflows without features section", func(t *testing.T) { + content := `--- +engine: copilot +--- + +# Test Workflow +` + frontmatter := map[string]any{ + "engine": "copilot", + } + + result, applied, err := codemod.Apply(content, frontmatter) + require.NoError(t, err, "Should not error") + assert.False(t, applied, "Should not apply when features absent") + assert.Equal(t, content, result, "Content should be unchanged") + }) + + t.Run("preserves other features when removing difc-proxy", func(t *testing.T) { + content := `--- +engine: copilot +features: + action-mode: true + difc-proxy: true +tools: + github: + min-integrity: approved +--- + +# Test Workflow +` + frontmatter := map[string]any{ + "engine": "copilot", + "features": map[string]any{ + "action-mode": true, + "difc-proxy": true, + }, + "tools": map[string]any{ + "github": map[string]any{ + "min-integrity": "approved", + }, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + require.NoError(t, err, "Should not error") + assert.True(t, applied, "Should have applied the codemod") + assert.NotContains(t, result, "difc-proxy", "Should remove difc-proxy") + assert.Contains(t, result, "action-mode: true", "Should preserve other features") + }) + + t.Run("integrity-proxy: false is added inside tools.github block", func(t *testing.T) { + content := `--- +engine: copilot +features: + difc-proxy: false +tools: + github: + mode: local + toolsets: [default] + min-integrity: approved + allowed-repos: all +--- + +# Test Workflow +` + frontmatter := map[string]any{ + "engine": "copilot", + "features": map[string]any{ + "difc-proxy": false, + }, + "tools": map[string]any{ + "github": map[string]any{ + "mode": "local", + "toolsets": []any{"default"}, + "min-integrity": "approved", + "allowed-repos": "all", + }, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + require.NoError(t, err, "Should not error") + assert.True(t, applied, "Should have applied the codemod") + assert.Contains(t, result, "integrity-proxy: false", "Should add integrity-proxy: false") + assert.Contains(t, result, "min-integrity: approved", "Should preserve min-integrity") + assert.Contains(t, result, "allowed-repos: all", "Should preserve allowed-repos") + assert.NotContains(t, result, "difc-proxy", "Should remove difc-proxy from features") + // Verify integrity-proxy uses same indentation as other fields (4-space context) + assert.Contains(t, result, " integrity-proxy: false", "integrity-proxy: false should use same indentation as other fields") + }) + + t.Run("string 'false' value treated as false (adds integrity-proxy: false)", func(t *testing.T) { + content := `--- +engine: copilot +features: + difc-proxy: "false" +tools: + github: + min-integrity: approved +--- + +# Test Workflow +` + frontmatter := map[string]any{ + "engine": "copilot", + "features": map[string]any{ + "difc-proxy": "false", + }, + "tools": map[string]any{ + "github": map[string]any{ + "min-integrity": "approved", + }, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + require.NoError(t, err, "Should not error") + assert.True(t, applied, "Should have applied the codemod") + assert.NotContains(t, result, "difc-proxy", "Should remove features.difc-proxy") + assert.Contains(t, result, "integrity-proxy: false", "String 'false' should be treated as false") + }) +} diff --git a/pkg/cli/fix_codemods.go b/pkg/cli/fix_codemods.go index 9d34534dae6..c9a682e361f 100644 --- a/pkg/cli/fix_codemods.go +++ b/pkg/cli/fix_codemods.go @@ -50,6 +50,7 @@ func GetAllCodemods() []Codemod { getSafeInputsToMCPScriptsCodemod(), // Rename safe-inputs to mcp-scripts getPluginsToDependenciesCodemod(), // Migrate plugins to dependencies (plugins removed in favour of APM) getGitHubReposToAllowedReposCodemod(), // Rename deprecated tools.github.repos to tools.github.allowed-repos + getDIFCProxyToIntegrityProxyCodemod(), // Migrate deprecated features.difc-proxy to tools.github.integrity-proxy } 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 e316943126f..feddc2e0d78 100644 --- a/pkg/cli/fix_codemods_test.go +++ b/pkg/cli/fix_codemods_test.go @@ -43,7 +43,7 @@ func TestGetAllCodemods_ReturnsAllCodemods(t *testing.T) { codemods := GetAllCodemods() // Verify we have the expected number of codemods - expectedCount := 28 + expectedCount := 29 assert.Len(t, codemods, expectedCount, "Should return all %d codemods", expectedCount) // Verify all codemods have required fields @@ -133,6 +133,7 @@ func TestGetAllCodemods_InExpectedOrder(t *testing.T) { "safe-inputs-to-mcp-scripts", "plugins-to-dependencies", "github-repos-to-allowed-repos", + "features-difc-proxy-to-tools-github", } require.Len(t, codemods, len(expectedOrder), "Should have expected number of codemods") diff --git a/pkg/constants/feature_constants.go b/pkg/constants/feature_constants.go index 228351239c2..3b2ae27f08a 100644 --- a/pkg/constants/feature_constants.go +++ b/pkg/constants/feature_constants.go @@ -22,9 +22,9 @@ const ( // When enabled: no secret validation step is generated, copilot-requests: write permission is added, // and the GitHub Actions token is used as the agentic engine secret. CopilotRequestsFeatureFlag FeatureFlag = "copilot-requests" - // DIFCProxyFeatureFlag is the feature flag name for enabling the DIFC proxy. - // When enabled, the compiler injects DIFC proxy steps (start/stop) around pre-agent - // gh CLI steps and qmd indexing steps when guard policies are configured. - // By default (flag absent), DIFC proxy steps are not emitted. + // DIFCProxyFeatureFlag is the deprecated feature flag name for the DIFC proxy. + // Deprecated: Use tools.github.integrity-proxy instead. The proxy is now enabled + // by default when guard policies are configured. Set tools.github.integrity-proxy: false + // to disable it. The codemod "features-difc-proxy-to-tools-github" migrates this flag. DIFCProxyFeatureFlag FeatureFlag = "difc-proxy" ) diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index f1e81686f26..59d50266bec 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -3481,6 +3481,11 @@ "description": "Enable lockdown mode to limit content surfaced from public repositories (only items authored by users with push access). Default: false", "default": false }, + "integrity-proxy": { + "type": "boolean", + "description": "Controls DIFC proxy injection for pre-agent gh CLI steps when guard policies (min-integrity) are configured. Default: true (enabled). Set to false to disable proxy injection and rely solely on MCP gateway-level filtering.", + "default": true + }, "github-token": { "$ref": "#/$defs/github_token", "description": "Optional custom GitHub token (e.g., '${{ secrets.CUSTOM_PAT }}'). For 'remote' type, defaults to GH_AW_GITHUB_TOKEN if not specified." diff --git a/pkg/workflow/compiler_difc_proxy.go b/pkg/workflow/compiler_difc_proxy.go index f9a0401a012..8469db7d402 100644 --- a/pkg/workflow/compiler_difc_proxy.go +++ b/pkg/workflow/compiler_difc_proxy.go @@ -68,14 +68,15 @@ import ( var difcProxyLog = logger.New("workflow:difc_proxy") // hasDIFCGuardsConfigured returns true if the GitHub tool has explicit guard policies configured -// (min-integrity is set) AND the "difc-proxy" feature flag is enabled. +// (min-integrity is set) AND the DIFC proxy has not been explicitly disabled via +// tools.github.integrity-proxy: false. // This is the base condition for DIFC proxy injection. func hasDIFCGuardsConfigured(data *WorkflowData) bool { if data == nil { return false } - if !isFeatureEnabled(constants.DIFCProxyFeatureFlag, data) { - difcProxyLog.Print("difc-proxy feature flag not enabled, skipping DIFC proxy injection") + if !isIntegrityProxyEnabled(data) { + difcProxyLog.Print("integrity-proxy disabled via tools.github.integrity-proxy: false, skipping DIFC proxy injection") return false } githubTool, hasGitHub := data.Tools["github"] @@ -85,6 +86,31 @@ func hasDIFCGuardsConfigured(data *WorkflowData) bool { return len(getGitHubGuardPolicies(githubTool)) > 0 } +// isIntegrityProxyEnabled returns true unless the user has explicitly disabled the DIFC proxy +// by setting tools.github.integrity-proxy: false. +// The proxy is enabled by default (opt-out model): absent or true → enabled; false → disabled. +func isIntegrityProxyEnabled(data *WorkflowData) bool { + if data == nil { + return true + } + githubTool, hasGitHub := data.Tools["github"] + if !hasGitHub { + return true + } + toolConfig, ok := githubTool.(map[string]any) + if !ok { + return true + } + val, hasField := toolConfig["integrity-proxy"] + if !hasField { + return true // default: enabled + } + if enabled, ok := val.(bool); ok { + return enabled + } + return true +} + // hasDIFCProxyNeeded returns true if the DIFC proxy should be injected in the main job. // // The proxy is only needed when: @@ -216,12 +242,12 @@ func (c *Compiler) buildStartDIFCProxyStepYAML(data *WorkflowData) string { sb.WriteString(" env:\n") fmt.Fprintf(&sb, " GH_TOKEN: %s\n", effectiveToken) sb.WriteString(" GITHUB_SERVER_URL: ${{ github.server_url }}\n") + // Store policy and image in env vars to avoid shell-quoting issues with + // inline JSON arguments and to keep the run: command clean. + fmt.Fprintf(&sb, " DIFC_PROXY_POLICY: '%s'\n", policyJSON) + fmt.Fprintf(&sb, " DIFC_PROXY_IMAGE: '%s'\n", containerImage) sb.WriteString(" run: |\n") - // The policy JSON contains only static values from the workflow frontmatter - // (min-integrity and repos). It never contains GitHub Actions expressions (${{ }}) - // because getDIFCProxyPolicyJSON() only includes compile-time values, making - // single-quoting safe here. - fmt.Fprintf(&sb, " bash ${RUNNER_TEMP}/gh-aw/actions/start_difc_proxy.sh '%s' '%s'\n", policyJSON, containerImage) + sb.WriteString(" bash ${RUNNER_TEMP}/gh-aw/actions/start_difc_proxy.sh\n") return sb.String() } diff --git a/pkg/workflow/compiler_difc_proxy_test.go b/pkg/workflow/compiler_difc_proxy_test.go index f47281a0d05..4f6ca76f31b 100644 --- a/pkg/workflow/compiler_difc_proxy_test.go +++ b/pkg/workflow/compiler_difc_proxy_test.go @@ -65,20 +65,21 @@ func TestHasDIFCProxyNeeded(t *testing.T) { desc: "guard policy without GH_TOKEN pre-agent steps should not trigger proxy", }, { - name: "guard policy + custom steps with GH_TOKEN but feature flag disabled", + name: "guard policy + custom steps with GH_TOKEN but integrity-proxy disabled", data: &WorkflowData{ Tools: map[string]any{ "github": map[string]any{ - "min-integrity": "approved", + "min-integrity": "approved", + "integrity-proxy": false, }, }, CustomSteps: "steps:\n - name: Fetch issues\n env:\n GH_TOKEN: ${{ github.token }}\n run: gh issue list", }, expected: false, - desc: "feature flag absent → proxy not triggered even when guard policy and GH_TOKEN present", + desc: "integrity-proxy: false → proxy not triggered even when guard policy and GH_TOKEN present", }, { - name: "guard policy + custom steps with GH_TOKEN + feature flag enabled", + name: "guard policy + custom steps with GH_TOKEN (proxy enabled by default)", data: &WorkflowData{ Tools: map[string]any{ "github": map[string]any{ @@ -86,10 +87,23 @@ func TestHasDIFCProxyNeeded(t *testing.T) { }, }, CustomSteps: "steps:\n - name: Fetch issues\n env:\n GH_TOKEN: ${{ github.token }}\n run: gh issue list", - Features: map[string]any{"difc-proxy": true}, }, expected: true, - desc: "guard policy + custom steps with GH_TOKEN + difc-proxy feature flag should trigger proxy", + desc: "guard policy + custom steps with GH_TOKEN should trigger proxy by default (no feature flag needed)", + }, + { + name: "guard policy + custom steps with GH_TOKEN + integrity-proxy explicitly true", + data: &WorkflowData{ + Tools: map[string]any{ + "github": map[string]any{ + "min-integrity": "approved", + "integrity-proxy": true, + }, + }, + CustomSteps: "steps:\n - name: Fetch issues\n env:\n GH_TOKEN: ${{ github.token }}\n run: gh issue list", + }, + expected: true, + desc: "integrity-proxy: true explicitly set should trigger proxy", }, { name: "guard policy + repo-memory configured", @@ -108,7 +122,7 @@ func TestHasDIFCProxyNeeded(t *testing.T) { desc: "guard policy + repo-memory should NOT trigger proxy: repo-memory clones use direct git URLs, not GH_HOST", }, { - name: "guard policy with allowed-repos + custom steps with GH_TOKEN + feature flag enabled", + name: "guard policy with allowed-repos + custom steps with GH_TOKEN (default enabled)", data: &WorkflowData{ Tools: map[string]any{ "github": map[string]any{ @@ -117,10 +131,9 @@ func TestHasDIFCProxyNeeded(t *testing.T) { }, }, CustomSteps: "steps:\n - name: Fetch PRs\n env:\n GH_TOKEN: ${{ secrets.MY_TOKEN }}\n run: gh pr list", - Features: map[string]any{"difc-proxy": true}, }, expected: true, - desc: "allowed-repos + min-integrity + GH_TOKEN custom steps + difc-proxy flag should trigger proxy", + desc: "allowed-repos + min-integrity + GH_TOKEN custom steps should trigger proxy by default", }, } @@ -306,7 +319,6 @@ func TestGenerateStartDIFCProxyStep(t *testing.T) { }, CustomSteps: "steps:\n - name: Fetch\n env:\n GH_TOKEN: ${{ github.token }}\n run: gh issue list", SandboxConfig: &SandboxConfig{}, - Features: map[string]any{"difc-proxy": true}, } ensureDefaultMCPGatewayConfig(data) c.generateStartDIFCProxyStep(&yaml, data) @@ -316,10 +328,12 @@ func TestGenerateStartDIFCProxyStep(t *testing.T) { assert.Contains(t, result, "Start DIFC proxy for pre-agent gh calls", "step name should be present") assert.Contains(t, result, "GH_TOKEN:", "step should include GH_TOKEN env var") assert.Contains(t, result, "GITHUB_SERVER_URL:", "step should include GITHUB_SERVER_URL env var") + assert.Contains(t, result, "DIFC_PROXY_POLICY:", "step should include policy as env var") + assert.Contains(t, result, "DIFC_PROXY_IMAGE:", "step should include image as env var") assert.Contains(t, result, "start_difc_proxy.sh", "step should call the proxy script") - assert.Contains(t, result, `"allow-only"`, "step should include guard policy JSON") - assert.Contains(t, result, `"min-integrity":"approved"`, "step should include min-integrity in policy") - assert.Contains(t, result, "ghcr.io/github/gh-aw-mcpg", "step should include container image") + assert.Contains(t, result, `"allow-only"`, "step should include guard policy JSON in env var") + assert.Contains(t, result, `"min-integrity":"approved"`, "step should include min-integrity in policy env var") + assert.Contains(t, result, "ghcr.io/github/gh-aw-mcpg", "step should include container image in env var") assert.NotContains(t, result, "blocked-users", "proxy policy should not include dynamic blocked-users") assert.NotContains(t, result, "approval-labels", "proxy policy should not include dynamic approval-labels") }) @@ -349,7 +363,6 @@ func TestGenerateStopDIFCProxyStep(t *testing.T) { }, CustomSteps: "steps:\n - name: Fetch\n env:\n GH_TOKEN: ${{ github.token }}\n run: gh issue list", SandboxConfig: &SandboxConfig{}, - Features: map[string]any{"difc-proxy": true}, } c.generateStopDIFCProxyStep(&yaml, data) @@ -378,7 +391,6 @@ func TestDIFCProxyLogPaths(t *testing.T) { "github": map[string]any{"min-integrity": "approved"}, }, CustomSteps: "steps:\n - name: Fetch\n env:\n GH_TOKEN: ${{ github.token }}\n run: gh issue list", - Features: map[string]any{"difc-proxy": true}, } paths := difcProxyLogPaths(data) require.Len(t, paths, 2, "should return include path and exclusion path") @@ -394,8 +406,6 @@ func TestDIFCProxyStepOrderInCompiledWorkflow(t *testing.T) { workflow := `--- on: issues engine: copilot -features: - difc-proxy: true tools: github: mode: local @@ -413,7 +423,7 @@ steps: # Test Workflow -Test that DIFC proxy is injected when min-integrity is set with custom steps using GH_TOKEN. +Test that DIFC proxy is injected by default when min-integrity is set with custom steps using GH_TOKEN. ` compiler := NewCompiler() data, err := compiler.ParseWorkflowString(workflow, "test-workflow.md") @@ -461,16 +471,17 @@ Test that DIFC proxy is injected when min-integrity is set with custom steps usi // Verify the policy JSON in the proxy start step does NOT contain dynamic fields. // Note: the MCP gateway config may include approval-labels/blocked-users, but the proxy policy must not. - proxyStartLine := "" + // The policy is stored in the DIFC_PROXY_POLICY env var line. + proxyPolicyLine := "" for line := range strings.SplitSeq(result, "\n") { - if strings.Contains(line, "start_difc_proxy.sh") { - proxyStartLine = line + if strings.Contains(line, "DIFC_PROXY_POLICY") { + proxyPolicyLine = line break } } - require.NotEmpty(t, proxyStartLine, "should find the start_difc_proxy.sh invocation line") - assert.NotContains(t, proxyStartLine, "blocked-users", "proxy policy invocation should not include blocked-users") - assert.NotContains(t, proxyStartLine, "approval-labels", "proxy policy invocation should not include approval-labels") + require.NotEmpty(t, proxyPolicyLine, "should find the DIFC_PROXY_POLICY env var line") + assert.NotContains(t, proxyPolicyLine, "blocked-users", "proxy policy should not include blocked-users") + assert.NotContains(t, proxyPolicyLine, "approval-labels", "proxy policy should not include approval-labels") } // TestDIFCProxyNotInjectedWithoutGuardPolicy verifies no proxy injection without guard policy. @@ -506,9 +517,9 @@ Test that DIFC proxy is NOT injected when min-integrity is not set. "compiled workflow should NOT contain proxy stop step without guard policy") } -// TestDIFCProxyNotInjectedWithoutFeatureFlag verifies no proxy injection when -// guard policies are configured but the "difc-proxy" feature flag is absent. -func TestDIFCProxyNotInjectedWithoutFeatureFlag(t *testing.T) { +// TestDIFCProxyNotInjectedWhenIntegrityProxyFalse verifies no proxy injection when +// guard policies are configured but tools.github.integrity-proxy: false is set. +func TestDIFCProxyNotInjectedWhenIntegrityProxyFalse(t *testing.T) { workflow := `--- on: issues engine: copilot @@ -517,6 +528,7 @@ tools: mode: local toolsets: [default] min-integrity: approved + integrity-proxy: false steps: - name: Fetch repo data env: @@ -526,7 +538,7 @@ steps: # Test Workflow -Test that DIFC proxy is NOT injected when min-integrity is set but difc-proxy feature flag is absent. +Test that DIFC proxy is NOT injected when integrity-proxy: false is set. ` compiler := NewCompiler() data, err := compiler.ParseWorkflowString(workflow, "test-workflow.md") @@ -568,24 +580,40 @@ func TestHasDIFCGuardsConfigured(t *testing.T) { expected: false, }, { - name: "github tool with min-integrity but feature flag disabled", + name: "github tool with min-integrity (enabled by default)", data: &WorkflowData{ Tools: map[string]any{ "github": map[string]any{"min-integrity": "approved"}, }, }, - expected: false, + expected: true, }, { - name: "github tool with min-integrity and feature flag enabled", + name: "github tool with min-integrity and integrity-proxy: true", data: &WorkflowData{ - Tools: map[string]any{"github": map[string]any{"min-integrity": "approved"}}, - Features: map[string]any{"difc-proxy": true}, + Tools: map[string]any{ + "github": map[string]any{ + "min-integrity": "approved", + "integrity-proxy": true, + }, + }, }, expected: true, }, { - name: "github tool with allowed-repos and min-integrity and feature flag enabled", + name: "github tool with min-integrity and integrity-proxy: false", + data: &WorkflowData{ + Tools: map[string]any{ + "github": map[string]any{ + "min-integrity": "approved", + "integrity-proxy": false, + }, + }, + }, + expected: false, + }, + { + name: "github tool with allowed-repos and min-integrity (enabled by default)", data: &WorkflowData{ Tools: map[string]any{ "github": map[string]any{ @@ -593,7 +621,6 @@ func TestHasDIFCGuardsConfigured(t *testing.T) { "min-integrity": "merged", }, }, - Features: map[string]any{"difc-proxy": true}, }, expected: true, }, @@ -639,7 +666,6 @@ func TestDIFCProxyInjectedInIndexingJob(t *testing.T) { }, QmdConfig: &QmdToolConfig{}, SandboxConfig: &SandboxConfig{}, - Features: map[string]any{"difc-proxy": true}, } ensureDefaultMCPGatewayConfig(data) @@ -666,7 +692,6 @@ func TestDIFCProxyInjectedInIndexingJob(t *testing.T) { CacheKey: "qmd-test", }, SandboxConfig: &SandboxConfig{}, - Features: map[string]any{"difc-proxy": true}, } ensureDefaultMCPGatewayConfig(data) @@ -759,7 +784,6 @@ func TestGenerateSetGHRepoAfterDIFCProxyStep(t *testing.T) { }, CustomSteps: "steps:\n - name: Fetch\n env:\n GH_TOKEN: ${{ github.token }}\n run: gh issue list", SandboxConfig: &SandboxConfig{}, - Features: map[string]any{"difc-proxy": true}, } c.generateSetGHRepoAfterDIFCProxyStep(&yaml, data)