diff --git a/.github/workflows/weekly-blog-post-writer.lock.yml b/.github/workflows/weekly-blog-post-writer.lock.yml index 10dc8ce3f4d..b8b802fb463 100644 --- a/.github/workflows/weekly-blog-post-writer.lock.yml +++ b/.github/workflows/weekly-blog-post-writer.lock.yml @@ -1220,6 +1220,11 @@ 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 }} + run: | + bash ${RUNNER_TEMP}/gh-aw/actions/start_difc_proxy.sh '{"allow-only":{"min-integrity":"approved","repos":["github/gh-aw"]}}' 'ghcr.io/github/gh-aw-mcpg:v0.2.4' - name: Restore qmd index from cache id: qmd-cache-restore uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 @@ -1266,6 +1271,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: agent diff --git a/actions/setup/sh/start_difc_proxy.sh b/actions/setup/sh/start_difc_proxy.sh index ce9bdc5f47b..3dc639c6e31 100644 --- a/actions/setup/sh/start_difc_proxy.sh +++ b/actions/setup/sh/start_difc_proxy.sh @@ -66,8 +66,17 @@ for i in $(seq 1 30); do fi if curl -sf "https://localhost:18443/api/v3/health" -o /dev/null 2>/dev/null; then echo "DIFC proxy ready on port 18443" + # Route gh CLI calls through the proxy. echo "GH_HOST=localhost:18443" >> "$GITHUB_ENV" git remote add proxy "https://localhost:18443/${GITHUB_REPOSITORY}.git" || true + # Route actions/github-script Octokit calls through the proxy. + # Save the originals so stop_difc_proxy.sh can restore them. + echo "GH_AW_ORIGINAL_GITHUB_API_URL=${GITHUB_API_URL:-}" >> "$GITHUB_ENV" + echo "GH_AW_ORIGINAL_GITHUB_GRAPHQL_URL=${GITHUB_GRAPHQL_URL:-}" >> "$GITHUB_ENV" + echo "GITHUB_API_URL=https://localhost:18443/api/v3" >> "$GITHUB_ENV" + echo "GITHUB_GRAPHQL_URL=https://localhost:18443/api/graphql" >> "$GITHUB_ENV" + # Trust the proxy TLS certificate from Node.js (used by actions/github-script). + echo "NODE_EXTRA_CA_CERTS=$PROXY_LOG_DIR/proxy-tls/ca.crt" >> "$GITHUB_ENV" PROXY_READY=true break fi diff --git a/actions/setup/sh/stop_difc_proxy.sh b/actions/setup/sh/stop_difc_proxy.sh index 2b5831d8209..22dd5b42dff 100644 --- a/actions/setup/sh/stop_difc_proxy.sh +++ b/actions/setup/sh/stop_difc_proxy.sh @@ -27,4 +27,16 @@ fi if [ "${GH_HOST:-}" = "localhost:18443" ]; then echo "GH_HOST=" >> "$GITHUB_ENV" fi + +# Restore GITHUB_API_URL and GITHUB_GRAPHQL_URL to their original values. +if [ "${GITHUB_API_URL:-}" = "https://localhost:18443/api/v3" ]; then + echo "GITHUB_API_URL=${GH_AW_ORIGINAL_GITHUB_API_URL:-}" >> "$GITHUB_ENV" +fi +if [ "${GITHUB_GRAPHQL_URL:-}" = "https://localhost:18443/api/graphql" ]; then + echo "GITHUB_GRAPHQL_URL=${GH_AW_ORIGINAL_GITHUB_GRAPHQL_URL:-}" >> "$GITHUB_ENV" +fi + +# Clear the Node.js CA certs override set by start_difc_proxy.sh. +echo "NODE_EXTRA_CA_CERTS=" >> "$GITHUB_ENV" + echo "DIFC proxy stopped" diff --git a/pkg/workflow/compiler_difc_proxy.go b/pkg/workflow/compiler_difc_proxy.go index 45af1cbf6b3..c7a2846b231 100644 --- a/pkg/workflow/compiler_difc_proxy.go +++ b/pkg/workflow/compiler_difc_proxy.go @@ -15,8 +15,9 @@ package workflow // is running. Only gh CLI calls that honour GH_HOST are actually filtered. // // Note: qmd indexing GitHub API calls are made via actions/github-script (@actions/github -// Octokit), which uses GITHUB_API_URL / GITHUB_GRAPHQL_URL rather than GH_HOST. The proxy -// does not intercept those calls, so no proxy wrapping is injected into the qmd indexing job. +// Octokit). The proxy sets GITHUB_API_URL, GITHUB_GRAPHQL_URL, and NODE_EXTRA_CA_CERTS in +// addition to GH_HOST, so it intercepts Octokit calls as well. Proxy wrapping is therefore +// also injected around qmd indexing steps when DIFC guards are configured. // // The proxy uses the same container image as the MCP gateway (gh-aw-mcpg) // but runs in "proxy" mode with --guards-mode filter (graceful degradation) @@ -24,15 +25,23 @@ package workflow // // Injection conditions: // -// Main job: GitHub tool has explicit guard policies (min-integrity set) AND -// custom steps set GH_TOKEN +// Main job: GitHub tool has explicit guard policies (min-integrity set) AND +// custom steps set GH_TOKEN +// Indexing job: GitHub tool has explicit guard policies (min-integrity set) // // Proxy lifecycle within the main job: // 1. Start proxy — after "Configure gh CLI" step, before custom steps -// 2. Custom steps run with GH_HOST=localhost:18443 (set via $GITHUB_ENV) +// 2. Custom steps run with GH_HOST=localhost:18443, GITHUB_API_URL, GITHUB_GRAPHQL_URL, +// and NODE_EXTRA_CA_CERTS set (via $GITHUB_ENV) // 3. Stop proxy — before MCP gateway starts (generateMCPSetup); always runs // even if earlier steps failed (if: always(), continue-on-error: true) // +// Proxy lifecycle within the indexing job: +// 1. Start proxy — before qmd index-building steps +// 2. qmd steps run with all proxy env vars set (GH_HOST, GITHUB_API_URL, GITHUB_GRAPHQL_URL, +// NODE_EXTRA_CA_CERTS); Octokit calls in actions/github-script are intercepted +// 3. Stop proxy — after qmd steps; always runs (if: always(), continue-on-error: true) +// // Guard policy note: // // The proxy policy uses only the static fields from the workflow's frontmatter diff --git a/pkg/workflow/compiler_difc_proxy_test.go b/pkg/workflow/compiler_difc_proxy_test.go index 9f66bcafbd9..98823ec0ccc 100644 --- a/pkg/workflow/compiler_difc_proxy_test.go +++ b/pkg/workflow/compiler_difc_proxy_test.go @@ -579,4 +579,57 @@ func TestDIFCProxyInjectedInIndexingJob(t *testing.T) { assert.Contains(t, stopStep, "Stop DIFC proxy") assert.Contains(t, stopStep, "stop_difc_proxy.sh") }) + + t.Run("buildQmdIndexingJob wraps steps with proxy when guard policy configured", func(t *testing.T) { + data := &WorkflowData{ + Tools: map[string]any{ + "github": map[string]any{"min-integrity": "approved"}, + }, + QmdConfig: &QmdToolConfig{ + Searches: []*QmdSearchEntry{{Query: "repo:owner/repo language:Markdown"}}, + CacheKey: "qmd-test", + }, + SandboxConfig: &SandboxConfig{}, + } + ensureDefaultMCPGatewayConfig(data) + + job, err := c.buildQmdIndexingJob(data) + require.NoError(t, err, "buildQmdIndexingJob should succeed") + require.NotNil(t, job, "job should not be nil") + + allSteps := strings.Join(job.Steps, "\n") + require.Contains(t, allSteps, "Start DIFC proxy for pre-agent gh calls", + "indexing job should include proxy start step when guard policy is configured") + require.Contains(t, allSteps, "Stop DIFC proxy", + "indexing job should include proxy stop step when guard policy is configured") + + // Proxy start must come before the qmd index step and proxy stop must come after. + startIdx := strings.Index(allSteps, "Start DIFC proxy for pre-agent gh calls") + stopIdx := strings.Index(allSteps, "Stop DIFC proxy") + assert.Less(t, startIdx, stopIdx, "Start proxy must come before Stop proxy in indexing job") + }) + + t.Run("buildQmdIndexingJob has no proxy steps without guard policy", func(t *testing.T) { + data := &WorkflowData{ + Tools: map[string]any{ + "github": map[string]any{"toolsets": []string{"default"}}, + }, + QmdConfig: &QmdToolConfig{ + Searches: []*QmdSearchEntry{{Query: "repo:owner/repo language:Markdown"}}, + CacheKey: "qmd-test", + }, + SandboxConfig: &SandboxConfig{}, + } + ensureDefaultMCPGatewayConfig(data) + + job, err := c.buildQmdIndexingJob(data) + require.NoError(t, err, "buildQmdIndexingJob should succeed") + require.NotNil(t, job, "job should not be nil") + + allSteps := strings.Join(job.Steps, "\n") + assert.NotContains(t, allSteps, "Start DIFC proxy", + "indexing job should NOT include proxy start step without guard policy") + assert.NotContains(t, allSteps, "Stop DIFC proxy", + "indexing job should NOT include proxy stop step without guard policy") + }) } diff --git a/pkg/workflow/qmd.go b/pkg/workflow/qmd.go index 7725f1055b7..53103671502 100644 --- a/pkg/workflow/qmd.go +++ b/pkg/workflow/qmd.go @@ -591,14 +591,23 @@ func (c *Compiler) buildQmdIndexingJob(data *WorkflowData) (*Job, error) { // Generate all qmd index-building steps (cache restore/save, Node.js, SDK install, github-script). qmdSteps := generateQmdIndexSteps(data.QmdConfig) - // Note: qmd indexing GitHub API calls are made via actions/github-script (@actions/github - // Octokit), which uses GITHUB_API_URL / GITHUB_GRAPHQL_URL rather than GH_HOST. - // The DIFC proxy started by buildStartDIFCProxyStepYAML() only sets GH_HOST, so it does - // not intercept qmd's traffic. We therefore do not wrap qmd indexing steps with the proxy. + // Wrap qmd indexing steps with the DIFC proxy when guard policies are configured. + // The proxy now sets GITHUB_API_URL, GITHUB_GRAPHQL_URL, and NODE_EXTRA_CA_CERTS in + // addition to GH_HOST, so it intercepts Octokit calls made by actions/github-script + // during qmd indexing. if hasDIFCGuardsConfigured(data) { - qmdLog.Print("DIFC guards configured; qmd indexing steps are not wrapped with GH_HOST-based DIFC proxy") + qmdLog.Print("DIFC guards configured; wrapping qmd indexing steps with DIFC proxy") + startStep := c.buildStartDIFCProxyStepYAML(data) + if startStep != "" { + steps = append(steps, startStep) + steps = append(steps, qmdSteps...) + steps = append(steps, buildStopDIFCProxyStepYAML()) + } else { + steps = append(steps, qmdSteps...) + } + } else { + steps = append(steps, qmdSteps...) } - steps = append(steps, qmdSteps...) // The indexing job runs after the activation job to inherit the artifact prefix output. needs := []string{string(constants.ActivationJobName)}