diff --git a/.github/workflows/daily-formal-spec-verifier.lock.yml b/.github/workflows/daily-formal-spec-verifier.lock.yml index 9542baf9218..bd5fa55e8b3 100644 --- a/.github/workflows/daily-formal-spec-verifier.lock.yml +++ b/.github/workflows/daily-formal-spec-verifier.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"7282f619432d4e8888225711f1fb6a9fc3e9d18fb12eaeb3424fe198e8885c86","body_hash":"511c354d1036187b61d80cedbcc3a648d047e9e336b83a11a0bcc8bbf096319d","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60","copilot-sdk":"1.0.0"}} -# gh-aw-manifest: {"version":1,"secrets":["GH_AW_AGENT_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.2","digest":"sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.2@sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} -# This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"7282f619432d4e8888225711f1fb6a9fc3e9d18fb12eaeb3424fe198e8885c86","body_hash":"511c354d1036187b61d80cedbcc3a648d047e9e336b83a11a0bcc8bbf096319d","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.63","copilot-sdk":"1.0.1"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_AGENT_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.4","digest":"sha256:b268ebf37df2428b19efcb383f001d65dc6a5ec10af43feb886d1a8477ab0e3a","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.4@sha256:b268ebf37df2428b19efcb383f001d65dc6a5ec10af43feb886d1a8477ab0e3a"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.4","digest":"sha256:3ea0d12a2d124db8ed6e2d18aff040e30ab3568161f258a132fccdeede4198cd","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.4@sha256:3ea0d12a2d124db8ed6e2d18aff040e30ab3568161f258a132fccdeede4198cd"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.4","digest":"sha256:72c378c029d2fad4684847ab44c329e526ac6b1a78cdf97656870ea11d201545","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.4@sha256:72c378c029d2fad4684847ab44c329e526ac6b1a78cdf97656870ea11d201545"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.4","digest":"sha256:87979038897e40caed22245b64d1daa796390d2dca289b99d3d1174c85740af8","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.4@sha256:87979038897e40caed22245b64d1daa796390d2dca289b99d3d1174c85740af8"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.26","digest":"sha256:d3b03f54eee3a8176818c9a52087623e45b7f644a28814337fcc0838e2534490","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.26@sha256:d3b03f54eee3a8176818c9a52087623e45b7f644a28814337fcc0838e2534490"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.3.0","digest":"sha256:5c83359327a0bacc3d34db730bea6557d39d341cee0bf6c58c9a896e33150e80","pinned_image":"ghcr.io/github/github-mcp-server:v1.3.0@sha256:5c83359327a0bacc3d34db730bea6557d39d341cee0bf6c58c9a896e33150e80"}]} +# This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -51,20 +51,20 @@ # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 -# - ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.2@sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 -# - ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa -# - ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c +# - ghcr.io/github/gh-aw-firewall/agent:0.27.4@sha256:b268ebf37df2428b19efcb383f001d65dc6a5ec10af43feb886d1a8477ab0e3a +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.4@sha256:3ea0d12a2d124db8ed6e2d18aff040e30ab3568161f258a132fccdeede4198cd +# - ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.4@sha256:72c378c029d2fad4684847ab44c329e526ac6b1a78cdf97656870ea11d201545 +# - ghcr.io/github/gh-aw-firewall/squid:0.27.4@sha256:87979038897e40caed22245b64d1daa796390d2dca289b99d3d1174c85740af8 +# - ghcr.io/github/gh-aw-mcpg:v0.3.26@sha256:d3b03f54eee3a8176818c9a52087623e45b7f644a28814337fcc0838e2534490 +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.3.0@sha256:5c83359327a0bacc3d34db730bea6557d39d341cee0bf6c58c9a896e33150e80 name: "Daily Formal Spec Verifier" on: schedule: - - cron: "10 10 * * *" + - cron: "23 15 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: @@ -84,7 +84,7 @@ run-name: "Daily Formal Spec Verifier" env: OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.GH_AW_OTEL_SENTRY_ENDPOINT }} OTEL_SERVICE_NAME: gh-aw.daily-formal-spec-verifier - OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=Daily Formal Spec Verifier,gh-aw.repository=${{ github.repository }},gh-aw.run.id=${{ github.run_id }},github.run_id=${{ github.run_id }},gh-aw.engine.id=copilot' + OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=Daily%20Formal%20Spec%20Verifier,gh-aw.repository=${{ github.repository }},gh-aw.run.id=${{ github.run_id }},github.run_id=${{ github.run_id }},gh-aw.engine.id=copilot' OTEL_EXPORTER_OTLP_HEADERS: x-sentry-auth=${{ secrets.GH_AW_OTEL_SENTRY_AUTHORIZATION }} GH_AW_OTLP_ALL_HEADERS: x-sentry-auth=${{ secrets.GH_AW_OTEL_SENTRY_AUTHORIZATION }},Authorization=${{ secrets.GH_AW_OTEL_GRAFANA_AUTHORIZATION }} GH_AW_OTLP_ENDPOINTS: '[{"url":"${{ secrets.GH_AW_OTEL_SENTRY_ENDPOINT }}","headers":"x-sentry-auth=${{ secrets.GH_AW_OTEL_SENTRY_AUTHORIZATION }}"},{"url":"${{ secrets.GH_AW_OTEL_GRAFANA_ENDPOINT }}","headers":"Authorization=${{ secrets.GH_AW_OTEL_GRAFANA_AUTHORIZATION }}"}]' @@ -111,9 +111,16 @@ jobs: setup-trace-id: ${{ steps.setup.outputs.trace-id }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} steps: + - name: Checkout actions folder + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + repository: github/gh-aw + sparse-checkout: | + actions + persist-credentials: false - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: ./actions/setup with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -121,8 +128,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Daily Formal Spec Verifier" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-formal-spec-verifier.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.4" GH_AW_INFO_ENGINE_ID: "copilot" - name: Mask OTLP telemetry headers run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh" @@ -132,16 +139,15 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AGENT_VERSION: "1.0.60" - GH_AW_INFO_CLI_VERSION: "v0.79.8" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AGENT_VERSION: "1.0.63" GH_AW_INFO_WORKFLOW_NAME: "Daily Formal Spec Verifier" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["*.grafana.net","*.sentry.io","defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_AWF_VERSION: "v0.27.4" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_INFO_FRONTMATTER_EMOJI: "🔬" @@ -153,6 +159,30 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-dailyformalspecverifier-${{ github.run_id }} + restore-keys: agentic-workflow-usage-dailyformalspecverifier- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs'); + await main(); - name: Check daily workflow token guardrail id: daily-effective-workflow-guardrail if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} @@ -162,6 +192,8 @@ jobs: GH_AW_WORKFLOW_ID: "daily-formal-spec-verifier" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} with: @@ -178,6 +210,7 @@ jobs: sparse-checkout: | .github .agents + actions/setup .antigravity .claude .codex @@ -205,16 +238,6 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); await main(); - - name: Check compile-agentic version - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_COMPILED_VERSION: "v0.79.8" - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); - await main(); - name: Create prompt with built-in context env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -411,6 +434,8 @@ jobs: ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }} aic: ${{ steps.parse-mcp-gateway.outputs.aic }} ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }} + cache_memory_restore_0_cache_hit: ${{ steps.restore_cache_memory_0.outputs.cache-hit || 'false' }} + cache_memory_restore_0_matched_key: ${{ steps.restore_cache_memory_0.outputs.cache-matched-key || '' }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} @@ -425,9 +450,16 @@ jobs: setup-trace-id: ${{ steps.setup.outputs.trace-id }} unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }} steps: + - name: Checkout actions folder + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + repository: github/gh-aw + sparse-checkout: | + actions + persist-credentials: false - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: ./actions/setup with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -436,8 +468,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Daily Formal Spec Verifier" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-formal-spec-verifier.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.4" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -463,6 +495,7 @@ jobs: - name: Create cache-memory directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_cache_memory_dir.sh" - name: Restore cache-memory file share data + id: restore_cache_memory_0 uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: key: memory-none-nopolicy-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }} @@ -512,13 +545,13 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.63 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.4 - name: Install GitHub Copilot SDK (Node.js) - run: cd "${GITHUB_WORKSPACE}" && npm install --ignore-scripts --no-save @github/copilot-sdk@1.0.0 + run: cd "${GITHUB_WORKSPACE}" && npm install --ignore-scripts --no-save @github/copilot-sdk@1.0.1 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) @@ -550,7 +583,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.2@sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.4@sha256:b268ebf37df2428b19efcb383f001d65dc6a5ec10af43feb886d1a8477ab0e3a ghcr.io/github/gh-aw-firewall/api-proxy:0.27.4@sha256:3ea0d12a2d124db8ed6e2d18aff040e30ab3568161f258a132fccdeede4198cd ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.4@sha256:72c378c029d2fad4684847ab44c329e526ac6b1a78cdf97656870ea11d201545 ghcr.io/github/gh-aw-firewall/squid:0.27.4@sha256:87979038897e40caed22245b64d1daa796390d2dca289b99d3d1174c85740af8 ghcr.io/github/gh-aw-mcpg:v0.3.26@sha256:d3b03f54eee3a8176818c9a52087623e45b7f644a28814337fcc0838e2534490 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.3.0@sha256:5c83359327a0bacc3d34db730bea6557d39d341cee0bf6c58c9a896e33150e80 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -692,52 +725,13 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -764,18 +758,35 @@ jobs: * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.25' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.26' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_91fa07d379a1dfd6_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_6e6ad9d38cb21e0b_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw/mcp-logs/safeoutputs:/tmp/gh-aw/mcp-logs/safeoutputs:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "exec node ${RUNNER_TEMP}/gh-aw/safeoutputs/safe_outputs_mcp_server.cjs"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_SERVER_URL": "\${GITHUB_SERVER_URL}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { @@ -798,7 +809,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_91fa07d379a1dfd6_EOF + GH_AW_MCP_CONFIG_6e6ad9d38cb21e0b_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -825,7 +836,7 @@ jobs: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_SERVER_URL: ${{ github.server_url }} CLI_PROXY_POLICY: '{"allow-only":{"repos":"all","min-integrity":"none"}}' - CLI_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.3.25' + CLI_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.3.26' run: | bash "${RUNNER_TEMP}/gh-aw/actions/start_cli_proxy.sh" - name: Execute GitHub Copilot CLI @@ -869,13 +880,30 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.grafana.net\",\"*.sentry.io\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.4/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.grafana.net\",\"*.sentry.io\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.4,squid=sha256:87979038897e40caed22245b64d1daa796390d2dca289b99d3d1174c85740af8,agent=sha256:b268ebf37df2428b19efcb383f001d65dc6a5ec10af43feb886d1a8477ab0e3a,api-proxy=sha256:3ea0d12a2d124db8ed6e2d18aff040e30ab3568161f258a132fccdeede4198cd,cli-proxy=sha256:72c378c029d2fad4684847ab44c329e526ac6b1a78cdf97656870ea11d201545\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + python3 - <<'PY' + import json,os,subprocess as sp + from pathlib import Path + try: + p=Path(os.environ["RUNNER_TEMP"])/"gh-aw"/"awf-config.json" + c=json.loads(p.read_text()) + c["chroot"]={"binariesSourcePath":"/tmp/gh-aw","identity":{"user":sp.check_output(["id","-un"],text=True).strip(),"uid":int(sp.check_output(["id","-u"],text=True)),"gid":int(sp.check_output(["id","-g"],text=True)),"home":"/tmp/gh-aw/home"}} + out=json.dumps(c,separators=(",",":"),ensure_ascii=False)+"\n" + p.write_text(out) + Path("/tmp/gh-aw/awf-config.json").write_text(out) + except Exception as e: + raise SystemExit(f"chroot config patch failed: {e}") from e + PY fi GH_AW_TOOL_CACHE_MOUNT="" GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" @@ -887,7 +915,7 @@ jobs: GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull --difc-proxy-host host.docker.internal:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull --difc-proxy-host host.docker.internal:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_WORKSPACE_NODE_MODULES="${GITHUB_WORKSPACE:-$PWD}/node_modules"; if [ -d "$GH_AW_WORKSPACE_NODE_MODULES" ]; then export NODE_PATH="${GH_AW_WORKSPACE_NODE_MODULES}${NODE_PATH:+:${NODE_PATH}}"; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_sdk_driver.cjs" /usr/local/bin/copilot' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 @@ -898,13 +926,14 @@ jobs: COPILOT_SDK_URI: http://127.0.0.1:3002 GH_AW_COPILOT_SDK_DRIVER: 1 GH_AW_COPILOT_SDK_SERVER_ARGS: '["--headless","--no-auto-update","--port","3002","--add-dir","/tmp/gh-aw/","--log-level","all","--log-dir","/tmp/gh-aw/sandbox/agent/logs/","--disable-builtin-mcps","--no-ask-user","--allow-tool","github","--allow-tool","safeoutputs","--allow-tool","shell(cat pkg/cli/*.go)","--allow-tool","shell(cat pkg/workflow/*.go | head -200)","--allow-tool","shell(cat specs/*.md)","--allow-tool","shell(cat)","--allow-tool","shell(date)","--allow-tool","shell(echo)","--allow-tool","shell(find . -name \"*_test.go\" -path \"*/pkg/*\" | head -20)","--allow-tool","shell(find specs -type f -name \"*.md\" | sort)","--allow-tool","shell(gh:*)","--allow-tool","shell(grep)","--allow-tool","shell(head)","--allow-tool","shell(ls)","--allow-tool","shell(printf)","--allow-tool","shell(pwd)","--allow-tool","shell(safeoutputs:*)","--allow-tool","shell(sed -n)","--allow-tool","shell(sort)","--allow-tool","shell(tail)","--allow-tool","shell(uniq)","--allow-tool","shell(wc)","--allow-tool","shell(yq)","--allow-tool","write","--add-dir","/tmp/gh-aw/cache-memory/","--allow-all-paths"]' + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_MAX_TOOL_DENIALS: 5 GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 25 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: dev GH_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || github.token }} GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true @@ -921,6 +950,7 @@ jobs: GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Stop CLI Proxy if: always() continue-on-error: true @@ -1153,9 +1183,16 @@ jobs: tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} total_count: ${{ steps.missing_tool.outputs.total_count }} steps: + - name: Checkout actions folder + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + repository: github/gh-aw + sparse-checkout: | + actions + persist-credentials: false - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: ./actions/setup with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1164,8 +1201,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Daily Formal Spec Verifier" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-formal-spec-verifier.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.4" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1215,6 +1252,45 @@ jobs: /tmp/gh-aw/usage/agent/token_usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-dailyformalspecverifier-${{ github.run_id }} + restore-keys: agentic-workflow-usage-dailyformalspecverifier- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context); + const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs'); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-dailyformalspecverifier-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1329,6 +1405,8 @@ jobs: GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "25" GH_AW_CACHE_MEMORY_ENABLED: "true" + GH_AW_CACHE_MEMORY_RESTORE_0_MATCHED_KEY: ${{ needs.agent.outputs.cache_memory_restore_0_matched_key || '' }} + GH_AW_CACHE_MEMORY_RESTORE_0_CACHE_HIT: ${{ needs.agent.outputs.cache_memory_restore_0_cache_hit || 'false' }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1353,9 +1431,16 @@ jobs: detection_reason: ${{ steps.detection_conclusion.outputs.reason }} detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: + - name: Checkout actions folder + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + repository: github/gh-aw + sparse-checkout: | + actions + persist-credentials: false - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: ./actions/setup with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1364,8 +1449,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Daily Formal Spec Verifier" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-formal-spec-verifier.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.4" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1392,7 +1477,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.4@sha256:b268ebf37df2428b19efcb383f001d65dc6a5ec10af43feb886d1a8477ab0e3a ghcr.io/github/gh-aw-firewall/api-proxy:0.27.4@sha256:3ea0d12a2d124db8ed6e2d18aff040e30ab3568161f258a132fccdeede4198cd ghcr.io/github/gh-aw-firewall/squid:0.27.4@sha256:87979038897e40caed22245b64d1daa796390d2dca289b99d3d1174c85740af8 - name: Check if detection needed id: detection_guard if: always() @@ -1455,11 +1540,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.63 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.4 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1478,13 +1563,30 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.4/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS}},\"container\":{\"imageTag\":\"0.27.4,squid=sha256:87979038897e40caed22245b64d1daa796390d2dca289b99d3d1174c85740af8,agent=sha256:b268ebf37df2428b19efcb383f001d65dc6a5ec10af43feb886d1a8477ab0e3a,api-proxy=sha256:3ea0d12a2d124db8ed6e2d18aff040e30ab3568161f258a132fccdeede4198cd,cli-proxy=sha256:72c378c029d2fad4684847ab44c329e526ac6b1a78cdf97656870ea11d201545\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + python3 - <<'PY' + import json,os,subprocess as sp + from pathlib import Path + try: + p=Path(os.environ["RUNNER_TEMP"])/"gh-aw"/"awf-config.json" + c=json.loads(p.read_text()) + c["chroot"]={"binariesSourcePath":"/tmp/gh-aw","identity":{"user":sp.check_output(["id","-un"],text=True).strip(),"uid":int(sp.check_output(["id","-u"],text=True)),"gid":int(sp.check_output(["id","-g"],text=True)),"home":"/tmp/gh-aw/home"}} + out=json.dumps(c,separators=(",",":"),ensure_ascii=False)+"\n" + p.write_text(out) + Path("/tmp/gh-aw/awf-config.json").write_text(out) + except Exception as e: + raise SystemExit(f"chroot config patch failed: {e}") from e + PY fi GH_AW_TOOL_CACHE_MOUNT="" GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" @@ -1496,7 +1598,7 @@ jobs: GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ -- /bin/bash -c 'set +o histexpand; GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 @@ -1504,11 +1606,12 @@ jobs: COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: dev GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1523,6 +1626,7 @@ jobs: GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Parse threat detection token usage for step summary id: parse_detection_token_usage if: always() @@ -1595,9 +1699,16 @@ jobs: validation_error_default: ${{ steps.push_repo_memory_default.outputs.validation_error }} validation_failed_default: ${{ steps.push_repo_memory_default.outputs.validation_failed }} steps: + - name: Checkout actions folder + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + repository: github/gh-aw + sparse-checkout: | + actions + persist-credentials: false - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: ./actions/setup with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1606,8 +1717,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Daily Formal Spec Verifier" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-formal-spec-verifier.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.4" GH_AW_INFO_ENGINE_ID: "copilot" - name: Checkout repository uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 @@ -1656,6 +1767,15 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/push_repo_memory.cjs'); await main(); + - name: Restore actions folder + if: always() + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + repository: github/gh-aw + sparse-checkout: | + actions/setup + sparse-checkout-cone-mode: true + persist-credentials: false safe_outputs: needs: @@ -1678,7 +1798,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.60" + GH_AW_ENGINE_VERSION: "1.0.63" GH_AW_PROJECT_UTC: "-08:00" GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_TRACKER_ID: "daily-formal-spec-verifier" @@ -1696,9 +1816,16 @@ jobs: process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: + - name: Checkout actions folder + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + repository: github/gh-aw + sparse-checkout: | + actions + persist-credentials: false - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: ./actions/setup with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1707,8 +1834,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Daily Formal Spec Verifier" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-formal-spec-verifier.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.4" GH_AW_INFO_ENGINE_ID: "copilot" - name: Mask OTLP telemetry headers run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh" @@ -1729,8 +1856,7 @@ jobs: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1772,13 +1898,21 @@ jobs: - detection if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim - permissions: {} + permissions: + contents: read env: GH_AW_WORKFLOW_ID_SANITIZED: dailyformalspecverifier steps: + - name: Checkout actions folder + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + repository: github/gh-aw + sparse-checkout: | + actions + persist-credentials: false - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: ./actions/setup with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1787,8 +1921,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Daily Formal Spec Verifier" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-formal-spec-verifier.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.4" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download cache-memory artifact (default) id: download_cache_default diff --git a/pkg/workflow/action_pins.go b/pkg/workflow/action_pins.go index 3c1b5c2afa5..7777d5b7568 100644 --- a/pkg/workflow/action_pins.go +++ b/pkg/workflow/action_pins.go @@ -134,31 +134,6 @@ func lookupContainerPin(image string, cache *ActionCache) (ContainerPin, bool) { return ContainerPin{}, false } -// resolveContainerImage returns the digest-pinned image reference when a cache or -// embedded container pin exists for image; otherwise it returns the original image. -func resolveContainerImage(image string, data *WorkflowData) string { - var cache *ActionCache - if data != nil { - cache = data.ActionCache - } - if pin, ok := lookupContainerPin(image, cache); ok && pin.PinnedImage != "" { - return pin.PinnedImage - } - return image -} - -// resolveMCPGatewayContainerImage returns an MCP Gateway-compatible container -// reference. MCP Gateway container fields accept image[:tag] but not digest -// references, so digest-pinned images are normalized back to their base image. -func resolveMCPGatewayContainerImage(image string, data *WorkflowData) string { - resolved := resolveContainerImage(image, data) - base, _, hasDigest := strings.Cut(resolved, "@") - if hasDigest { - return base - } - return resolved -} - // getActionPinWithData returns the pinned action reference for a given action@version, // delegating to pkg/actionpins with a PinContext built from WorkflowData. func getActionPinWithData(actionRepo, version string, data *WorkflowData) (string, error) { diff --git a/pkg/workflow/docker.go b/pkg/workflow/docker.go index 18d2f24d29b..b262745a119 100644 --- a/pkg/workflow/docker.go +++ b/pkg/workflow/docker.go @@ -49,18 +49,6 @@ func collectDockerImages(tools map[string]any, workflowData *WorkflowData, actio } } - // Check for safe-outputs MCP server. - // Safe outputs run in the published gh-aw node container and must be part of - // the default predownload set and lock-file manifest whenever enabled. - if workflowData != nil && HasSafeOutputsEnabled(workflowData.SafeOutputs) { - image := constants.DefaultGhAwNodeImage - if !imageSet[image] { - images = append(images, image) - imageSet[image] = true - dockerLog.Printf("Added safe-outputs MCP server container: %s", image) - } - } - // Check for agentic-workflows tool // In dev mode, the image is built locally in the workflow, so don't add to pull list // In release/script mode, use alpine:latest which needs to be pulled diff --git a/pkg/workflow/docker_pin_test.go b/pkg/workflow/docker_pin_test.go index d1eee3967d7..88f4b77c890 100644 --- a/pkg/workflow/docker_pin_test.go +++ b/pkg/workflow/docker_pin_test.go @@ -41,13 +41,6 @@ func TestApplyContainerPins(t *testing.T) { expectedRefs: []string{"ghcr.io/github/gh-aw-firewall/agent:0.27.0@sha256:3816d1692e6d96887b27f1e4f1d64b8d7edb43ed9d7506b8f203913cbb81c248"}, expectedDigests: []string{"sha256:3816d1692e6d96887b27f1e4f1d64b8d7edb43ed9d7506b8f203913cbb81c248"}, }, - { - name: "embedded gh-aw-node pin used when cache is absent", - images: []string{constants.DefaultGhAwNodeImage}, - pins: nil, - expectedRefs: []string{"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"}, - expectedDigests: []string{"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"}, - }, { name: "pinned image replaced with digest reference", images: []string{"node:lts-alpine"}, @@ -129,10 +122,11 @@ func TestCollectDockerImages_StoresInWorkflowData(t *testing.T) { assert.Len(t, workflowData.DockerImagePins, len(workflowData.DockerImages), "pin count should match image count") } -// TestCollectDockerImages_SafeOutputsAddsGhAwNodeImage verifies that enabling -// safe-outputs adds the published gh-aw-node container to the default Docker pull -// list and manifest data, while not falling back to node:lts-alpine. -func TestCollectDockerImages_SafeOutputsAddsGhAwNodeImage(t *testing.T) { +// TestCollectDockerImages_SafeOutputsNoLongerPullsNodeAlpine verifies that enabling +// safe-outputs does not add node:lts-alpine to the Docker pull list. The safe-outputs +// MCP server runs directly via system Node (not inside a Docker container), so pulling +// the image is both unnecessary and fails when Docker Hub is unreachable. +func TestCollectDockerImages_SafeOutputsNoLongerPullsNodeAlpine(t *testing.T) { workflowData := &WorkflowData{ SafeOutputs: &SafeOutputsConfig{ CreateIssues: &CreateIssuesConfig{ @@ -143,16 +137,6 @@ func TestCollectDockerImages_SafeOutputsAddsGhAwNodeImage(t *testing.T) { images := collectDockerImages(map[string]any{}, workflowData, ActionModeRelease) - pinnedGhAwNodeImage := resolveContainerImage(constants.DefaultGhAwNodeImage, nil) - assert.Contains(t, images, pinnedGhAwNodeImage, - "safe-outputs should add the gh-aw-node container image to the Docker pull list") - require.NotEmpty(t, workflowData.DockerImagePins, "DockerImagePins should be populated") - assert.Contains(t, workflowData.DockerImagePins, GHAWManifestContainer{ - Image: constants.DefaultGhAwNodeImage, - Digest: "sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b", - PinnedImage: pinnedGhAwNodeImage, - }, "safe-outputs should add gh-aw-node to manifest container pins") - for _, img := range images { assert.NotContains(t, img, constants.DefaultNodeAlpineLTSImage, "safe-outputs should not add node:lts-alpine (or any digest-pinned form) to the Docker pull list") diff --git a/pkg/workflow/docker_predownload_test.go b/pkg/workflow/docker_predownload_test.go index dde83b67b2f..e8ddbeebc0d 100644 --- a/pkg/workflow/docker_predownload_test.go +++ b/pkg/workflow/docker_predownload_test.go @@ -15,14 +15,11 @@ import ( ) func TestDockerImagePredownload(t *testing.T) { - pinnedGhAwNodeImage := resolveContainerImage(constants.DefaultGhAwNodeImage, nil) - // Representative sample - tests key docker image predownload scenarios tests := []struct { name string frontmatter string expectedImages []string - manifestImages []string expectStep bool }{ { @@ -80,7 +77,7 @@ Test workflow with custom MCP container.`, expectStep: true, }, { - name: "Safe outputs predownloads gh-aw-node", + name: "Safe outputs does not pull node:lts-alpine", frontmatter: `--- on: issues engine: claude @@ -94,12 +91,8 @@ network: # Test Test workflow - safe outputs MCP server without GitHub tool.`, expectedImages: []string{ - pinnedGhAwNodeImage, "ghcr.io/github/gh-aw-mcpg:" + string(constants.DefaultMCPGatewayVersion), }, - manifestImages: []string{ - constants.DefaultGhAwNodeImage, - }, expectStep: true, }, } @@ -145,29 +138,6 @@ Test workflow - safe outputs MCP server without GitHub tool.`, t.Errorf("Expected to find image '%s' in generated YAML", expectedImage) } } - - if len(tt.manifestImages) > 0 { - manifest, err := ExtractGHAWManifestFromLockFile(string(yaml)) - if err != nil { - t.Fatalf("Failed to parse gh-aw-manifest: %v", err) - } - if manifest == nil { - t.Fatal("Expected gh-aw-manifest to be present") - } - - for _, expectedImage := range tt.manifestImages { - foundImage := false - for _, container := range manifest.Containers { - if container.Image == expectedImage { - foundImage = true - break - } - } - if !foundImage { - t.Fatalf("Expected gh-aw-manifest containers to include %q, got %#v", expectedImage, manifest.Containers) - } - } - } } }) } diff --git a/pkg/workflow/mcp_api_key_masking_test.go b/pkg/workflow/mcp_api_key_masking_test.go index 5a71964a8fe..86638d16fe7 100644 --- a/pkg/workflow/mcp_api_key_masking_test.go +++ b/pkg/workflow/mcp_api_key_masking_test.go @@ -6,13 +6,12 @@ import ( "strings" "testing" - "github.com/github/gh-aw/pkg/constants" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -// TestSafeOutputsNoLongerGeneratesHTTPAPIKey verifies that safe-outputs no longer -// creates an HTTP server API key now that it runs as a stdio MCP container. +// TestSafeOutputsAPIKeyImmediateMasking verifies that the Safe Outputs API key +// is masked immediately after generation, before any other operations. func TestSafeOutputsAPIKeyImmediateMasking(t *testing.T) { workflowData := &WorkflowData{ SafeOutputs: &SafeOutputsConfig{ @@ -30,12 +29,45 @@ func TestSafeOutputsAPIKeyImmediateMasking(t *testing.T) { require.NoError(t, compiler.generateMCPSetup(&yaml, workflowData.Tools, mockEngine, workflowData)) output := yaml.String() - assert.NotContains(t, output, "Generate Safe Outputs MCP Server Config", - "Safe outputs should not have a dedicated HTTP server config step") - assert.NotContains(t, output, "Start Safe Outputs MCP HTTP Server", - "Safe outputs should not launch a dedicated HTTP server") - assert.Contains(t, output, `"container": "`+resolveMCPGatewayContainerImage(constants.DefaultGhAwNodeImage, nil)+`"`, - "Safe outputs should run in the gh-aw node container") + // Find the Safe Outputs config generation section + configStart := strings.Index(output, "Generate Safe Outputs MCP Server Config") + require.Greater(t, configStart, -1, "Should find Safe Outputs config generation step") + + // Extract just the run script for this step + runStart := strings.Index(output[configStart:], "run: |") + require.Greater(t, runStart, -1, "Should find run script") + runSection := output[configStart+runStart:] + + // Find the next step or end of this step's run block + nextStepIdx := strings.Index(runSection, "\n - name:") + if nextStepIdx > 0 { + runSection = runSection[:nextStepIdx] + } + + // Find the API key generation line + keyGenIdx := strings.Index(runSection, "API_KEY=$(openssl rand -base64 45") + require.Greater(t, keyGenIdx, -1, "Should find API key generation") + + // Find the masking line + maskIdx := strings.Index(runSection, "echo \"::add-mask::${API_KEY}\"") + require.Greater(t, maskIdx, -1, "Should find API key masking") + + // Verify masking comes immediately after generation (no PORT or other operations in between) + betweenGenAndMask := runSection[keyGenIdx:maskIdx] + + // Should only contain the key generation line and whitespace - no PORT assignment or other operations + lines := strings.Split(betweenGenAndMask, "\n") + require.LessOrEqual(t, len(lines), 2, "Should have at most 2 lines between generation and masking (generation line + empty line)") + + // Verify no PORT assignment or other operations before masking + assert.NotContains(t, betweenGenAndMask, "PORT=", "PORT assignment should come after masking") + assert.NotContains(t, betweenGenAndMask, "Set outputs", "Output setting comment should come after masking") + + // Verify masking comes before PORT assignment + portIdx := strings.Index(runSection, "PORT=") + if portIdx > 0 { + assert.Less(t, maskIdx, portIdx, "API key masking should come before PORT assignment") + } } // TestMCPScriptsAPIKeyImmediateMasking verifies that the MCP Scripts API key diff --git a/pkg/workflow/mcp_config_refactor_test.go b/pkg/workflow/mcp_config_refactor_test.go index a0a00a0841a..40de66cbcac 100644 --- a/pkg/workflow/mcp_config_refactor_test.go +++ b/pkg/workflow/mcp_config_refactor_test.go @@ -5,15 +5,11 @@ package workflow import ( "strings" "testing" - - "github.com/github/gh-aw/pkg/constants" ) // TestRenderSafeOutputsMCPConfigWithOptions verifies the shared Safe Outputs config helper // works correctly with both Copilot and non-Copilot engines func TestRenderSafeOutputsMCPConfigWithOptions(t *testing.T) { - pinnedGhAwNodeImage := resolveMCPGatewayContainerImage(constants.DefaultGhAwNodeImage, nil) - tests := []struct { name string isLast bool @@ -22,45 +18,44 @@ func TestRenderSafeOutputsMCPConfigWithOptions(t *testing.T) { unexpectedContent []string }{ { - name: "Copilot with stdio container and escaped env vars", + name: "Copilot with HTTP transport and escaped API key", isLast: true, includeCopilotFields: true, expectedContent: []string{ `"safeoutputs": {`, - `"type": "stdio"`, - `"container": "` + pinnedGhAwNodeImage + `"`, - `"${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw"`, - `"/tmp/gh-aw/mcp-logs/safeoutputs:/tmp/gh-aw/mcp-logs/safeoutputs:rw"`, - `"entrypoint": "sh"`, - `"entrypointArgs": ["-c", "exec node ${RUNNER_TEMP}/gh-aw/safeoutputs/safe_outputs_mcp_server.cjs"]`, - `"env": {`, - `"GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}"`, + `"type": "http"`, + `"url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT"`, + `"headers": {`, + `"Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}"`, ` }`, }, unexpectedContent: []string{ - `"url": "http://`, - `"Authorization":`, + `"container"`, + `"entrypoint"`, + `"entrypointArgs"`, + `"env": {`, + `"stdio"`, }, }, { - name: "Claude/Custom with stdio container and shell variables", + name: "Claude/Custom with HTTP transport and shell variable", isLast: false, includeCopilotFields: false, expectedContent: []string{ `"safeoutputs": {`, - `"container": "` + pinnedGhAwNodeImage + `"`, - `"args": ["-w", "\${GITHUB_WORKSPACE}"]`, - `"entrypoint": "sh"`, - `"entrypointArgs": ["-c", "exec node ${RUNNER_TEMP}/gh-aw/safeoutputs/safe_outputs_mcp_server.cjs"]`, - `"env": {`, - `"GH_AW_SAFE_OUTPUTS": "$GH_AW_SAFE_OUTPUTS"`, - `"RUNNER_TEMP": "$RUNNER_TEMP"`, + `"type": "http"`, + `"url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT"`, + `"headers": {`, + `"Authorization": "$GH_AW_SAFE_OUTPUTS_API_KEY"`, ` },`, }, unexpectedContent: []string{ - `"type": "stdio"`, - `"url": "http://`, - `"Authorization":`, + `"container"`, + `"entrypoint"`, + `"entrypointArgs"`, + `"env": {`, + `"stdio"`, + `\\${`, }, }, } @@ -227,21 +222,19 @@ func TestRenderSafeOutputsMCPConfigTOML(t *testing.T) { expectedContent := []string{ `[mcp_servers.safeoutputs]`, - `container = "` + resolveMCPGatewayContainerImage(constants.DefaultGhAwNodeImage, nil) + `"`, - `mounts = ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw/mcp-logs/safeoutputs:/tmp/gh-aw/mcp-logs/safeoutputs:rw"]`, - `args = ["-w", "$GITHUB_WORKSPACE"]`, - `entrypoint = "sh"`, - `entrypointArgs = ["-c", "exec node ${RUNNER_TEMP}/gh-aw/safeoutputs/safe_outputs_mcp_server.cjs"]`, - `env_vars = ["DEBUG", "DEFAULT_BRANCH", "GH_AW_ASSETS_ALLOWED_EXTS", "GH_AW_ASSETS_BRANCH", "GH_AW_ASSETS_MAX_SIZE_KB", "GH_AW_MCP_LOG_DIR", "GH_AW_SAFE_OUTPUTS", "GH_AW_SAFE_OUTPUTS_CONFIG_PATH", "GH_AW_SAFE_OUTPUTS_TOOLS_PATH", "GITHUB_REPOSITORY", "GITHUB_SERVER_URL", "GITHUB_TOKEN", "GITHUB_WORKSPACE", "RUNNER_TEMP"]`, + `type = "http"`, + `url = "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT"`, + `[mcp_servers.safeoutputs.headers]`, + `Authorization = "$GH_AW_SAFE_OUTPUTS_API_KEY"`, } unexpectedContent := []string{ `container = "node:lts-alpine"`, `entrypoint = "node"`, `entrypointArgs = ["${RUNNER_TEMP}/gh-aw/safeoutputs/mcp-server.cjs"]`, - `type = "http"`, - `url = "http://`, - `Authorization = `, + `mounts =`, + `env_vars =`, + `stdio`, } for _, expected := range expectedContent { @@ -257,32 +250,36 @@ func TestRenderSafeOutputsMCPConfigTOML(t *testing.T) { } } -// TestRenderSafeOutputsMCPConfigTOMLStableAcrossSandboxModes verifies the stdio container -// rendering does not vary by sandbox host rewriting modes. -func TestRenderSafeOutputsMCPConfigTOMLStableAcrossSandboxModes(t *testing.T) { +// TestRenderSafeOutputsMCPConfigTOMLSandboxAware verifies sandbox-aware host selection in the +// production renderSafeOutputsTOML path +func TestRenderSafeOutputsMCPConfigTOMLSandboxAware(t *testing.T) { tests := []struct { name string workflowData *WorkflowData + expectedHost string }{ { - name: "nil workflowData", + name: "nil workflowData uses host.docker.internal", workflowData: nil, + expectedHost: "host.docker.internal", }, { - name: "agent enabled", + name: "agent enabled uses host.docker.internal", workflowData: &WorkflowData{ SandboxConfig: &SandboxConfig{ Agent: &AgentSandboxConfig{Disabled: false}, }, }, + expectedHost: "host.docker.internal", }, { - name: "agent disabled", + name: "agent disabled uses localhost", workflowData: &WorkflowData{ SandboxConfig: &SandboxConfig{ Agent: &AgentSandboxConfig{Disabled: true}, }, }, + expectedHost: "localhost", }, } @@ -295,14 +292,8 @@ func TestRenderSafeOutputsMCPConfigTOMLStableAcrossSandboxModes(t *testing.T) { }) renderer.RenderSafeOutputsMCP(&output, tt.workflowData) result := output.String() - if !strings.Contains(result, `container = "`+resolveMCPGatewayContainerImage(constants.DefaultGhAwNodeImage, tt.workflowData)+`"`) { - t.Errorf("Expected gh-aw node container not found in output:\n%s", result) - } - if !strings.Contains(result, `mounts = ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw/mcp-logs/safeoutputs:/tmp/gh-aw/mcp-logs/safeoutputs:rw"]`) { - t.Errorf("Expected stable safe-outputs mounts not found in output:\n%s", result) - } - if strings.Contains(result, "host.docker.internal") || strings.Contains(result, "localhost") { - t.Errorf("Did not expect HTTP host rewriting in stdio safe-outputs config:\n%s", result) + if !strings.Contains(result, tt.expectedHost) { + t.Errorf("Expected host %q not found in output:\n%s", tt.expectedHost, result) } }) } diff --git a/pkg/workflow/mcp_renderer_builtin.go b/pkg/workflow/mcp_renderer_builtin.go index 67cacdae65f..8b13a166d5b 100644 --- a/pkg/workflow/mcp_renderer_builtin.go +++ b/pkg/workflow/mcp_renderer_builtin.go @@ -86,18 +86,22 @@ func (r *MCPConfigRendererUnified) RenderSafeOutputsMCP(yaml *strings.Builder, w } // renderSafeOutputsTOML generates Safe Outputs MCP configuration in TOML format -// Uses containerized stdio transport in the gh-aw-node image, overriding the container's -// default entrypoint to run the stdio MCP server script. +// Now uses HTTP transport instead of stdio, similar to mcp-scripts func (r *MCPConfigRendererUnified) renderSafeOutputsTOML(yaml *strings.Builder, workflowData *WorkflowData) { - containerImage := resolveMCPGatewayContainerImage(constants.DefaultGhAwNodeImage, workflowData) + // Determine host based on whether agent is disabled + host := "host.docker.internal" + if workflowData != nil && workflowData.SandboxConfig != nil && workflowData.SandboxConfig.Agent != nil && workflowData.SandboxConfig.Agent.Disabled { + // When agent is disabled (no firewall), use localhost instead of host.docker.internal + host = "localhost" + } + yaml.WriteString(" \n") yaml.WriteString(" [mcp_servers." + constants.SafeOutputsMCPServerID.String() + "]\n") - yaml.WriteString(" container = \"" + containerImage + "\"\n") - yaml.WriteString(" mounts = [\"" + constants.DefaultWorkspaceMount + "\", \"" + constants.DefaultSafeOutputsMount + "\", \"" + constants.DefaultSafeOutputsLogMount + "\"]\n") - yaml.WriteString(" args = [\"-w\", \"$GITHUB_WORKSPACE\"]\n") - yaml.WriteString(" entrypoint = \"sh\"\n") - yaml.WriteString(" entrypointArgs = [\"-c\", \"exec node ${RUNNER_TEMP}/gh-aw/safeoutputs/safe_outputs_mcp_server.cjs\"]\n") - yaml.WriteString(" env_vars = [\"DEBUG\", \"DEFAULT_BRANCH\", \"GH_AW_ASSETS_ALLOWED_EXTS\", \"GH_AW_ASSETS_BRANCH\", \"GH_AW_ASSETS_MAX_SIZE_KB\", \"GH_AW_MCP_LOG_DIR\", \"GH_AW_SAFE_OUTPUTS\", \"GH_AW_SAFE_OUTPUTS_CONFIG_PATH\", \"GH_AW_SAFE_OUTPUTS_TOOLS_PATH\", \"GITHUB_REPOSITORY\", \"GITHUB_SERVER_URL\", \"GITHUB_TOKEN\", \"GITHUB_WORKSPACE\", \"RUNNER_TEMP\"]\n") + yaml.WriteString(" type = \"http\"\n") + yaml.WriteString(" url = \"http://" + host + ":$GH_AW_SAFE_OUTPUTS_PORT\"\n") + yaml.WriteString(" \n") + yaml.WriteString(" [mcp_servers." + constants.SafeOutputsMCPServerID.String() + ".headers]\n") + yaml.WriteString(" Authorization = \"$GH_AW_SAFE_OUTPUTS_API_KEY\"\n") // Check if GitHub tool has guard-policies configured (or auto-lockdown will run) // If so, generate a linked write-sink guard-policy for safeoutputs @@ -231,58 +235,39 @@ func (r *MCPConfigRendererUnified) renderAgenticWorkflowsTOML(yaml *strings.Buil yaml.WriteString(" env_vars = [\"DEBUG\", \"GH_TOKEN\", \"GITHUB_TOKEN\", \"GITHUB_ACTOR\", \"GITHUB_REPOSITORY\"]\n") } -// renderSafeOutputsMCPConfigWithOptions generates the Safe Outputs MCP server configuration with engine-specific options. -// The server runs as a containerized stdio MCP server in the published gh-aw node image. +// renderSafeOutputsMCPConfigWithOptions generates the Safe Outputs MCP server configuration with engine-specific options +// Now uses HTTP transport instead of stdio, similar to mcp-scripts +// The server is started in a separate step before the agent job func renderSafeOutputsMCPConfigWithOptions(yaml *strings.Builder, isLast bool, includeCopilotFields bool, workflowData *WorkflowData) { mcpRendererBuiltinLog.Printf("Rendering Safe Outputs MCP config with options: isLast=%v, includeCopilotFields=%v", isLast, includeCopilotFields) - containerImage := resolveMCPGatewayContainerImage(constants.DefaultGhAwNodeImage, workflowData) yaml.WriteString(" \"" + constants.SafeOutputsMCPServerID.String() + "\": {\n") - if includeCopilotFields { - yaml.WriteString(" \"type\": \"stdio\",\n") - } - yaml.WriteString(" \"container\": \"" + containerImage + "\",\n") - yaml.WriteString(" \"mounts\": [\"" + constants.DefaultWorkspaceMount + "\", \"" + constants.DefaultSafeOutputsMount + "\", \"" + constants.DefaultSafeOutputsLogMount + "\"],\n") - yaml.WriteString(" \"args\": [\"-w\", \"\\${GITHUB_WORKSPACE}\"],\n") - yaml.WriteString(" \"entrypoint\": \"sh\",\n") - yaml.WriteString(" \"entrypointArgs\": [\"-c\", \"exec node ${RUNNER_TEMP}/gh-aw/safeoutputs/safe_outputs_mcp_server.cjs\"],\n") - yaml.WriteString(" \"env\": {\n") + // HTTP transport configuration - server started in separate step + // Add type field for HTTP (required by MCP specification for HTTP transport) + yaml.WriteString(" \"type\": \"http\",\n") - envVars := []struct { - name string - value string - isLiteral bool - }{ - {"DEBUG", "*", true}, - {"DEFAULT_BRANCH", "DEFAULT_BRANCH", false}, - {"GH_AW_ASSETS_ALLOWED_EXTS", "GH_AW_ASSETS_ALLOWED_EXTS", false}, - {"GH_AW_ASSETS_BRANCH", "GH_AW_ASSETS_BRANCH", false}, - {"GH_AW_ASSETS_MAX_SIZE_KB", "GH_AW_ASSETS_MAX_SIZE_KB", false}, - {"GH_AW_MCP_LOG_DIR", "GH_AW_MCP_LOG_DIR", false}, - {"GH_AW_SAFE_OUTPUTS", "GH_AW_SAFE_OUTPUTS", false}, - {"GH_AW_SAFE_OUTPUTS_CONFIG_PATH", "GH_AW_SAFE_OUTPUTS_CONFIG_PATH", false}, - {"GH_AW_SAFE_OUTPUTS_TOOLS_PATH", "GH_AW_SAFE_OUTPUTS_TOOLS_PATH", false}, - {"GITHUB_REPOSITORY", "GITHUB_REPOSITORY", false}, - {"GITHUB_SERVER_URL", "GITHUB_SERVER_URL", false}, - {"GITHUB_TOKEN", "GITHUB_TOKEN", false}, - {"GITHUB_WORKSPACE", "GITHUB_WORKSPACE", false}, - {"RUNNER_TEMP", "RUNNER_TEMP", false}, + // Determine host based on whether agent is disabled + host := "host.docker.internal" + if workflowData != nil && workflowData.SandboxConfig != nil && workflowData.SandboxConfig.Agent != nil && workflowData.SandboxConfig.Agent.Disabled { + // When agent is disabled (no firewall), use localhost instead of host.docker.internal + host = "localhost" + mcpRendererBuiltinLog.Print("Agent firewall disabled, using localhost instead of host.docker.internal") } + mcpRendererBuiltinLog.Printf("Using host: %s", host) - for i, envVar := range envVars { - comma := "," - if i == len(envVars)-1 { - comma = "" - } - var valueStr string - if envVar.isLiteral { - valueStr = envVar.value - } else if includeCopilotFields { - valueStr = "\\${" + envVar.value + "}" - } else { - valueStr = "$" + envVar.value - } - yaml.WriteString(" \"" + envVar.name + "\": \"" + valueStr + "\"" + comma + "\n") + // HTTP URL using environment variable - NOT escaped so shell expands it before awmg validation + // Use host.docker.internal to allow access from firewall container (or localhost if agent disabled) + // Note: awmg validates URL format before variable resolution, so we must expand the port variable + yaml.WriteString(" \"url\": \"http://" + host + ":$GH_AW_SAFE_OUTPUTS_PORT\",\n") + + // Add Authorization header with API key + yaml.WriteString(" \"headers\": {\n") + if includeCopilotFields { + // Copilot format: backslash-escaped shell variable reference + yaml.WriteString(" \"Authorization\": \"\\${GH_AW_SAFE_OUTPUTS_API_KEY}\"\n") + } else { + // Claude/Custom format: direct shell variable reference + yaml.WriteString(" \"Authorization\": \"$GH_AW_SAFE_OUTPUTS_API_KEY\"\n") } yaml.WriteString(" }") diff --git a/pkg/workflow/mcp_renderer_test.go b/pkg/workflow/mcp_renderer_test.go index 58baa8193b8..19281006199 100644 --- a/pkg/workflow/mcp_renderer_test.go +++ b/pkg/workflow/mcp_renderer_test.go @@ -67,7 +67,6 @@ func TestNewMCPConfigRenderer(t *testing.T) { } func TestRenderSafeOutputsMCP_JSON_Copilot(t *testing.T) { - pinnedGhAwNodeImage := resolveMCPGatewayContainerImage(constants.DefaultGhAwNodeImage, nil) renderer := NewMCPConfigRenderer(MCPRendererOptions{ IncludeCopilotFields: true, InlineArgs: true, @@ -80,41 +79,30 @@ func TestRenderSafeOutputsMCP_JSON_Copilot(t *testing.T) { output := yaml.String() - // Verify Safe Outputs now uses containerized stdio transport - if !strings.Contains(output, `"type": "stdio"`) { - t.Error("Expected 'type': 'stdio' field for Copilot safe outputs server") + // Verify Safe Outputs now uses HTTP transport + if !strings.Contains(output, `"type": "http"`) { + t.Error("Expected 'type': 'http' field (safe outputs uses HTTP transport)") } if !strings.Contains(output, `"safeoutputs": {`) { t.Error("Expected safeoutputs server ID") } - if !strings.Contains(output, `"container": "`+pinnedGhAwNodeImage+`"`) { - t.Error("Expected gh-aw node container image") - } - if !strings.Contains(output, `"mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw/mcp-logs/safeoutputs:/tmp/gh-aw/mcp-logs/safeoutputs:rw"]`) { - t.Error("Expected workspace, safe-outputs, and log mounts") - } - if !strings.Contains(output, `"args": ["-w", "\${GITHUB_WORKSPACE}"]`) { - t.Error("Expected working directory args") - } - if !strings.Contains(output, `"entrypoint": "sh"`) { - t.Error("Expected entrypoint override to sh") + // Verify HTTP-based configuration + if !strings.Contains(output, `"url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT"`) { + t.Error("Expected HTTP URL field") } - if !strings.Contains(output, `"entrypointArgs": ["-c", "exec node ${RUNNER_TEMP}/gh-aw/safeoutputs/safe_outputs_mcp_server.cjs"]`) { - t.Error("Expected entrypointArgs to run the stdio MCP server script") + if !strings.Contains(output, `"headers": {`) { + t.Error("Expected headers field") } - if !strings.Contains(output, `"GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}"`) { - t.Error("Expected safe-outputs config path env var") + if !strings.Contains(output, `"Authorization":`) { + t.Error("Expected Authorization header") } - if strings.Contains(output, `"url": "http://`) { - t.Error("Did not expect HTTP URL field") - } - if strings.Contains(output, `"Authorization":`) { - t.Error("Did not expect Authorization header") + // Check for env var with backslash escaping (Copilot format) + if !strings.Contains(output, `\${`) { + t.Error("Expected backslash-escaped env vars for Copilot") } } func TestRenderSafeOutputsMCP_JSON_Claude(t *testing.T) { - pinnedGhAwNodeImage := resolveMCPGatewayContainerImage(constants.DefaultGhAwNodeImage, nil) renderer := NewMCPConfigRenderer(MCPRendererOptions{ IncludeCopilotFields: false, InlineArgs: false, @@ -127,37 +115,27 @@ func TestRenderSafeOutputsMCP_JSON_Claude(t *testing.T) { output := yaml.String() + // Verify HTTP transport is used (same as Copilot) + if !strings.Contains(output, `"type": "http"`) { + t.Error("Expected 'type': 'http' field for HTTP transport") + } if !strings.Contains(output, `"safeoutputs": {`) { t.Error("Expected safeoutputs server ID") } - if !strings.Contains(output, `"container": "`+pinnedGhAwNodeImage+`"`) { - t.Error("Expected gh-aw node container image") - } - if !strings.Contains(output, `"entrypoint": "sh"`) { - t.Error("Expected entrypoint override to sh") - } - if !strings.Contains(output, `"entrypointArgs": ["-c", "exec node ${RUNNER_TEMP}/gh-aw/safeoutputs/safe_outputs_mcp_server.cjs"]`) { - t.Error("Expected entrypointArgs to run the stdio MCP server script") - } - if !strings.Contains(output, `"GH_AW_SAFE_OUTPUTS": "$GH_AW_SAFE_OUTPUTS"`) { - t.Error("Expected direct shell variable reference for safe outputs path") - } - if !strings.Contains(output, `"RUNNER_TEMP": "$RUNNER_TEMP"`) { - t.Error("Expected direct shell variable reference for RUNNER_TEMP") - } + // Should not contain 'tools' field (HTTP servers don't have tools field) if strings.Contains(output, `"tools"`) { - t.Error("Should not contain 'tools' field") + t.Error("Should not contain 'tools' field for HTTP servers") } - if strings.Contains(output, `"type"`) { - t.Error("Should not contain 'type' field for Claude stdio server config") + // Check for env var without backslash escaping (Claude format) + if strings.Contains(output, `\${`) { + t.Error("Should not have backslash-escaped env vars for Claude") } - if strings.Contains(output, `"url": "http://`) { - t.Error("Did not expect HTTP URL field") + if !strings.Contains(output, `"$GH_AW_SAFE_OUTPUTS`) { + t.Error("Expected direct shell variable reference for Claude") } } func TestRenderSafeOutputsMCP_TOML(t *testing.T) { - pinnedGhAwNodeImage := resolveMCPGatewayContainerImage(constants.DefaultGhAwNodeImage, nil) renderer := NewMCPConfigRenderer(MCPRendererOptions{ IncludeCopilotFields: false, InlineArgs: false, @@ -170,33 +148,21 @@ func TestRenderSafeOutputsMCP_TOML(t *testing.T) { output := yaml.String() - // Verify TOML format with containerized stdio transport + // Verify TOML format with HTTP transport if !strings.Contains(output, "[mcp_servers.safeoutputs]") { t.Error("Expected TOML section header") } - if !strings.Contains(output, `container = "`+pinnedGhAwNodeImage+`"`) { - t.Error("Expected gh-aw node container image") - } - if !strings.Contains(output, `mounts = ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw/mcp-logs/safeoutputs:/tmp/gh-aw/mcp-logs/safeoutputs:rw"]`) { - t.Error("Expected TOML mounts") - } - if !strings.Contains(output, `args = ["-w", "$GITHUB_WORKSPACE"]`) { - t.Error("Expected TOML args") - } - if !strings.Contains(output, `entrypoint = "sh"`) { - t.Error("Expected TOML entrypoint override to sh") - } - if !strings.Contains(output, `entrypointArgs = ["-c", "exec node ${RUNNER_TEMP}/gh-aw/safeoutputs/safe_outputs_mcp_server.cjs"]`) { - t.Error("Expected TOML entrypointArgs to run the stdio MCP server script") + if !strings.Contains(output, `type = "http"`) { + t.Error("Expected TOML type field for HTTP transport") } - if !strings.Contains(output, `env_vars = ["DEBUG", "DEFAULT_BRANCH", "GH_AW_ASSETS_ALLOWED_EXTS", "GH_AW_ASSETS_BRANCH", "GH_AW_ASSETS_MAX_SIZE_KB", "GH_AW_MCP_LOG_DIR", "GH_AW_SAFE_OUTPUTS", "GH_AW_SAFE_OUTPUTS_CONFIG_PATH", "GH_AW_SAFE_OUTPUTS_TOOLS_PATH", "GITHUB_REPOSITORY", "GITHUB_SERVER_URL", "GITHUB_TOKEN", "GITHUB_WORKSPACE", "RUNNER_TEMP"]`) { - t.Error("Expected TOML env vars") + if !strings.Contains(output, `url = "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT"`) { + t.Error("Expected TOML HTTP URL") } - if strings.Contains(output, `type = "http"`) { - t.Error("Did not expect TOML HTTP type field") + if !strings.Contains(output, "[mcp_servers.safeoutputs.headers]") { + t.Error("Expected TOML headers section") } - if strings.Contains(output, `url = "http://`) { - t.Error("Did not expect TOML HTTP URL") + if !strings.Contains(output, `Authorization = "$GH_AW_SAFE_OUTPUTS_API_KEY"`) { + t.Error("Expected TOML Authorization header") } } diff --git a/pkg/workflow/mcp_setup_generator.go b/pkg/workflow/mcp_setup_generator.go index ed9d96b6c57..b42f69cdf02 100644 --- a/pkg/workflow/mcp_setup_generator.go +++ b/pkg/workflow/mcp_setup_generator.go @@ -321,6 +321,53 @@ func generateSafeOutputsSetup(c *Compiler, yaml *strings.Builder, safeOutputConf yaml.WriteString(" with:\n") yaml.WriteString(" script: |\n") yaml.WriteString(generateGitHubScriptWithRequire("generate_safe_outputs_tools.cjs")) + + yaml.WriteString(" - name: Generate Safe Outputs MCP Server Config\n") + yaml.WriteString(" id: safe-outputs-config\n") + yaml.WriteString(" run: |\n") + yaml.WriteString(" # Generate a secure random API key (360 bits of entropy, 40+ chars)\n") + yaml.WriteString(" # Mask immediately to prevent timing vulnerabilities\n") + yaml.WriteString(" API_KEY=$(openssl rand -base64 45 | tr -d '/+=')\n") + yaml.WriteString(" echo \"::add-mask::${API_KEY}\"\n") + yaml.WriteString(" \n") + fmt.Fprintf(yaml, " PORT=%d\n", constants.DefaultMCPInspectorPort) + yaml.WriteString(" \n") + yaml.WriteString(" # Set outputs for next steps\n") + yaml.WriteString(" {\n") + yaml.WriteString(" echo \"safe_outputs_api_key=${API_KEY}\"\n") + yaml.WriteString(" echo \"safe_outputs_port=${PORT}\"\n") + yaml.WriteString(" } >> \"$GITHUB_OUTPUT\"\n") + yaml.WriteString(" \n") + yaml.WriteString(" echo \"Safe Outputs MCP server will run on port ${PORT}\"\n") + yaml.WriteString(" \n") + + yaml.WriteString(" - name: Start Safe Outputs MCP HTTP Server\n") + yaml.WriteString(" id: safe-outputs-start\n") + yaml.WriteString(" env:\n") + yaml.WriteString(" DEBUG: '*'\n") + yaml.WriteString(" GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}\n") + yaml.WriteString(" GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }}\n") + yaml.WriteString(" GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }}\n") + yaml.WriteString(" GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json\n") + yaml.WriteString(" GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json\n") + yaml.WriteString(" GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs\n") + safeOutputsConfigEnvKeys, safeOutputsConfigEnvValues := buildSafeOutputsConfigRuntimeEnvVars(safeOutputConfig) + writeStepEnvVars(yaml, safeOutputsConfigEnvKeys, safeOutputsConfigEnvValues) + yaml.WriteString(" run: |\n") + yaml.WriteString(" # Environment variables are set above to prevent template injection\n") + yaml.WriteString(" export DEBUG\n") + yaml.WriteString(" export GH_AW_SAFE_OUTPUTS\n") + yaml.WriteString(" export GH_AW_SAFE_OUTPUTS_PORT\n") + yaml.WriteString(" export GH_AW_SAFE_OUTPUTS_API_KEY\n") + yaml.WriteString(" export GH_AW_SAFE_OUTPUTS_TOOLS_PATH\n") + yaml.WriteString(" export GH_AW_SAFE_OUTPUTS_CONFIG_PATH\n") + yaml.WriteString(" export GH_AW_MCP_LOG_DIR\n") + for _, varName := range safeOutputsConfigEnvKeys { + yaml.WriteString(" export " + varName + "\n") + } + yaml.WriteString(" \n") + yaml.WriteString(" bash \"${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh\"\n") + yaml.WriteString(" \n") } // safeOutputsSecretEnvPrefix is prepended to secret names when generating step env var names for diff --git a/pkg/workflow/safe_outputs_mcp_integration_test.go b/pkg/workflow/safe_outputs_mcp_integration_test.go index a51b7b8ae78..3bf6115eef5 100644 --- a/pkg/workflow/safe_outputs_mcp_integration_test.go +++ b/pkg/workflow/safe_outputs_mcp_integration_test.go @@ -8,7 +8,6 @@ import ( "strings" "testing" - "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/testutil" ) @@ -63,19 +62,9 @@ Test safe outputs workflow with MCP server integration. t.Error("Expected safeoutputs in MCP server configuration") } - // Check that the MCP server is configured as a containerized stdio MCP server - pinnedGhAwNodeImage := resolveMCPGatewayContainerImage(constants.DefaultGhAwNodeImage, nil) - if !strings.Contains(yamlStr, `"container": "`+pinnedGhAwNodeImage+`"`) { - t.Error("Expected safeoutputs MCP server to run in the gh-aw node container") - } - if !strings.Contains(yamlStr, `"mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw/mcp-logs/safeoutputs:/tmp/gh-aw/mcp-logs/safeoutputs:rw"]`) { - t.Error("Expected safeoutputs MCP server mounts for workspace, runtime files, and logs") - } - if !strings.Contains(yamlStr, `"entrypoint": "sh"`) { - t.Error("Expected safeoutputs MCP server to override container entrypoint to sh") - } - if !strings.Contains(yamlStr, `"entrypointArgs": ["-c", "exec node ${RUNNER_TEMP}/gh-aw/safeoutputs/safe_outputs_mcp_server.cjs"]`) { - t.Error("Expected safeoutputs MCP server entrypointArgs to run the stdio MCP server script") + // Check that the MCP server is configured with HTTP transport (per MCP Gateway spec) + if !strings.Contains(yamlStr, `"type": "http"`) { + t.Error("Expected safeoutputs MCP server to be configured with HTTP transport") } // Check that safe outputs config is written to file, not as environment variable @@ -83,10 +72,6 @@ Test safe outputs workflow with MCP server integration. t.Error("GH_AW_SAFE_OUTPUTS_CONFIG should NOT be in environment variables - config is now in file") } - if strings.Contains(yamlStr, "Start Safe Outputs MCP HTTP Server") { - t.Error("Expected safeoutputs MCP server to be listed in MCP servers, not launched in a dedicated step") - } - // Check that config file is created if !strings.Contains(yamlStr, `cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json"`) { t.Error("Expected config file to be created") @@ -209,19 +194,9 @@ Test safe outputs workflow with Codex engine. t.Error("Expected safeoutputs in Codex MCP server TOML configuration") } - // Check that the MCP server is configured as a containerized stdio MCP server in TOML - pinnedGhAwNodeImage := resolveMCPGatewayContainerImage(constants.DefaultGhAwNodeImage, nil) - if !strings.Contains(yamlStr, `container = "`+pinnedGhAwNodeImage+`"`) { - t.Error("Expected safeoutputs MCP server to run in the gh-aw node container in TOML") - } - if !strings.Contains(yamlStr, `mounts = ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw/mcp-logs/safeoutputs:/tmp/gh-aw/mcp-logs/safeoutputs:rw"]`) { - t.Error("Expected safeoutputs TOML MCP configuration to mount workspace, runtime files, and logs") - } - if !strings.Contains(yamlStr, `entrypoint = "sh"`) { - t.Error("Expected safeoutputs TOML MCP server to override container entrypoint to sh") - } - if !strings.Contains(yamlStr, `entrypointArgs = ["-c", "exec node ${RUNNER_TEMP}/gh-aw/safeoutputs/safe_outputs_mcp_server.cjs"]`) { - t.Error("Expected safeoutputs TOML MCP server entrypointArgs to run the stdio MCP server script") + // Check that the MCP server is configured with HTTP transport (per MCP Gateway spec) + if !strings.Contains(yamlStr, `type = "http"`) { + t.Error("Expected safeoutputs MCP server to be configured with HTTP transport in TOML") } t.Log("Safe outputs MCP server Codex integration test passed")