From a2a0d79296f11654d8c3ed796abff65318781c42 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:23:08 +0000 Subject: [PATCH 1/5] Add daily firewall image security scan Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../workflows/daily-squid-image-scan.lock.yml | 1902 +++++++++++++++++ .github/workflows/daily-squid-image-scan.md | 314 +++ .grant.yaml | 11 + 3 files changed, 2227 insertions(+) create mode 100644 .github/workflows/daily-squid-image-scan.lock.yml create mode 100644 .github/workflows/daily-squid-image-scan.md create mode 100644 .grant.yaml diff --git a/.github/workflows/daily-squid-image-scan.lock.yml b/.github/workflows/daily-squid-image-scan.lock.yml new file mode 100644 index 00000000000..6189c20ce69 --- /dev/null +++ b/.github/workflows/daily-squid-image-scan.lock.yml @@ -0,0 +1,1902 @@ +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"af298f30c23b125b2900b9346d2010b0b5dcfee55fa40cd720eeb4a372886155","body_hash":"a785398107e35cec799aae4088095a642fc5bb6dae012a57eb34bc62d60bb224","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"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":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.37","digest":"sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.37@sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.37","digest":"sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.37@sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.37","digest":"sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.37@sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"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.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} +# 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 +# +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# Scan the pinned agentic workflow firewall image for vulnerabilities, updates, and rejected licenses +# +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 +# - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) +# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 +# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 +# +# Container images used: +# - ghcr.io/github/gh-aw-firewall/agent:0.27.37@sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.37@sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317 +# - ghcr.io/github/gh-aw-firewall/squid:0.27.37@sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b +# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 + +name: "Daily Squid Image Security Scan" +on: + schedule: + - cron: "26 5 * * *" # Friendly format: daily (scattered) + workflow_dispatch: + inputs: + aw_context: + default: "" + description: "Agent caller context (used internally by Agentic Workflows)." + required: false + type: string + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}" + +run-name: "Daily Squid Image Security Scan" + +jobs: + activation: + runs-on: ubuntu-slim + permissions: + actions: read + contents: read + env: + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + outputs: + comment_id: "" + comment_repo: "" + daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }} + daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }} + daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }} + engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} + model: ${{ steps.generate_aw_info.outputs.model }} + oauth_token_check_failed: ${{ steps.check-oauth-tokens.outputs.oauth_token_check_failed == 'true' }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: github/gh-aw + sparse-checkout: | + actions + clean: false + persist-credentials: false + - name: Setup Scripts + id: setup + uses: ./actions/setup + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-squid-image-scan.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.37" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Generate agentic run info + id: generate_aw_info + env: + 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.73" + GH_AW_INFO_AGENT_VERSION: "1.0.73" + GH_AW_INFO_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.27.37" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "squid" + GH_AW_INFO_FRONTMATTER_EMOJI: "🛡️" + GH_AW_COMPILED_STRICT: "true" + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + 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/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@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + key: agentic-workflow-usage-dailysquidimagescan-${{ github.run_id }} + restore-keys: agentic-workflow-usage-dailysquidimagescan- + 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 != '' }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_WORKFLOW_ID: "daily-squid-image-scan" + 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: + 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/check_daily_aic_workflow_guardrail.cjs'); + await main(); + - name: Check for OAuth tokens + id: check-oauth-tokens + run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh" + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + - name: Checkout .github and .agents folders + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + sparse-checkout: | + .github + .agents + actions/setup + .antigravity + .claude + .codex + .gemini + .opencode + .pi + sparse-checkout-cone-mode: true + fetch-depth: 1 + - name: Save agent config folders for base branch restoration + env: + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" + - name: Check workflow lock file + id: check-lock-file + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_FILE: "daily-squid-image-scan.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" + 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_workflow_timestamp_api.cjs'); + await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + # poutine:ignore untrusted_checkout_exec + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" + { + cat << 'GH_AW_PROMPT_904e367dd70b6dbb_EOF' + + GH_AW_PROMPT_904e367dd70b6dbb_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_904e367dd70b6dbb_EOF' + + Tools: create_issue, missing_tool, missing_data, noop + + GH_AW_PROMPT_904e367dd70b6dbb_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_904e367dd70b6dbb_EOF' + + The following GitHub context information is available for this workflow: + {{#if github.actor}} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if github.repository}} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if github.workspace}} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} + - **issue-number**: #__GH_AW_EXPR_802A9F6A__ + {{/if}} + {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} + - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ + {{/if}} + {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} + - **pull-request-number**: #__GH_AW_EXPR_463A214A__ + {{/if}} + {{#if github.event.comment.id || github.aw.context.comment_id}} + - **comment-id**: __GH_AW_EXPR_FF1D34CE__ + {{/if}} + {{#if github.run_id}} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_904e367dd70b6dbb_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" + cat << 'GH_AW_PROMPT_904e367dd70b6dbb_EOF' + + {{#runtime-import .github/workflows/daily-squid-image-scan.md}} + GH_AW_PROMPT_904e367dd70b6dbb_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_ENGINE_ID: "copilot" + 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/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: activation + include-hidden-files: true + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/models.json + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw-prompts/prompt-template.txt + /tmp/gh-aw/aw-prompts/prompt-import-tree.json + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/base + /tmp/gh-aw/.github/agents + /tmp/gh-aw/.github/skills + if-no-files-found: ignore + retention-days: 1 + + agent: + needs: + - activation + - scan_image + if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' + runs-on: ubuntu-latest + permissions: + contents: read + copilot-requests: write + issues: read + concurrency: + group: "gh-aw-copilot-${{ github.workflow }}" + queue: max + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_PROJECT_UTC: "-08:00" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + GH_AW_WORKFLOW_ID_SANITIZED: dailysquidimagescan + outputs: + agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + 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 }} + 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 }} + http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }} + inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }} + mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} + model: ${{ needs.activation.outputs.model }} + model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: github/gh-aw + sparse-checkout: | + actions + clean: false + persist-credentials: false + - name: Setup Scripts + id: setup + uses: ./actions/setup + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-squid-image-scan.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.37" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Set runtime paths + id: set-runtime-paths + run: | + { + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" + } >> "$GITHUB_OUTPUT" + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Download image scan results + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: squid-image-scan + path: /tmp/gh-aw/agent/image-scan + + - name: Configure Git credentials + env: + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" + - name: Checkout PR branch + id: checkout-pr + if: | + github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_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/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.37 --rootless + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + with: + script: | + const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); + await determineAutomaticLockdown(github, context, core); + - name: Restore agent config folders from base branch + if: steps.checkout-pr.outcome == 'success' + env: + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: "AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" + - name: Restore inline skills from activation artifact + env: + 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.37@sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.37@sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317 ghcr.io/github/gh-aw-firewall/squid:0.27.37@sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 + - name: Generate Safe Outputs Config + run: | + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_2907ddee8e809b31_EOF' + {"create_issue":{"deduplicate_by_title":true,"labels":["cookie","security"],"max":1,"title_prefix":"[squid-image-scan] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_2907ddee8e809b31_EOF + - name: Generate Safe Outputs Tools + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[squid-image-scan] \". Labels [\"cookie\" \"security\"] will be automatically added." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "create_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000, + "minLength": 20 + }, + "fields": { + "type": "array" + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "parent": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "temporary_id": { + "type": "string" + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } + } + } + } + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + 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/generate_safe_outputs_tools.cjs'); + await main(); + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + 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_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} + GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="8080" + export MCP_GATEWAY_DOMAIN="awmg-mcpg" + export MCP_GATEWAY_HOST_DOMAIN="localhost" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + export GH_AW_MCP_CLI_SERVERS='["github","safeoutputs"]' + MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') + MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') + source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh" + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --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_POLICY_ALLOW_CREATE_PULL_REQUEST -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 -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 -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3' + + 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_4d6f106ca28cda70_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v1.6.0", + "env": { + "GITHUB_FEATURES": "fields_param", + "GITHUB_HOST": "${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" + }, + "guard-policies": { + "allow-only": { + "min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY", + "repos": "$GITHUB_MCP_GUARD_REPOS" + } + } + }, + "safeoutputs": { + "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:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "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}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_SHA": "\${GITHUB_SHA}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ], + "sink-visibility": ${{ toJSON(steps.determine-automatic-lockdown.outputs.visibility) }} + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}", + "startupTimeout": 120 + } + } + GH_AW_MCP_CONFIG_4d6f106ca28cda70_EOF + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); + - name: Clean credentials + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Audit pre-agent workspace + id: pre_agent_audit + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + # --allow-tool github + # --allow-tool safeoutputs + # --allow-tool shell(cat /tmp/gh-aw/agent/image-scan/*) + # --allow-tool shell(cat) + # --allow-tool shell(date) + # --allow-tool shell(echo) + # --allow-tool shell(github:*) + # --allow-tool shell(grep) + # --allow-tool shell(head) + # --allow-tool shell(jq * /tmp/gh-aw/agent/image-scan/*) + # --allow-tool shell(ls) + # --allow-tool shell(printf) + # --allow-tool shell(pwd) + # --allow-tool shell(safeoutputs:*) + # --allow-tool shell(sort) + # --allow-tool shell(tail) + # --allow-tool shell(uniq) + # --allow-tool shell(wc) + # --allow-tool shell(yq) + # --allow-tool write + timeout-minutes: 20 + run: | + set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" + export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json" + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + 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="${GH_AW_MAX_AI_CREDITS:-1000}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.37/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"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\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"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\",\"kimi\"],\"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*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"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-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"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.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"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*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"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\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"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-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.37,squid=sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b,agent=sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67,api-proxy=sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317,cli-proxy=sha256:1d5300d9b08e1c4f2ad1830860656a0656383a83280058f17e805a7c3ecda203\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${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 + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_CHROOT_BINARIES_SOURCE_PATH="${RUNNER_TEMP}/gh-aw" GH_AW_CHROOT_IDENTITY_HOME="${RUNNER_TEMP}/gh-aw/home" node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" + fi + GH_AW_TOOL_CACHE_MOUNT="" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" + if [ -d "$GH_AW_TOOL_CACHE" ]; then + if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then + GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" + fi + fi + # shellcheck disable=SC1003,SC2016,SC2086 + 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"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/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-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat /tmp/gh-aw/agent/image-scan/*)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(github:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq * /tmp/gh-aw/agent/image-scan/*)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + AWF_REFLECT_ENABLED: 1 + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ github.token }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} + 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: 20 + GH_AW_VERSION: dev + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + 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: Detect agent errors + if: always() + id: detect-agent-errors + continue-on-error: true + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" + - name: Configure Git credentials + env: + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + 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/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Append agent step summary + if: always() + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" + - name: Copy Safe Outputs + if: always() + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "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" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + 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/collect_ndjson_output.cjs'); + await main(); + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + 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/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + id: parse-mcp-gateway + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + 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/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_firewall_logs.sh" --rootless + - name: Parse token usage for step summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + 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/parse_token_usage.cjs'); + await main(); + - name: Print AWF reflect summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + 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/awf_reflect_summary.cjs'); + await main(); + - name: Write agent output placeholder if missing + if: always() + run: | + if [ ! -f /tmp/gh-aw/agent_output.json ]; then + echo '{"items":[]}' > /tmp/gh-aw/agent_output.json + fi + - if: always() + name: Enforce critical vulnerability and license gates + run: |- + summary="/tmp/gh-aw/agent/image-scan/summary.json" + jq -e ' + (.critical_vulnerabilities == 0) and + (.license_rejected == false) and + ((.operational_errors | length) == 0) + ' "$summary" + + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: agent + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/agent_usage.json + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/pre-agent-audit.txt + /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/aw-*.patch + /tmp/gh-aw/aw-*.bundle + /tmp/gh-aw/awf-config.json + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/sandbox/firewall/audit/ + /tmp/gh-aw/sandbox/firewall/awf-reflect.json + if-no-files-found: ignore + + conclusion: + needs: + - activation + - agent + - detection + - safe_outputs + - scan_image + if: > + always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || + needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' || + needs.activation.outputs.secret_verification_result == 'failed' || needs.activation.outputs.daily_ai_credits_exceeded == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + concurrency: + group: "gh-aw-conclusion-daily-squid-image-scan" + cancel-in-progress: false + queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Checkout actions folder + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: github/gh-aw + sparse-checkout: | + actions + clean: false + persist-credentials: false + - name: Setup Scripts + id: setup + uses: ./actions/setup + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-squid-image-scan.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.37" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Download safe outputs items manifest + id: download-safe-outputs-manifest + if: always() + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: safe-outputs-items + path: /tmp/gh-aw/ + - name: Collect usage artifact files + if: always() + continue-on-error: true + run: | + mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection + echo "Usage artifact source file status:" + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" + done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true + [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true + [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true + [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true + [ -f /tmp/gh-aw/evals/evals.jsonl ] && cp /tmp/gh-aw/evals/evals.jsonl /tmp/gh-aw/usage/evals.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl + [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node "${RUNNER_TEMP}/gh-aw/actions/generate_usage_activity_summary.cjs" + find /tmp/gh-aw/usage -type f -print | sort + - name: Upload usage artifact + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: usage + path: | + /tmp/gh-aw/usage/aw_info.json + /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json + /tmp/gh-aw/usage/agent_usage.jsonl + /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/evals.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl + /tmp/gh-aw/usage/agent/token_usage.jsonl + /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json + 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@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + key: agentic-workflow-usage-dailysquidimagescan-${{ github.run_id }} + restore-keys: agentic-workflow-usage-dailysquidimagescan- + 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@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + key: agentic-workflow-usage-dailysquidimagescan-${{ 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 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/daily-squid-image-scan.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} + GH_AW_WORKFLOW_ID: "daily-squid-image-scan" + with: + github-token: ${{ secrets.GH_AW_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/handle_noop_message.cjs'); + await main(); + - name: Log detection run + id: detection_runs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/daily-squid-image-scan.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + with: + github-token: ${{ secrets.GH_AW_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/handle_detection_runs.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/daily-squid-image-scan.md" + with: + github-token: ${{ secrets.GH_AW_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/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/daily-squid-image-scan.md" + with: + github-token: ${{ secrets.GH_AW_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/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure + id: handle_agent_failure + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/daily-squid-image-scan.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "daily-squid-image-scan" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "12" + GH_AW_ENGINE_ID: "copilot" + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} + GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} + GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} + GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" + GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_OAUTH_TOKEN_CHECK_FAILED: ${{ needs.activation.outputs.oauth_token_check_failed }} + GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_DAILY_AI_CREDITS_EXCEEDED: ${{ needs.activation.outputs.daily_ai_credits_exceeded }} + GH_AW_DAILY_AI_CREDITS_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_ai_credits_total_effective_tokens }} + GH_AW_DAILY_AI_CREDITS_THRESHOLD: ${{ needs.activation.outputs.daily_ai_credits_threshold }} + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" + GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" + GH_AW_TIMEOUT_MINUTES: "20" + with: + github-token: ${{ secrets.GH_AW_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/handle_agent_failure.cjs'); + await main(); + + detection: + needs: + - activation + - agent + if: always() && needs.agent.result != 'skipped' + runs-on: ubuntu-latest + permissions: + contents: read + copilot-requests: write + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + outputs: + aic: ${{ steps.parse_detection_token_usage.outputs.aic }} + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_reason: ${{ steps.detection_conclusion.outputs.reason }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + steps: + - name: Checkout actions folder + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: github/gh-aw + sparse-checkout: | + actions + clean: false + persist-credentials: false + - name: Setup Scripts + id: setup + uses: ./actions/setup + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-squid-image-scan.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.37" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository for patch context + if: needs.agent.outputs.has_patch == 'true' + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + # --- Threat Detection --- + - name: Clean stale firewall files from agent artifact + run: | + 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.37@sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.37@sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317 ghcr.io/github/gh-aw-firewall/squid:0.27.37@sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP Config for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" + rm -f "$HOME/.copilot/mcp-config.json" + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + rm -f /tmp/gh-aw/agent_usage.json + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then + echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context." + fi + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + for f in /tmp/gh-aw/aw-*.bundle; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + WORKFLOW_NAME: "Daily Squid Image Security Scan" + WORKFLOW_DESCRIPTION: "Scan the pinned agentic workflow firewall image for vulnerabilities, updates, and rejected licenses" + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + 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/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Setup Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + 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.73 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.37 + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + 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="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.37/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},\"maxCacheMisses\":5,\"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\",\"kimi\"],\"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*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"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-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"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.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"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*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"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\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"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-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.37,squid=sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b,agent=sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67,api-proxy=sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317,cli-proxy=sha256:1d5300d9b08e1c4f2ad1830860656a0656383a83280058f17e805a7c3ecda203\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${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 + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + fi + GH_AW_TOOL_CACHE_MOUNT="" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" + if [ -d "$GH_AW_TOOL_CACHE" ]; then + if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then + GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" + fi + fi + # shellcheck disable=SC1003,SC2016,SC2086 + 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"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/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 + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + 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_LLM_PROVIDER: github + 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: dev + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + 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() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage + 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/parse_token_usage.cjs'); + await main(); + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: detection + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Parse and conclude threat detection + id: detection_conclusion + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} + GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" + with: + script: | + try { + 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/parse_threat_detection_results.cjs'); + await main(); + } catch (loadErr) { + const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; + const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); + core.error(msg); + core.setOutput('reason', 'parse_error'); + if (continueOnError && !detectionExecutionFailed) { + core.warning('\u26A0\uFE0F ' + msg); + core.setOutput('conclusion', 'warning'); + core.setOutput('success', 'false'); + } else { + core.setOutput('conclusion', 'failure'); + core.setOutput('success', 'false'); + core.setFailed(msg); + } + } + + safe_outputs: + needs: + - activation + - agent + - detection + if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + timeout-minutes: 45 + env: + GH_AW_AGENT_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/daily-squid-image-scan" + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + 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.73" + GH_AW_PROJECT_UTC: "-08:00" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_WORKFLOW_EMOJI: "🛡️" + GH_AW_WORKFLOW_ID: "daily-squid-image-scan" + GH_AW_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/daily-squid-image-scan.md" + outputs: + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} + created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} + 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: github/gh-aw + sparse-checkout: | + actions + clean: false + persist-credentials: false + - name: Setup Scripts + id: setup + uses: ./actions/setup + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-squid-image-scan.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.37" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + 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://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_ALLOWED_DOMAINS: "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" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"deduplicate_by_title\":true,\"labels\":[\"cookie\",\"security\"],\"max\":1,\"title_prefix\":\"[squid-image-scan] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + with: + github-token: ${{ secrets.GH_AW_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/safe_output_handler_manager.cjs'); + await main(); + - name: Upload Safe Outputs Items + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: safe-outputs-items + path: | + /tmp/gh-aw/safe-output-items.jsonl + /tmp/gh-aw/temporary-id-map.json + if-no-files-found: ignore + + scan_image: + name: Scan immutable firewall image + needs: activation + runs-on: ubuntu-latest + permissions: + contents: read + packages: read + + timeout-minutes: 30 + steps: + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + 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://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - name: Install pinned security tools + id: install_tools + run: | + set -euo pipefail + test "$(uname -m)" = "x86_64" + tools_dir="$RUNNER_TEMP/image-scan-tools" + mkdir -p "$tools_dir" + + install_tool() { + tool="$1" + version="$2" + checksum="$3" + archive="$RUNNER_TEMP/${tool}.tar.gz" + curl -fsSL \ + "https://github.com/anchore/${tool}/releases/download/v${version}/${tool}_${version}_linux_amd64.tar.gz" \ + -o "$archive" + echo "${checksum} ${archive}" | sha256sum -c - + tar -xzf "$archive" --no-same-owner -C "$tools_dir" "$tool" + } + + install_tool syft "$SYFT_VERSION" "$SYFT_SHA256" + install_tool grype "$GRYPE_VERSION" "$GRYPE_SHA256" + install_tool grant "$GRANT_VERSION" "$GRANT_SHA256" + echo "$tools_dir" >> "$GITHUB_PATH" + env: + GRANT_SHA256: 6500f8bbf0f20fb993de8084686e199f0ba1eb494769ff75454286d5ef63f919 + GRANT_VERSION: 0.6.8 + GRYPE_SHA256: 40aff724297312f91ea390d003bed8d8651c74cc7f5b26732db80b3a408d2fc5 + GRYPE_VERSION: 0.116.0 + SYFT_SHA256: 7aa2f03ee92739cf643279ba3990548b9925d4e22cae13f46831ee62821147fe + SYFT_VERSION: 1.49.0 + continue-on-error: true + - name: Scan each platform image + if: always() + run: | + set -uo pipefail + output="$RUNNER_TEMP/squid-image-scan" + mkdir -p "$output" + : > "$output/errors.txt" + : > "$output/platforms.tsv" + + record_error() { + printf '%s\n' "$1" | tee -a "$output/errors.txt" + } + + image_prefix="ghcr.io/github/gh-aw-firewall/squid:" + pin_file="pkg/actionpins/data/action_pins.json" + mirror_file="pkg/workflow/data/action_pins.json" + + pin_entry=$(jq -cer --arg prefix "$image_prefix" ' + [.entries | to_entries[] | select(.key | startswith($prefix))] + | if length == 1 then .[0] else error("expected exactly one squid image pin") end + ' "$pin_file") || record_error "Unable to read the canonical Squid image pin." + mirror_entry=$(jq -cer --arg prefix "$image_prefix" ' + [.entries | to_entries[] | select(.key | startswith($prefix))] + | if length == 1 then .[0] else error("expected exactly one squid image pin") end + ' "$mirror_file") || record_error "Unable to read the mirrored Squid image pin." + + if [ -z "${pin_entry:-}" ] || [ -z "${mirror_entry:-}" ]; then + image_tag="unknown" + pinned_image="unknown" + index_digest="unknown" + else + if [ "$pin_entry" != "$mirror_entry" ]; then + record_error "The canonical and mirrored Squid image pins disagree." + fi + image_tag=$(jq -r '.key' <<<"$pin_entry") + pinned_image=$(jq -r '.value.pinned_image' <<<"$pin_entry") + index_digest=$(jq -r '.value.digest' <<<"$pin_entry") + if [[ ! "$pinned_image" =~ @sha256:[0-9a-f]{64}$ ]] || + [[ ! "$index_digest" =~ ^sha256:[0-9a-f]{64}$ ]]; then + record_error "The Squid image is not pinned to a valid SHA-256 digest." + fi + fi + + current_digest="unknown" + image_updated=false + if [ "$image_tag" != "unknown" ]; then + if current_digest=$( + docker buildx imagetools inspect "$image_tag" --format '{{json .Manifest}}' | + jq -er '.digest' + ); then + if [ "$current_digest" != "$index_digest" ]; then + image_updated=true + fi + else + current_digest="unknown" + record_error "Unable to resolve the current Squid image tag digest." + fi + fi + + tools_ready=true + if [ "$INSTALL_OUTCOME" != "success" ]; then + tools_ready=false + record_error "Pinned security tool installation failed." + fi + + total_vulnerabilities=0 + critical_vulnerabilities=0 + fixable_vulnerabilities=0 + license_rejected=false + + if [ "$pinned_image" != "unknown" ]; then + if docker buildx imagetools inspect "$pinned_image" --raw > "$output/index.json"; then + if ! echo "${index_digest#sha256:} $output/index.json" | sha256sum -c -; then + record_error "The downloaded image index does not match its pinned digest." + fi + else + record_error "Unable to download the pinned image index." + echo '{"manifests":[]}' > "$output/index.json" + fi + else + echo '{"manifests":[]}' > "$output/index.json" + fi + + if [ "$tools_ready" = true ]; then + if ! grype db update > "$output/grype-db-update.txt" 2>&1; then + record_error "Grype vulnerability database update failed." + fi + grype db status > "$output/grype-db-status.txt" 2>&1 || true + fi + + for platform in linux/amd64 linux/arm64; do + arch="${platform#linux/}" + suffix="linux-${arch}" + child_digest=$(jq -er --arg arch "$arch" ' + [.manifests[] | select(.platform.os == "linux" and .platform.architecture == $arch)] + | if length == 1 then .[0].digest else error("expected exactly one platform manifest") end + ' "$output/index.json") || { + record_error "Unable to resolve exactly one ${platform} image manifest." + continue + } + printf '%s\t%s\n' "$platform" "$child_digest" >> "$output/platforms.tsv" + + if [ "$tools_ready" != true ]; then + continue + fi + + image_repository="${image_tag%:*}" + immutable_image="${image_repository}@${child_digest}" + syft_json="$output/sbom-${suffix}.syft.json" + spdx_json="$output/sbom-${suffix}.spdx.json" + + if ! syft --platform "$platform" "$immutable_image" \ + -o "syft-json=${syft_json}" \ + -o "spdx-json=${spdx_json}" > "$output/syft-${suffix}.txt" 2>&1; then + record_error "Syft failed to scan ${platform} at ${child_digest}." + continue + fi + + if ! GRYPE_DB_AUTO_UPDATE=false grype "sbom:${syft_json}" \ + -o json > "$output/grype-${suffix}.json" 2> "$output/grype-${suffix}.stderr"; then + record_error "Grype failed to produce JSON results for ${platform}." + continue + fi + + platform_total=$(jq '.matches | length' "$output/grype-${suffix}.json") + platform_critical=$(jq ' + [.matches[] | select((.vulnerability.severity // "") | ascii_downcase == "critical")] + | length + ' "$output/grype-${suffix}.json") + platform_fixable=$(jq ' + [.matches[] | select((.vulnerability.fix.versions // []) | length > 0)] + | length + ' "$output/grype-${suffix}.json") + total_vulnerabilities=$((total_vulnerabilities + platform_total)) + critical_vulnerabilities=$((critical_vulnerabilities + platform_critical)) + fixable_vulnerabilities=$((fixable_vulnerabilities + platform_fixable)) + + GRYPE_DB_AUTO_UPDATE=false grype "sbom:${syft_json}" \ + --fail-on critical -o table > "$output/grype-${suffix}.txt" 2>&1 + grype_exit=$? + if [ "$grype_exit" -ne 0 ] && [ "$platform_critical" -eq 0 ]; then + record_error "Grype table scan failed unexpectedly for ${platform}." + fi + + if ! grant list "$spdx_json" > "$output/grant-list-${suffix}.txt" 2>&1; then + record_error "Grant could not list licenses for ${platform}." + fi + grant check --config .grant.yaml "$spdx_json" \ + > "$output/grant-check-${suffix}.txt" 2>&1 + if [ "$?" -ne 0 ]; then + license_rejected=true + fi + done + + errors=$(jq -Rs 'split("\n") | map(select(length > 0))' "$output/errors.txt") + platforms=$(jq -Rn ' + [inputs | split("\t") | {platform: .[0], digest: .[1]}] + ' < "$output/platforms.tsv") + jq -n \ + --arg image_tag "$image_tag" \ + --arg pinned_image "$pinned_image" \ + --arg index_digest "$index_digest" \ + --arg current_digest "$current_digest" \ + --argjson image_updated "$image_updated" \ + --argjson platforms "$platforms" \ + --argjson total_vulnerabilities "$total_vulnerabilities" \ + --argjson critical_vulnerabilities "$critical_vulnerabilities" \ + --argjson fixable_vulnerabilities "$fixable_vulnerabilities" \ + --argjson license_rejected "$license_rejected" \ + --argjson operational_errors "$errors" \ + '{ + image_tag: $image_tag, + pinned_image: $pinned_image, + index_digest: $index_digest, + current_digest: $current_digest, + image_updated: $image_updated, + platforms: $platforms, + total_vulnerabilities: $total_vulnerabilities, + critical_vulnerabilities: $critical_vulnerabilities, + fixable_vulnerabilities: $fixable_vulnerabilities, + license_rejected: $license_rejected, + operational_errors: $operational_errors, + tools: { + syft: "1.49.0", + grype: "0.116.0", + grant: "0.6.8" + } + }' > "$output/summary.json" + env: + INSTALL_OUTCOME: ${{ steps.install_tools.outcome }} + - name: Upload image scan results + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + if-no-files-found: error + name: squid-image-scan + path: ${{ runner.temp }}/squid-image-scan + retention-days: 14 diff --git a/.github/workflows/daily-squid-image-scan.md b/.github/workflows/daily-squid-image-scan.md new file mode 100644 index 00000000000..75448a9fa3e --- /dev/null +++ b/.github/workflows/daily-squid-image-scan.md @@ -0,0 +1,314 @@ +--- +name: Daily Squid Image Security Scan +description: Scan the pinned agentic workflow firewall image for vulnerabilities, updates, and rejected licenses +emoji: "🛡️" +on: + schedule: daily + workflow_dispatch: +permissions: + contents: read + issues: read + copilot-requests: write +strict: true +network: + allowed: + - defaults +tools: + cli-proxy: true + bash: + - "cat /tmp/gh-aw/agent/image-scan/*" + - "jq * /tmp/gh-aw/agent/image-scan/*" +safe-outputs: + create-issue: + title-prefix: "[squid-image-scan] " + labels: [cookie, security] + max: 1 + deduplicate-by-title: true +steps: + - name: Download image scan results + uses: actions/download-artifact@v8.0.1 + with: + name: squid-image-scan + path: /tmp/gh-aw/agent/image-scan +post-steps: + - name: Enforce critical vulnerability and license gates + if: always() + run: | + summary="/tmp/gh-aw/agent/image-scan/summary.json" + jq -e ' + (.critical_vulnerabilities == 0) and + (.license_rejected == false) and + ((.operational_errors | length) == 0) + ' "$summary" +jobs: + scan_image: + name: Scan immutable firewall image + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + packages: read + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.0 + with: + persist-credentials: false + - name: Install pinned security tools + id: install_tools + continue-on-error: true + env: + SYFT_VERSION: "1.49.0" + SYFT_SHA256: "7aa2f03ee92739cf643279ba3990548b9925d4e22cae13f46831ee62821147fe" + GRYPE_VERSION: "0.116.0" + GRYPE_SHA256: "40aff724297312f91ea390d003bed8d8651c74cc7f5b26732db80b3a408d2fc5" + GRANT_VERSION: "0.6.8" + GRANT_SHA256: "6500f8bbf0f20fb993de8084686e199f0ba1eb494769ff75454286d5ef63f919" + run: | + set -euo pipefail + test "$(uname -m)" = "x86_64" + tools_dir="$RUNNER_TEMP/image-scan-tools" + mkdir -p "$tools_dir" + + install_tool() { + tool="$1" + version="$2" + checksum="$3" + archive="$RUNNER_TEMP/${tool}.tar.gz" + curl -fsSL \ + "https://github.com/anchore/${tool}/releases/download/v${version}/${tool}_${version}_linux_amd64.tar.gz" \ + -o "$archive" + echo "${checksum} ${archive}" | sha256sum -c - + tar -xzf "$archive" --no-same-owner -C "$tools_dir" "$tool" + } + + install_tool syft "$SYFT_VERSION" "$SYFT_SHA256" + install_tool grype "$GRYPE_VERSION" "$GRYPE_SHA256" + install_tool grant "$GRANT_VERSION" "$GRANT_SHA256" + echo "$tools_dir" >> "$GITHUB_PATH" + - name: Scan each platform image + if: always() + env: + INSTALL_OUTCOME: ${{ steps.install_tools.outcome }} + run: | + set -uo pipefail + output="$RUNNER_TEMP/squid-image-scan" + mkdir -p "$output" + : > "$output/errors.txt" + : > "$output/platforms.tsv" + + record_error() { + printf '%s\n' "$1" | tee -a "$output/errors.txt" + } + + image_prefix="ghcr.io/github/gh-aw-firewall/squid:" + pin_file="pkg/actionpins/data/action_pins.json" + mirror_file="pkg/workflow/data/action_pins.json" + + pin_entry=$(jq -cer --arg prefix "$image_prefix" ' + [.entries | to_entries[] | select(.key | startswith($prefix))] + | if length == 1 then .[0] else error("expected exactly one squid image pin") end + ' "$pin_file") || record_error "Unable to read the canonical Squid image pin." + mirror_entry=$(jq -cer --arg prefix "$image_prefix" ' + [.entries | to_entries[] | select(.key | startswith($prefix))] + | if length == 1 then .[0] else error("expected exactly one squid image pin") end + ' "$mirror_file") || record_error "Unable to read the mirrored Squid image pin." + + if [ -z "${pin_entry:-}" ] || [ -z "${mirror_entry:-}" ]; then + image_tag="unknown" + pinned_image="unknown" + index_digest="unknown" + else + if [ "$pin_entry" != "$mirror_entry" ]; then + record_error "The canonical and mirrored Squid image pins disagree." + fi + image_tag=$(jq -r '.key' <<<"$pin_entry") + pinned_image=$(jq -r '.value.pinned_image' <<<"$pin_entry") + index_digest=$(jq -r '.value.digest' <<<"$pin_entry") + if [[ ! "$pinned_image" =~ @sha256:[0-9a-f]{64}$ ]] || + [[ ! "$index_digest" =~ ^sha256:[0-9a-f]{64}$ ]]; then + record_error "The Squid image is not pinned to a valid SHA-256 digest." + fi + fi + + current_digest="unknown" + image_updated=false + if [ "$image_tag" != "unknown" ]; then + if current_digest=$( + docker buildx imagetools inspect "$image_tag" --format '{{json .Manifest}}' | + jq -er '.digest' + ); then + if [ "$current_digest" != "$index_digest" ]; then + image_updated=true + fi + else + current_digest="unknown" + record_error "Unable to resolve the current Squid image tag digest." + fi + fi + + tools_ready=true + if [ "$INSTALL_OUTCOME" != "success" ]; then + tools_ready=false + record_error "Pinned security tool installation failed." + fi + + total_vulnerabilities=0 + critical_vulnerabilities=0 + fixable_vulnerabilities=0 + license_rejected=false + + if [ "$pinned_image" != "unknown" ]; then + if docker buildx imagetools inspect "$pinned_image" --raw > "$output/index.json"; then + if ! echo "${index_digest#sha256:} $output/index.json" | sha256sum -c -; then + record_error "The downloaded image index does not match its pinned digest." + fi + else + record_error "Unable to download the pinned image index." + echo '{"manifests":[]}' > "$output/index.json" + fi + else + echo '{"manifests":[]}' > "$output/index.json" + fi + + if [ "$tools_ready" = true ]; then + if ! grype db update > "$output/grype-db-update.txt" 2>&1; then + record_error "Grype vulnerability database update failed." + fi + grype db status > "$output/grype-db-status.txt" 2>&1 || true + fi + + for platform in linux/amd64 linux/arm64; do + arch="${platform#linux/}" + suffix="linux-${arch}" + child_digest=$(jq -er --arg arch "$arch" ' + [.manifests[] | select(.platform.os == "linux" and .platform.architecture == $arch)] + | if length == 1 then .[0].digest else error("expected exactly one platform manifest") end + ' "$output/index.json") || { + record_error "Unable to resolve exactly one ${platform} image manifest." + continue + } + printf '%s\t%s\n' "$platform" "$child_digest" >> "$output/platforms.tsv" + + if [ "$tools_ready" != true ]; then + continue + fi + + image_repository="${image_tag%:*}" + immutable_image="${image_repository}@${child_digest}" + syft_json="$output/sbom-${suffix}.syft.json" + spdx_json="$output/sbom-${suffix}.spdx.json" + + if ! syft --platform "$platform" "$immutable_image" \ + -o "syft-json=${syft_json}" \ + -o "spdx-json=${spdx_json}" > "$output/syft-${suffix}.txt" 2>&1; then + record_error "Syft failed to scan ${platform} at ${child_digest}." + continue + fi + + if ! GRYPE_DB_AUTO_UPDATE=false grype "sbom:${syft_json}" \ + -o json > "$output/grype-${suffix}.json" 2> "$output/grype-${suffix}.stderr"; then + record_error "Grype failed to produce JSON results for ${platform}." + continue + fi + + platform_total=$(jq '.matches | length' "$output/grype-${suffix}.json") + platform_critical=$(jq ' + [.matches[] | select((.vulnerability.severity // "") | ascii_downcase == "critical")] + | length + ' "$output/grype-${suffix}.json") + platform_fixable=$(jq ' + [.matches[] | select((.vulnerability.fix.versions // []) | length > 0)] + | length + ' "$output/grype-${suffix}.json") + total_vulnerabilities=$((total_vulnerabilities + platform_total)) + critical_vulnerabilities=$((critical_vulnerabilities + platform_critical)) + fixable_vulnerabilities=$((fixable_vulnerabilities + platform_fixable)) + + GRYPE_DB_AUTO_UPDATE=false grype "sbom:${syft_json}" \ + --fail-on critical -o table > "$output/grype-${suffix}.txt" 2>&1 + grype_exit=$? + if [ "$grype_exit" -ne 0 ] && [ "$platform_critical" -eq 0 ]; then + record_error "Grype table scan failed unexpectedly for ${platform}." + fi + + if ! grant list "$spdx_json" > "$output/grant-list-${suffix}.txt" 2>&1; then + record_error "Grant could not list licenses for ${platform}." + fi + grant check --config .grant.yaml "$spdx_json" \ + > "$output/grant-check-${suffix}.txt" 2>&1 + if [ "$?" -ne 0 ]; then + license_rejected=true + fi + done + + errors=$(jq -Rs 'split("\n") | map(select(length > 0))' "$output/errors.txt") + platforms=$(jq -Rn ' + [inputs | split("\t") | {platform: .[0], digest: .[1]}] + ' < "$output/platforms.tsv") + jq -n \ + --arg image_tag "$image_tag" \ + --arg pinned_image "$pinned_image" \ + --arg index_digest "$index_digest" \ + --arg current_digest "$current_digest" \ + --argjson image_updated "$image_updated" \ + --argjson platforms "$platforms" \ + --argjson total_vulnerabilities "$total_vulnerabilities" \ + --argjson critical_vulnerabilities "$critical_vulnerabilities" \ + --argjson fixable_vulnerabilities "$fixable_vulnerabilities" \ + --argjson license_rejected "$license_rejected" \ + --argjson operational_errors "$errors" \ + '{ + image_tag: $image_tag, + pinned_image: $pinned_image, + index_digest: $index_digest, + current_digest: $current_digest, + image_updated: $image_updated, + platforms: $platforms, + total_vulnerabilities: $total_vulnerabilities, + critical_vulnerabilities: $critical_vulnerabilities, + fixable_vulnerabilities: $fixable_vulnerabilities, + license_rejected: $license_rejected, + operational_errors: $operational_errors, + tools: { + syft: "1.49.0", + grype: "0.116.0", + grant: "0.6.8" + } + }' > "$output/summary.json" + - name: Upload image scan results + if: always() + uses: actions/upload-artifact@v7.0.1 + with: + name: squid-image-scan + path: ${{ runner.temp }}/squid-image-scan + if-no-files-found: error + retention-days: 14 +sandbox: + agent: + sudo: false +timeout-minutes: 20 +--- + +# Daily Squid Image Security Scan + +Review the deterministic Syft, Grype, and Grant results in +`/tmp/gh-aw/agent/image-scan/`. + +1. Read `summary.json` first. +2. If the image tag has not changed, no vulnerabilities were found, all licenses + are accepted, and there are no operational errors, call `noop`. +3. Otherwise, create one issue. Use the title + `Container findings for ` so repeated + findings for the same immutable image are deduplicated. +4. Include: + - the pinned image, current tag digest, and both platform child digests; + - scanner versions and Grype database status; + - every vulnerability from both `grype-linux-*.json` files, with platform, + severity, vulnerability ID, package, installed version, and fixed versions; + - every rejected or unknown license shown in the `grant-check-linux-*.txt` + files; + - image digest drift, operational errors, and actionable remediation. +5. Keep the report factual and compact. Never omit lower-severity + vulnerabilities. + +The configured `create-issue` safe output is the only allowed write operation. diff --git a/.grant.yaml b/.grant.yaml new file mode 100644 index 00000000000..323759da057 --- /dev/null +++ b/.grant.yaml @@ -0,0 +1,11 @@ +# License allowlist from CONTRIBUTING.md. +require-license: true +require-known-license: true + +allow: + - MIT + - Apache-2.0 + - BSD-2-Clause + - BSD-3-Clause + - ISC + From 12f0ac459da7750aba05847f8fd4fcd2e2e53afb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:36:02 +0000 Subject: [PATCH 2/5] Harden scan failure reporting Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../workflows/daily-squid-image-scan.lock.yml | 32 ++++++++--- .github/workflows/daily-squid-image-scan.md | 54 +++++++++++++++++-- 2 files changed, 75 insertions(+), 11 deletions(-) diff --git a/.github/workflows/daily-squid-image-scan.lock.yml b/.github/workflows/daily-squid-image-scan.lock.yml index 6189c20ce69..5213b869f4e 100644 --- a/.github/workflows/daily-squid-image-scan.lock.yml +++ b/.github/workflows/daily-squid-image-scan.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"af298f30c23b125b2900b9346d2010b0b5dcfee55fa40cd720eeb4a372886155","body_hash":"a785398107e35cec799aae4088095a642fc5bb6dae012a57eb34bc62d60bb224","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1d3f28bcc3ad58cd0195f64a67bc2bfcf56257169b8d033d57f85085bac37d31","body_hash":"c5f80d894feb4d382cc15e85ca40a284c28623d407d58bec8429732f95db2cbd","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"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":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.37","digest":"sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.37@sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.37","digest":"sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.37@sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.37","digest":"sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.37@sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"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.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} # 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 # @@ -371,7 +371,7 @@ jobs: needs: - activation - scan_image - if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' + if: (always() && needs.scan_image.result != 'skipped') && (needs.activation.outputs.daily_ai_credits_exceeded != 'true') runs-on: ubuntu-latest permissions: contents: read @@ -455,11 +455,18 @@ jobs: with: name: activation path: /tmp/gh-aw - - name: Download image scan results + - continue-on-error: true + id: download_scan + name: Download image scan results uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: squid-image-scan path: /tmp/gh-aw/agent/image-scan + - env: + DOWNLOAD_OUTCOME: ${{ steps.download_scan.outcome }} + if: always() + name: Ensure image scan summary exists + run: "results=\"/tmp/gh-aw/agent/image-scan\"\nmkdir -p \"$results\"\nif [ \"$DOWNLOAD_OUTCOME\" != \"success\" ] ||\n ! jq -e 'type == \"object\"' \"$results/summary.json\" > /dev/null 2>&1; then\n jq -n '{\n image_tag: \"unknown\",\n pinned_image: \"unknown\",\n index_digest: \"unknown\",\n current_digest: \"unknown\",\n image_updated: false,\n platforms: [],\n total_vulnerabilities: 0,\n critical_vulnerabilities: 0,\n fixable_vulnerabilities: 0,\n license_rejected: false,\n operational_errors: [\"The scan job did not publish a valid result artifact.\"],\n tools: {\n syft: \"1.49.0\",\n grype: \"0.116.0\",\n grant: \"0.6.8\"\n }\n }' > \"$results/summary.json\"\nfi" - name: Configure Git credentials env: @@ -1849,10 +1856,23 @@ jobs: if ! grant list "$spdx_json" > "$output/grant-list-${suffix}.txt" 2>&1; then record_error "Grant could not list licenses for ${platform}." fi - grant check --config .grant.yaml "$spdx_json" \ - > "$output/grant-check-${suffix}.txt" 2>&1 - if [ "$?" -ne 0 ]; then + grant_json="$output/grant-check-${suffix}.json" + grant_stderr="$output/grant-check-${suffix}.stderr" + grant check --config .grant.yaml --output json "$spdx_json" \ + > "$grant_json" 2> "$grant_stderr" + grant_exit=$? + if ! jq -e '.run.targets | length > 0' "$grant_json" > /dev/null 2>&1; then + record_error "Grant did not produce valid results for ${platform}." + elif jq -e ' + any(.run.targets[]; .evaluation.status == "error") + ' "$grant_json" > /dev/null; then + record_error "Grant encountered an evaluation error for ${platform}." + elif jq -e ' + any(.run.targets[]; .evaluation.status == "noncompliant") + ' "$grant_json" > /dev/null; then license_rejected=true + elif [ "$grant_exit" -ne 0 ]; then + record_error "Grant failed unexpectedly for ${platform}." fi done diff --git a/.github/workflows/daily-squid-image-scan.md b/.github/workflows/daily-squid-image-scan.md index 75448a9fa3e..4799f6ebec5 100644 --- a/.github/workflows/daily-squid-image-scan.md +++ b/.github/workflows/daily-squid-image-scan.md @@ -10,6 +10,7 @@ permissions: issues: read copilot-requests: write strict: true +if: always() && needs.scan_image.result != 'skipped' network: allowed: - defaults @@ -26,10 +27,40 @@ safe-outputs: deduplicate-by-title: true steps: - name: Download image scan results + id: download_scan + continue-on-error: true uses: actions/download-artifact@v8.0.1 with: name: squid-image-scan path: /tmp/gh-aw/agent/image-scan + - name: Ensure image scan summary exists + if: always() + env: + DOWNLOAD_OUTCOME: ${{ steps.download_scan.outcome }} + run: | + results="/tmp/gh-aw/agent/image-scan" + mkdir -p "$results" + if [ "$DOWNLOAD_OUTCOME" != "success" ] || + ! jq -e 'type == "object"' "$results/summary.json" > /dev/null 2>&1; then + jq -n '{ + image_tag: "unknown", + pinned_image: "unknown", + index_digest: "unknown", + current_digest: "unknown", + image_updated: false, + platforms: [], + total_vulnerabilities: 0, + critical_vulnerabilities: 0, + fixable_vulnerabilities: 0, + license_rejected: false, + operational_errors: ["The scan job did not publish a valid result artifact."], + tools: { + syft: "1.49.0", + grype: "0.116.0", + grant: "0.6.8" + } + }' > "$results/summary.json" + fi post-steps: - name: Enforce critical vulnerability and license gates if: always() @@ -234,10 +265,23 @@ jobs: if ! grant list "$spdx_json" > "$output/grant-list-${suffix}.txt" 2>&1; then record_error "Grant could not list licenses for ${platform}." fi - grant check --config .grant.yaml "$spdx_json" \ - > "$output/grant-check-${suffix}.txt" 2>&1 - if [ "$?" -ne 0 ]; then + grant_json="$output/grant-check-${suffix}.json" + grant_stderr="$output/grant-check-${suffix}.stderr" + grant check --config .grant.yaml --output json "$spdx_json" \ + > "$grant_json" 2> "$grant_stderr" + grant_exit=$? + if ! jq -e '.run.targets | length > 0' "$grant_json" > /dev/null 2>&1; then + record_error "Grant did not produce valid results for ${platform}." + elif jq -e ' + any(.run.targets[]; .evaluation.status == "error") + ' "$grant_json" > /dev/null; then + record_error "Grant encountered an evaluation error for ${platform}." + elif jq -e ' + any(.run.targets[]; .evaluation.status == "noncompliant") + ' "$grant_json" > /dev/null; then license_rejected=true + elif [ "$grant_exit" -ne 0 ]; then + record_error "Grant failed unexpectedly for ${platform}." fi done @@ -305,8 +349,8 @@ Review the deterministic Syft, Grype, and Grant results in - scanner versions and Grype database status; - every vulnerability from both `grype-linux-*.json` files, with platform, severity, vulnerability ID, package, installed version, and fixed versions; - - every rejected or unknown license shown in the `grant-check-linux-*.txt` - files; + - every rejected or unknown license shown in the + `grant-check-linux-*.json` files; - image digest drift, operational errors, and actionable remediation. 5. Keep the report factual and compact. Never omit lower-severity vulnerabilities. From ed5ff9917b5bc194f0ed7812513115b1e097146e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:14:29 +0000 Subject: [PATCH 3/5] Scan all compiled workflow container images Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../workflows/daily-squid-image-scan.lock.yml | 411 ++++++++++-------- .github/workflows/daily-squid-image-scan.md | 382 +++++++++------- 2 files changed, 455 insertions(+), 338 deletions(-) diff --git a/.github/workflows/daily-squid-image-scan.lock.yml b/.github/workflows/daily-squid-image-scan.lock.yml index 5213b869f4e..79d19cca9b0 100644 --- a/.github/workflows/daily-squid-image-scan.lock.yml +++ b/.github/workflows/daily-squid-image-scan.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1d3f28bcc3ad58cd0195f64a67bc2bfcf56257169b8d033d57f85085bac37d31","body_hash":"c5f80d894feb4d382cc15e85ca40a284c28623d407d58bec8429732f95db2cbd","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"6387a21ad6c74389e8295dfa770a140aa17e4682e7a47b39e1f3804c5464ba2e","body_hash":"7a1f8b7d3ba999bc2945954c493e41a43a33b260a3374e9b0d67958617da65db","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"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":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.37","digest":"sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.37@sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.37","digest":"sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.37@sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.37","digest":"sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.37@sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"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.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} # 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 # @@ -23,7 +23,7 @@ # # For more information: https://github.github.com/gh-aw/introduction/overview/ # -# Scan the pinned agentic workflow firewall image for vulnerabilities, updates, and rejected licenses +# Scan container images used by compiled workflows for vulnerabilities, updates, and rejected licenses # # Secrets used: # - COPILOT_GITHUB_TOKEN @@ -49,7 +49,7 @@ # - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b # - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 -name: "Daily Squid Image Security Scan" +name: "Daily Container Image Security Scan" on: schedule: - cron: "26 5 * * *" # Friendly format: daily (scattered) @@ -66,7 +66,7 @@ permissions: {} concurrency: group: "gh-aw-${{ github.workflow }}" -run-name: "Daily Squid Image Security Scan" +run-name: "Daily Container Image Security Scan" jobs: activation: @@ -108,7 +108,7 @@ jobs: job-name: ${{ github.job }} safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} env: - GH_AW_SETUP_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_SETUP_WORKFLOW_NAME: "Daily Container Image Security Scan" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-squid-image-scan.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.73" GH_AW_INFO_AWF_VERSION: "v0.27.37" @@ -121,7 +121,7 @@ jobs: 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.73" GH_AW_INFO_AGENT_VERSION: "1.0.73" - GH_AW_INFO_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_INFO_WORKFLOW_NAME: "Daily Container Image Security Scan" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" @@ -168,7 +168,7 @@ jobs: if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_WORKFLOW_NAME: "Daily Container Image Security Scan" GH_AW_WORKFLOW_ID: "daily-squid-image-scan" 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 || '' }} @@ -243,20 +243,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_904e367dd70b6dbb_EOF' + cat << 'GH_AW_PROMPT_cf657744ac3b9d2b_EOF' - GH_AW_PROMPT_904e367dd70b6dbb_EOF + GH_AW_PROMPT_cf657744ac3b9d2b_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_904e367dd70b6dbb_EOF' + cat << 'GH_AW_PROMPT_cf657744ac3b9d2b_EOF' - Tools: create_issue, missing_tool, missing_data, noop + Tools: create_issue(max:25), missing_tool, missing_data, noop - GH_AW_PROMPT_904e367dd70b6dbb_EOF + GH_AW_PROMPT_cf657744ac3b9d2b_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_904e367dd70b6dbb_EOF' + cat << 'GH_AW_PROMPT_cf657744ac3b9d2b_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -285,12 +285,12 @@ jobs: {{/if}} - GH_AW_PROMPT_904e367dd70b6dbb_EOF + GH_AW_PROMPT_cf657744ac3b9d2b_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_904e367dd70b6dbb_EOF' + cat << 'GH_AW_PROMPT_cf657744ac3b9d2b_EOF' {{#runtime-import .github/workflows/daily-squid-image-scan.md}} - GH_AW_PROMPT_904e367dd70b6dbb_EOF + GH_AW_PROMPT_cf657744ac3b9d2b_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -427,7 +427,7 @@ jobs: trace-id: ${{ needs.activation.outputs.setup-trace-id }} parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: - GH_AW_SETUP_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_SETUP_WORKFLOW_NAME: "Daily Container Image Security Scan" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-squid-image-scan.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.73" GH_AW_INFO_AWF_VERSION: "v0.27.37" @@ -460,13 +460,13 @@ jobs: name: Download image scan results uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: squid-image-scan + name: container-image-scan path: /tmp/gh-aw/agent/image-scan - env: DOWNLOAD_OUTCOME: ${{ steps.download_scan.outcome }} if: always() name: Ensure image scan summary exists - run: "results=\"/tmp/gh-aw/agent/image-scan\"\nmkdir -p \"$results\"\nif [ \"$DOWNLOAD_OUTCOME\" != \"success\" ] ||\n ! jq -e 'type == \"object\"' \"$results/summary.json\" > /dev/null 2>&1; then\n jq -n '{\n image_tag: \"unknown\",\n pinned_image: \"unknown\",\n index_digest: \"unknown\",\n current_digest: \"unknown\",\n image_updated: false,\n platforms: [],\n total_vulnerabilities: 0,\n critical_vulnerabilities: 0,\n fixable_vulnerabilities: 0,\n license_rejected: false,\n operational_errors: [\"The scan job did not publish a valid result artifact.\"],\n tools: {\n syft: \"1.49.0\",\n grype: \"0.116.0\",\n grant: \"0.6.8\"\n }\n }' > \"$results/summary.json\"\nfi" + run: "results=\"/tmp/gh-aw/agent/image-scan\"\nmkdir -p \"$results\"\nif [ \"$DOWNLOAD_OUTCOME\" != \"success\" ] ||\n ! jq -e 'type == \"object\"' \"$results/summary.json\" > /dev/null 2>&1; then\n jq -n '{\n images: [],\n total_vulnerabilities: 0,\n critical_vulnerabilities: 0,\n fixable_vulnerabilities: 0,\n license_rejected: false,\n operational_errors: [\"The scan job did not publish a valid result artifact.\"],\n tools: {\n syft: \"1.49.0\",\n grype: \"0.116.0\",\n grant: \"0.6.8\"\n }\n }' > \"$results/summary.json\"\nfi" - name: Configure Git credentials env: @@ -526,15 +526,15 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_2907ddee8e809b31_EOF' - {"create_issue":{"deduplicate_by_title":true,"labels":["cookie","security"],"max":1,"title_prefix":"[squid-image-scan] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_2907ddee8e809b31_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_c6f758a872410540_EOF' + {"create_issue":{"deduplicate_by_title":true,"labels":["cookie","security"],"max":25,"title_prefix":"[container-image-scan] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_c6f758a872410540_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { "description_suffixes": { - "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[squid-image-scan] \". Labels [\"cookie\" \"security\"] will be automatically added." + "create_issue": " CONSTRAINTS: Maximum 25 issue(s) can be created. Title will be prefixed with \"[container-image-scan] \". Labels [\"cookie\" \"security\"] will be automatically added." }, "repo_params": {}, "dynamic_tools": [] @@ -1064,7 +1064,7 @@ jobs: trace-id: ${{ needs.activation.outputs.setup-trace-id }} parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: - GH_AW_SETUP_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_SETUP_WORKFLOW_NAME: "Daily Container Image Security Scan" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-squid-image-scan.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.73" GH_AW_INFO_AWF_VERSION: "v0.27.37" @@ -1181,7 +1181,7 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" - GH_AW_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_WORKFLOW_NAME: "Daily Container Image Security Scan" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/daily-squid-image-scan.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} @@ -1202,7 +1202,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_WORKFLOW_NAME: "Daily Container Image Security Scan" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/daily-squid-image-scan.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} @@ -1220,7 +1220,7 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" - GH_AW_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_WORKFLOW_NAME: "Daily Container Image Security Scan" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/daily-squid-image-scan.md" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1235,7 +1235,7 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" - GH_AW_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_WORKFLOW_NAME: "Daily Container Image Security Scan" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/daily-squid-image-scan.md" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1250,7 +1250,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_WORKFLOW_NAME: "Daily Container Image Security Scan" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/daily-squid-image-scan.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} @@ -1323,7 +1323,7 @@ jobs: trace-id: ${{ needs.activation.outputs.setup-trace-id }} parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: - GH_AW_SETUP_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_SETUP_WORKFLOW_NAME: "Daily Container Image Security Scan" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-squid-image-scan.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.73" GH_AW_INFO_AWF_VERSION: "v0.27.37" @@ -1396,8 +1396,8 @@ jobs: if: always() && steps.detection_guard.outputs.run_detection == 'true' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - WORKFLOW_NAME: "Daily Squid Image Security Scan" - WORKFLOW_DESCRIPTION: "Scan the pinned agentic workflow firewall image for vulnerabilities, updates, and rejected licenses" + WORKFLOW_NAME: "Daily Container Image Security Scan" + WORKFLOW_DESCRIPTION: "Scan container images used by compiled workflows for vulnerabilities, updates, and rejected licenses" HAS_PATCH: ${{ needs.agent.outputs.has_patch }} with: script: | @@ -1570,7 +1570,7 @@ jobs: GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_EMOJI: "🛡️" GH_AW_WORKFLOW_ID: "daily-squid-image-scan" - GH_AW_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_WORKFLOW_NAME: "Daily Container Image Security Scan" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/daily-squid-image-scan.md" outputs: code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} @@ -1599,7 +1599,7 @@ jobs: trace-id: ${{ needs.activation.outputs.setup-trace-id }} parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: - GH_AW_SETUP_WORKFLOW_NAME: "Daily Squid Image Security Scan" + GH_AW_SETUP_WORKFLOW_NAME: "Daily Container Image Security Scan" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-squid-image-scan.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.73" GH_AW_INFO_AWF_VERSION: "v0.27.37" @@ -1636,7 +1636,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "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" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"deduplicate_by_title\":true,\"labels\":[\"cookie\",\"security\"],\"max\":1,\"title_prefix\":\"[squid-image-scan] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"deduplicate_by_title\":true,\"labels\":[\"cookie\",\"security\"],\"max\":25,\"title_prefix\":\"[container-image-scan] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1655,14 +1655,14 @@ jobs: if-no-files-found: ignore scan_image: - name: Scan immutable firewall image + name: Scan immutable workflow images needs: activation runs-on: ubuntu-latest permissions: contents: read packages: read - timeout-minutes: 30 + timeout-minutes: 120 steps: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config @@ -1709,63 +1709,39 @@ jobs: SYFT_SHA256: 7aa2f03ee92739cf643279ba3990548b9925d4e22cae13f46831ee62821147fe SYFT_VERSION: 1.49.0 continue-on-error: true - - name: Scan each platform image + - name: Scan compiled workflow images if: always() run: | set -uo pipefail - output="$RUNNER_TEMP/squid-image-scan" + output="$RUNNER_TEMP/container-image-scan" mkdir -p "$output" : > "$output/errors.txt" - : > "$output/platforms.tsv" + : > "$output/image-results.jsonl" record_error() { printf '%s\n' "$1" | tee -a "$output/errors.txt" } - image_prefix="ghcr.io/github/gh-aw-firewall/squid:" - pin_file="pkg/actionpins/data/action_pins.json" - mirror_file="pkg/workflow/data/action_pins.json" - - pin_entry=$(jq -cer --arg prefix "$image_prefix" ' - [.entries | to_entries[] | select(.key | startswith($prefix))] - | if length == 1 then .[0] else error("expected exactly one squid image pin") end - ' "$pin_file") || record_error "Unable to read the canonical Squid image pin." - mirror_entry=$(jq -cer --arg prefix "$image_prefix" ' - [.entries | to_entries[] | select(.key | startswith($prefix))] - | if length == 1 then .[0] else error("expected exactly one squid image pin") end - ' "$mirror_file") || record_error "Unable to read the mirrored Squid image pin." - - if [ -z "${pin_entry:-}" ] || [ -z "${mirror_entry:-}" ]; then - image_tag="unknown" - pinned_image="unknown" - index_digest="unknown" - else - if [ "$pin_entry" != "$mirror_entry" ]; then - record_error "The canonical and mirrored Squid image pins disagree." - fi - image_tag=$(jq -r '.key' <<<"$pin_entry") - pinned_image=$(jq -r '.value.pinned_image' <<<"$pin_entry") - index_digest=$(jq -r '.value.digest' <<<"$pin_entry") - if [[ ! "$pinned_image" =~ @sha256:[0-9a-f]{64}$ ]] || - [[ ! "$index_digest" =~ ^sha256:[0-9a-f]{64}$ ]]; then - record_error "The Squid image is not pinned to a valid SHA-256 digest." - fi + if ! grep -h '^# gh-aw-manifest: ' .github/workflows/*.lock.yml | + sed 's/^# gh-aw-manifest: //' | + jq -s ' + [.[].containers[]?] + | unique_by(.pinned_image) + | sort_by(.image) + ' > "$output/images.json"; then + record_error "Unable to read container images from compiled workflow manifests." + echo '[]' > "$output/images.json" fi - current_digest="unknown" - image_updated=false - if [ "$image_tag" != "unknown" ]; then - if current_digest=$( - docker buildx imagetools inspect "$image_tag" --format '{{json .Manifest}}' | - jq -er '.digest' - ); then - if [ "$current_digest" != "$index_digest" ]; then - image_updated=true - fi - else - current_digest="unknown" - record_error "Unable to resolve the current Squid image tag digest." - fi + if ! jq -e ' + length > 0 and + all(.[]; + (.image | type == "string" and length > 0) and + (.digest | test("^sha256:[0-9a-f]{64}$")) and + (.pinned_image | test("@sha256:[0-9a-f]{64}$")) + ) + ' "$output/images.json" > /dev/null; then + record_error "Compiled workflow manifests contain no images or invalid image pins." fi tools_ready=true @@ -1779,19 +1755,6 @@ jobs: fixable_vulnerabilities=0 license_rejected=false - if [ "$pinned_image" != "unknown" ]; then - if docker buildx imagetools inspect "$pinned_image" --raw > "$output/index.json"; then - if ! echo "${index_digest#sha256:} $output/index.json" | sha256sum -c -; then - record_error "The downloaded image index does not match its pinned digest." - fi - else - record_error "Unable to download the pinned image index." - echo '{"manifests":[]}' > "$output/index.json" - fi - else - echo '{"manifests":[]}' > "$output/index.json" - fi - if [ "$tools_ready" = true ]; then if ! grype db update > "$output/grype-db-update.txt" 2>&1; then record_error "Grype vulnerability database update failed." @@ -1799,106 +1762,202 @@ jobs: grype db status > "$output/grype-db-status.txt" 2>&1 || true fi - for platform in linux/amd64 linux/arm64; do - arch="${platform#linux/}" - suffix="linux-${arch}" - child_digest=$(jq -er --arg arch "$arch" ' - [.manifests[] | select(.platform.os == "linux" and .platform.architecture == $arch)] - | if length == 1 then .[0].digest else error("expected exactly one platform manifest") end - ' "$output/index.json") || { - record_error "Unable to resolve exactly one ${platform} image manifest." - continue + image_number=0 + while IFS= read -r image_entry; do + image_number=$((image_number + 1)) + artifact_prefix=$(printf 'image-%02d' "$image_number") + image_tag=$(jq -r '.image' <<<"$image_entry") + pinned_image=$(jq -r '.pinned_image' <<<"$image_entry") + index_digest=$(jq -r '.digest' <<<"$image_entry") + image_errors="$output/${artifact_prefix}-errors.txt" + image_platforms="$output/${artifact_prefix}-platforms.tsv" + : > "$image_errors" + : > "$image_platforms" + + record_image_error() { + printf '%s\n' "$1" | tee -a "$output/errors.txt" "$image_errors" } - printf '%s\t%s\n' "$platform" "$child_digest" >> "$output/platforms.tsv" - if [ "$tools_ready" != true ]; then - continue + current_digest="unknown" + image_updated=false + if current_digest=$( + docker buildx imagetools inspect "$image_tag" --format '{{json .Manifest}}' | + jq -er '.digest' + ); then + if [ "$current_digest" != "$index_digest" ]; then + image_updated=true + fi + else + current_digest="unknown" + record_image_error "Unable to resolve the current digest for ${image_tag}." fi - image_repository="${image_tag%:*}" - immutable_image="${image_repository}@${child_digest}" - syft_json="$output/sbom-${suffix}.syft.json" - spdx_json="$output/sbom-${suffix}.spdx.json" - - if ! syft --platform "$platform" "$immutable_image" \ - -o "syft-json=${syft_json}" \ - -o "spdx-json=${spdx_json}" > "$output/syft-${suffix}.txt" 2>&1; then - record_error "Syft failed to scan ${platform} at ${child_digest}." - continue + image_manifest="$output/${artifact_prefix}-manifest.json" + if docker buildx imagetools inspect "$pinned_image" --raw > "$image_manifest"; then + if ! echo "${index_digest#sha256:} $image_manifest" | sha256sum -c -; then + record_image_error "The manifest for ${pinned_image} does not match its pinned digest." + fi + else + record_image_error "Unable to download the manifest for ${pinned_image}." + echo '{}' > "$image_manifest" fi - if ! GRYPE_DB_AUTO_UPDATE=false grype "sbom:${syft_json}" \ - -o json > "$output/grype-${suffix}.json" 2> "$output/grype-${suffix}.stderr"; then - record_error "Grype failed to produce JSON results for ${platform}." - continue + jq -r ' + .manifests[]? + | select(.platform.os == "linux") + | [ + .platform.os + "/" + .platform.architecture + + (if .platform.variant then "/" + .platform.variant else "" end), + .digest + ] + | @tsv + ' "$image_manifest" > "$image_platforms" + if [ ! -s "$image_platforms" ]; then + if jq -e 'has("manifests")' "$image_manifest" > /dev/null; then + record_image_error "The image index for ${pinned_image} has no Linux manifests." + elif platform=$( + docker buildx imagetools inspect "$pinned_image" --format '{{json .Image}}' | + jq -er ' + select(.os == "linux") + | .os + "/" + .architecture + + (if .variant then "/" + .variant else "" end) + ' + ); then + printf '%s\t%s\n' "$platform" "$index_digest" > "$image_platforms" + else + record_image_error "Unable to resolve a Linux platform for ${pinned_image}." + fi fi - platform_total=$(jq '.matches | length' "$output/grype-${suffix}.json") - platform_critical=$(jq ' - [.matches[] | select((.vulnerability.severity // "") | ascii_downcase == "critical")] - | length - ' "$output/grype-${suffix}.json") - platform_fixable=$(jq ' - [.matches[] | select((.vulnerability.fix.versions // []) | length > 0)] - | length - ' "$output/grype-${suffix}.json") - total_vulnerabilities=$((total_vulnerabilities + platform_total)) - critical_vulnerabilities=$((critical_vulnerabilities + platform_critical)) - fixable_vulnerabilities=$((fixable_vulnerabilities + platform_fixable)) + image_total=0 + image_critical=0 + image_fixable=0 + image_license_rejected=false - GRYPE_DB_AUTO_UPDATE=false grype "sbom:${syft_json}" \ - --fail-on critical -o table > "$output/grype-${suffix}.txt" 2>&1 - grype_exit=$? - if [ "$grype_exit" -ne 0 ] && [ "$platform_critical" -eq 0 ]; then - record_error "Grype table scan failed unexpectedly for ${platform}." - fi + while IFS=$'\t' read -r platform child_digest; do + suffix="${artifact_prefix}-$(tr '/_' '--' <<<"$platform" | tr -cd '[:alnum:].-')" + immutable_image="${pinned_image%@*}@${child_digest}" - if ! grant list "$spdx_json" > "$output/grant-list-${suffix}.txt" 2>&1; then - record_error "Grant could not list licenses for ${platform}." - fi - grant_json="$output/grant-check-${suffix}.json" - grant_stderr="$output/grant-check-${suffix}.stderr" - grant check --config .grant.yaml --output json "$spdx_json" \ - > "$grant_json" 2> "$grant_stderr" - grant_exit=$? - if ! jq -e '.run.targets | length > 0' "$grant_json" > /dev/null 2>&1; then - record_error "Grant did not produce valid results for ${platform}." - elif jq -e ' - any(.run.targets[]; .evaluation.status == "error") - ' "$grant_json" > /dev/null; then - record_error "Grant encountered an evaluation error for ${platform}." - elif jq -e ' - any(.run.targets[]; .evaluation.status == "noncompliant") - ' "$grant_json" > /dev/null; then + if [ "$tools_ready" != true ]; then + continue + fi + + syft_json="$output/sbom-${suffix}.syft.json" + spdx_json="$output/sbom-${suffix}.spdx.json" + syft_args=() + if [ "$platform" != "default" ]; then + syft_args=(--platform "$platform") + fi + + if ! syft "${syft_args[@]}" "$immutable_image" \ + -o "syft-json=${syft_json}" \ + -o "spdx-json=${spdx_json}" > "$output/syft-${suffix}.txt" 2>&1; then + record_image_error "Syft failed to scan ${image_tag} for ${platform} at ${child_digest}." + continue + fi + + if ! GRYPE_DB_AUTO_UPDATE=false grype "sbom:${syft_json}" \ + -o json > "$output/grype-${suffix}.json" 2> "$output/grype-${suffix}.stderr"; then + record_image_error "Grype failed to produce JSON results for ${image_tag} on ${platform}." + continue + fi + + platform_total=$(jq '.matches | length' "$output/grype-${suffix}.json") + platform_critical=$(jq ' + [.matches[] | select((.vulnerability.severity // "") | ascii_downcase == "critical")] + | length + ' "$output/grype-${suffix}.json") + platform_fixable=$(jq ' + [.matches[] | select((.vulnerability.fix.versions // []) | length > 0)] + | length + ' "$output/grype-${suffix}.json") + image_total=$((image_total + platform_total)) + image_critical=$((image_critical + platform_critical)) + image_fixable=$((image_fixable + platform_fixable)) + + GRYPE_DB_AUTO_UPDATE=false grype "sbom:${syft_json}" \ + --fail-on critical -o table > "$output/grype-${suffix}.txt" 2>&1 + grype_exit=$? + if [ "$grype_exit" -ne 0 ] && [ "$platform_critical" -eq 0 ]; then + record_image_error "Grype table scan failed unexpectedly for ${image_tag} on ${platform}." + fi + + if ! grant list "$spdx_json" > "$output/grant-list-${suffix}.txt" 2>&1; then + record_image_error "Grant could not list licenses for ${image_tag} on ${platform}." + fi + grant_json="$output/grant-check-${suffix}.json" + grant_stderr="$output/grant-check-${suffix}.stderr" + grant check --config .grant.yaml --output json "$spdx_json" \ + > "$grant_json" 2> "$grant_stderr" + grant_exit=$? + if ! jq -e '.run.targets | length > 0' "$grant_json" > /dev/null 2>&1; then + record_image_error "Grant did not produce valid results for ${image_tag} on ${platform}." + elif jq -e ' + any(.run.targets[]; .evaluation.status == "error") + ' "$grant_json" > /dev/null; then + record_image_error "Grant encountered an evaluation error for ${image_tag} on ${platform}." + elif jq -e ' + any(.run.targets[]; .evaluation.status == "noncompliant") + ' "$grant_json" > /dev/null; then + image_license_rejected=true + elif [ "$grant_exit" -ne 0 ]; then + record_image_error "Grant failed unexpectedly for ${image_tag} on ${platform}." + fi + done < "$image_platforms" + + total_vulnerabilities=$((total_vulnerabilities + image_total)) + critical_vulnerabilities=$((critical_vulnerabilities + image_critical)) + fixable_vulnerabilities=$((fixable_vulnerabilities + image_fixable)) + if [ "$image_license_rejected" = true ]; then license_rejected=true - elif [ "$grant_exit" -ne 0 ]; then - record_error "Grant failed unexpectedly for ${platform}." fi - done + + platforms=$(jq -Rn ' + [inputs | split("\t") | {platform: .[0], digest: .[1]}] + ' < "$image_platforms") + image_operational_errors=$(jq -Rs ' + split("\n") | map(select(length > 0)) + ' "$image_errors") + jq -n \ + --arg artifact_prefix "$artifact_prefix" \ + --arg image_tag "$image_tag" \ + --arg pinned_image "$pinned_image" \ + --arg index_digest "$index_digest" \ + --arg current_digest "$current_digest" \ + --argjson image_updated "$image_updated" \ + --argjson platforms "$platforms" \ + --argjson total_vulnerabilities "$image_total" \ + --argjson critical_vulnerabilities "$image_critical" \ + --argjson fixable_vulnerabilities "$image_fixable" \ + --argjson license_rejected "$image_license_rejected" \ + --argjson operational_errors "$image_operational_errors" \ + '{ + artifact_prefix: $artifact_prefix, + image_tag: $image_tag, + pinned_image: $pinned_image, + index_digest: $index_digest, + current_digest: $current_digest, + image_updated: $image_updated, + platforms: $platforms, + total_vulnerabilities: $total_vulnerabilities, + critical_vulnerabilities: $critical_vulnerabilities, + fixable_vulnerabilities: $fixable_vulnerabilities, + license_rejected: $license_rejected, + operational_errors: $operational_errors + }' >> "$output/image-results.jsonl" + done < <(jq -c '.[]' "$output/images.json") errors=$(jq -Rs 'split("\n") | map(select(length > 0))' "$output/errors.txt") - platforms=$(jq -Rn ' - [inputs | split("\t") | {platform: .[0], digest: .[1]}] - ' < "$output/platforms.tsv") + images=$(jq -s '.' "$output/image-results.jsonl") jq -n \ - --arg image_tag "$image_tag" \ - --arg pinned_image "$pinned_image" \ - --arg index_digest "$index_digest" \ - --arg current_digest "$current_digest" \ - --argjson image_updated "$image_updated" \ - --argjson platforms "$platforms" \ + --argjson images "$images" \ --argjson total_vulnerabilities "$total_vulnerabilities" \ --argjson critical_vulnerabilities "$critical_vulnerabilities" \ --argjson fixable_vulnerabilities "$fixable_vulnerabilities" \ --argjson license_rejected "$license_rejected" \ --argjson operational_errors "$errors" \ '{ - image_tag: $image_tag, - pinned_image: $pinned_image, - index_digest: $index_digest, - current_digest: $current_digest, - image_updated: $image_updated, - platforms: $platforms, + images: $images, total_vulnerabilities: $total_vulnerabilities, critical_vulnerabilities: $critical_vulnerabilities, fixable_vulnerabilities: $fixable_vulnerabilities, @@ -1917,6 +1976,6 @@ jobs: uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: if-no-files-found: error - name: squid-image-scan - path: ${{ runner.temp }}/squid-image-scan + name: container-image-scan + path: ${{ runner.temp }}/container-image-scan retention-days: 14 diff --git a/.github/workflows/daily-squid-image-scan.md b/.github/workflows/daily-squid-image-scan.md index 4799f6ebec5..02567702b70 100644 --- a/.github/workflows/daily-squid-image-scan.md +++ b/.github/workflows/daily-squid-image-scan.md @@ -1,6 +1,6 @@ --- -name: Daily Squid Image Security Scan -description: Scan the pinned agentic workflow firewall image for vulnerabilities, updates, and rejected licenses +name: Daily Container Image Security Scan +description: Scan container images used by compiled workflows for vulnerabilities, updates, and rejected licenses emoji: "🛡️" on: schedule: daily @@ -21,9 +21,9 @@ tools: - "jq * /tmp/gh-aw/agent/image-scan/*" safe-outputs: create-issue: - title-prefix: "[squid-image-scan] " + title-prefix: "[container-image-scan] " labels: [cookie, security] - max: 1 + max: 25 deduplicate-by-title: true steps: - name: Download image scan results @@ -31,7 +31,7 @@ steps: continue-on-error: true uses: actions/download-artifact@v8.0.1 with: - name: squid-image-scan + name: container-image-scan path: /tmp/gh-aw/agent/image-scan - name: Ensure image scan summary exists if: always() @@ -43,12 +43,7 @@ steps: if [ "$DOWNLOAD_OUTCOME" != "success" ] || ! jq -e 'type == "object"' "$results/summary.json" > /dev/null 2>&1; then jq -n '{ - image_tag: "unknown", - pinned_image: "unknown", - index_digest: "unknown", - current_digest: "unknown", - image_updated: false, - platforms: [], + images: [], total_vulnerabilities: 0, critical_vulnerabilities: 0, fixable_vulnerabilities: 0, @@ -73,9 +68,9 @@ post-steps: ' "$summary" jobs: scan_image: - name: Scan immutable firewall image + name: Scan immutable workflow images runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 120 permissions: contents: read packages: read @@ -116,65 +111,41 @@ jobs: install_tool grype "$GRYPE_VERSION" "$GRYPE_SHA256" install_tool grant "$GRANT_VERSION" "$GRANT_SHA256" echo "$tools_dir" >> "$GITHUB_PATH" - - name: Scan each platform image + - name: Scan compiled workflow images if: always() env: INSTALL_OUTCOME: ${{ steps.install_tools.outcome }} run: | set -uo pipefail - output="$RUNNER_TEMP/squid-image-scan" + output="$RUNNER_TEMP/container-image-scan" mkdir -p "$output" : > "$output/errors.txt" - : > "$output/platforms.tsv" + : > "$output/image-results.jsonl" record_error() { printf '%s\n' "$1" | tee -a "$output/errors.txt" } - image_prefix="ghcr.io/github/gh-aw-firewall/squid:" - pin_file="pkg/actionpins/data/action_pins.json" - mirror_file="pkg/workflow/data/action_pins.json" - - pin_entry=$(jq -cer --arg prefix "$image_prefix" ' - [.entries | to_entries[] | select(.key | startswith($prefix))] - | if length == 1 then .[0] else error("expected exactly one squid image pin") end - ' "$pin_file") || record_error "Unable to read the canonical Squid image pin." - mirror_entry=$(jq -cer --arg prefix "$image_prefix" ' - [.entries | to_entries[] | select(.key | startswith($prefix))] - | if length == 1 then .[0] else error("expected exactly one squid image pin") end - ' "$mirror_file") || record_error "Unable to read the mirrored Squid image pin." - - if [ -z "${pin_entry:-}" ] || [ -z "${mirror_entry:-}" ]; then - image_tag="unknown" - pinned_image="unknown" - index_digest="unknown" - else - if [ "$pin_entry" != "$mirror_entry" ]; then - record_error "The canonical and mirrored Squid image pins disagree." - fi - image_tag=$(jq -r '.key' <<<"$pin_entry") - pinned_image=$(jq -r '.value.pinned_image' <<<"$pin_entry") - index_digest=$(jq -r '.value.digest' <<<"$pin_entry") - if [[ ! "$pinned_image" =~ @sha256:[0-9a-f]{64}$ ]] || - [[ ! "$index_digest" =~ ^sha256:[0-9a-f]{64}$ ]]; then - record_error "The Squid image is not pinned to a valid SHA-256 digest." - fi + if ! grep -h '^# gh-aw-manifest: ' .github/workflows/*.lock.yml | + sed 's/^# gh-aw-manifest: //' | + jq -s ' + [.[].containers[]?] + | unique_by(.pinned_image) + | sort_by(.image) + ' > "$output/images.json"; then + record_error "Unable to read container images from compiled workflow manifests." + echo '[]' > "$output/images.json" fi - current_digest="unknown" - image_updated=false - if [ "$image_tag" != "unknown" ]; then - if current_digest=$( - docker buildx imagetools inspect "$image_tag" --format '{{json .Manifest}}' | - jq -er '.digest' - ); then - if [ "$current_digest" != "$index_digest" ]; then - image_updated=true - fi - else - current_digest="unknown" - record_error "Unable to resolve the current Squid image tag digest." - fi + if ! jq -e ' + length > 0 and + all(.[]; + (.image | type == "string" and length > 0) and + (.digest | test("^sha256:[0-9a-f]{64}$")) and + (.pinned_image | test("@sha256:[0-9a-f]{64}$")) + ) + ' "$output/images.json" > /dev/null; then + record_error "Compiled workflow manifests contain no images or invalid image pins." fi tools_ready=true @@ -188,19 +159,6 @@ jobs: fixable_vulnerabilities=0 license_rejected=false - if [ "$pinned_image" != "unknown" ]; then - if docker buildx imagetools inspect "$pinned_image" --raw > "$output/index.json"; then - if ! echo "${index_digest#sha256:} $output/index.json" | sha256sum -c -; then - record_error "The downloaded image index does not match its pinned digest." - fi - else - record_error "Unable to download the pinned image index." - echo '{"manifests":[]}' > "$output/index.json" - fi - else - echo '{"manifests":[]}' > "$output/index.json" - fi - if [ "$tools_ready" = true ]; then if ! grype db update > "$output/grype-db-update.txt" 2>&1; then record_error "Grype vulnerability database update failed." @@ -208,106 +166,202 @@ jobs: grype db status > "$output/grype-db-status.txt" 2>&1 || true fi - for platform in linux/amd64 linux/arm64; do - arch="${platform#linux/}" - suffix="linux-${arch}" - child_digest=$(jq -er --arg arch "$arch" ' - [.manifests[] | select(.platform.os == "linux" and .platform.architecture == $arch)] - | if length == 1 then .[0].digest else error("expected exactly one platform manifest") end - ' "$output/index.json") || { - record_error "Unable to resolve exactly one ${platform} image manifest." - continue + image_number=0 + while IFS= read -r image_entry; do + image_number=$((image_number + 1)) + artifact_prefix=$(printf 'image-%02d' "$image_number") + image_tag=$(jq -r '.image' <<<"$image_entry") + pinned_image=$(jq -r '.pinned_image' <<<"$image_entry") + index_digest=$(jq -r '.digest' <<<"$image_entry") + image_errors="$output/${artifact_prefix}-errors.txt" + image_platforms="$output/${artifact_prefix}-platforms.tsv" + : > "$image_errors" + : > "$image_platforms" + + record_image_error() { + printf '%s\n' "$1" | tee -a "$output/errors.txt" "$image_errors" } - printf '%s\t%s\n' "$platform" "$child_digest" >> "$output/platforms.tsv" - if [ "$tools_ready" != true ]; then - continue + current_digest="unknown" + image_updated=false + if current_digest=$( + docker buildx imagetools inspect "$image_tag" --format '{{json .Manifest}}' | + jq -er '.digest' + ); then + if [ "$current_digest" != "$index_digest" ]; then + image_updated=true + fi + else + current_digest="unknown" + record_image_error "Unable to resolve the current digest for ${image_tag}." fi - image_repository="${image_tag%:*}" - immutable_image="${image_repository}@${child_digest}" - syft_json="$output/sbom-${suffix}.syft.json" - spdx_json="$output/sbom-${suffix}.spdx.json" - - if ! syft --platform "$platform" "$immutable_image" \ - -o "syft-json=${syft_json}" \ - -o "spdx-json=${spdx_json}" > "$output/syft-${suffix}.txt" 2>&1; then - record_error "Syft failed to scan ${platform} at ${child_digest}." - continue + image_manifest="$output/${artifact_prefix}-manifest.json" + if docker buildx imagetools inspect "$pinned_image" --raw > "$image_manifest"; then + if ! echo "${index_digest#sha256:} $image_manifest" | sha256sum -c -; then + record_image_error "The manifest for ${pinned_image} does not match its pinned digest." + fi + else + record_image_error "Unable to download the manifest for ${pinned_image}." + echo '{}' > "$image_manifest" fi - if ! GRYPE_DB_AUTO_UPDATE=false grype "sbom:${syft_json}" \ - -o json > "$output/grype-${suffix}.json" 2> "$output/grype-${suffix}.stderr"; then - record_error "Grype failed to produce JSON results for ${platform}." - continue + jq -r ' + .manifests[]? + | select(.platform.os == "linux") + | [ + .platform.os + "/" + .platform.architecture + + (if .platform.variant then "/" + .platform.variant else "" end), + .digest + ] + | @tsv + ' "$image_manifest" > "$image_platforms" + if [ ! -s "$image_platforms" ]; then + if jq -e 'has("manifests")' "$image_manifest" > /dev/null; then + record_image_error "The image index for ${pinned_image} has no Linux manifests." + elif platform=$( + docker buildx imagetools inspect "$pinned_image" --format '{{json .Image}}' | + jq -er ' + select(.os == "linux") + | .os + "/" + .architecture + + (if .variant then "/" + .variant else "" end) + ' + ); then + printf '%s\t%s\n' "$platform" "$index_digest" > "$image_platforms" + else + record_image_error "Unable to resolve a Linux platform for ${pinned_image}." + fi fi - platform_total=$(jq '.matches | length' "$output/grype-${suffix}.json") - platform_critical=$(jq ' - [.matches[] | select((.vulnerability.severity // "") | ascii_downcase == "critical")] - | length - ' "$output/grype-${suffix}.json") - platform_fixable=$(jq ' - [.matches[] | select((.vulnerability.fix.versions // []) | length > 0)] - | length - ' "$output/grype-${suffix}.json") - total_vulnerabilities=$((total_vulnerabilities + platform_total)) - critical_vulnerabilities=$((critical_vulnerabilities + platform_critical)) - fixable_vulnerabilities=$((fixable_vulnerabilities + platform_fixable)) + image_total=0 + image_critical=0 + image_fixable=0 + image_license_rejected=false - GRYPE_DB_AUTO_UPDATE=false grype "sbom:${syft_json}" \ - --fail-on critical -o table > "$output/grype-${suffix}.txt" 2>&1 - grype_exit=$? - if [ "$grype_exit" -ne 0 ] && [ "$platform_critical" -eq 0 ]; then - record_error "Grype table scan failed unexpectedly for ${platform}." - fi + while IFS=$'\t' read -r platform child_digest; do + suffix="${artifact_prefix}-$(tr '/_' '--' <<<"$platform" | tr -cd '[:alnum:].-')" + immutable_image="${pinned_image%@*}@${child_digest}" - if ! grant list "$spdx_json" > "$output/grant-list-${suffix}.txt" 2>&1; then - record_error "Grant could not list licenses for ${platform}." - fi - grant_json="$output/grant-check-${suffix}.json" - grant_stderr="$output/grant-check-${suffix}.stderr" - grant check --config .grant.yaml --output json "$spdx_json" \ - > "$grant_json" 2> "$grant_stderr" - grant_exit=$? - if ! jq -e '.run.targets | length > 0' "$grant_json" > /dev/null 2>&1; then - record_error "Grant did not produce valid results for ${platform}." - elif jq -e ' - any(.run.targets[]; .evaluation.status == "error") - ' "$grant_json" > /dev/null; then - record_error "Grant encountered an evaluation error for ${platform}." - elif jq -e ' - any(.run.targets[]; .evaluation.status == "noncompliant") - ' "$grant_json" > /dev/null; then + if [ "$tools_ready" != true ]; then + continue + fi + + syft_json="$output/sbom-${suffix}.syft.json" + spdx_json="$output/sbom-${suffix}.spdx.json" + syft_args=() + if [ "$platform" != "default" ]; then + syft_args=(--platform "$platform") + fi + + if ! syft "${syft_args[@]}" "$immutable_image" \ + -o "syft-json=${syft_json}" \ + -o "spdx-json=${spdx_json}" > "$output/syft-${suffix}.txt" 2>&1; then + record_image_error "Syft failed to scan ${image_tag} for ${platform} at ${child_digest}." + continue + fi + + if ! GRYPE_DB_AUTO_UPDATE=false grype "sbom:${syft_json}" \ + -o json > "$output/grype-${suffix}.json" 2> "$output/grype-${suffix}.stderr"; then + record_image_error "Grype failed to produce JSON results for ${image_tag} on ${platform}." + continue + fi + + platform_total=$(jq '.matches | length' "$output/grype-${suffix}.json") + platform_critical=$(jq ' + [.matches[] | select((.vulnerability.severity // "") | ascii_downcase == "critical")] + | length + ' "$output/grype-${suffix}.json") + platform_fixable=$(jq ' + [.matches[] | select((.vulnerability.fix.versions // []) | length > 0)] + | length + ' "$output/grype-${suffix}.json") + image_total=$((image_total + platform_total)) + image_critical=$((image_critical + platform_critical)) + image_fixable=$((image_fixable + platform_fixable)) + + GRYPE_DB_AUTO_UPDATE=false grype "sbom:${syft_json}" \ + --fail-on critical -o table > "$output/grype-${suffix}.txt" 2>&1 + grype_exit=$? + if [ "$grype_exit" -ne 0 ] && [ "$platform_critical" -eq 0 ]; then + record_image_error "Grype table scan failed unexpectedly for ${image_tag} on ${platform}." + fi + + if ! grant list "$spdx_json" > "$output/grant-list-${suffix}.txt" 2>&1; then + record_image_error "Grant could not list licenses for ${image_tag} on ${platform}." + fi + grant_json="$output/grant-check-${suffix}.json" + grant_stderr="$output/grant-check-${suffix}.stderr" + grant check --config .grant.yaml --output json "$spdx_json" \ + > "$grant_json" 2> "$grant_stderr" + grant_exit=$? + if ! jq -e '.run.targets | length > 0' "$grant_json" > /dev/null 2>&1; then + record_image_error "Grant did not produce valid results for ${image_tag} on ${platform}." + elif jq -e ' + any(.run.targets[]; .evaluation.status == "error") + ' "$grant_json" > /dev/null; then + record_image_error "Grant encountered an evaluation error for ${image_tag} on ${platform}." + elif jq -e ' + any(.run.targets[]; .evaluation.status == "noncompliant") + ' "$grant_json" > /dev/null; then + image_license_rejected=true + elif [ "$grant_exit" -ne 0 ]; then + record_image_error "Grant failed unexpectedly for ${image_tag} on ${platform}." + fi + done < "$image_platforms" + + total_vulnerabilities=$((total_vulnerabilities + image_total)) + critical_vulnerabilities=$((critical_vulnerabilities + image_critical)) + fixable_vulnerabilities=$((fixable_vulnerabilities + image_fixable)) + if [ "$image_license_rejected" = true ]; then license_rejected=true - elif [ "$grant_exit" -ne 0 ]; then - record_error "Grant failed unexpectedly for ${platform}." fi - done + + platforms=$(jq -Rn ' + [inputs | split("\t") | {platform: .[0], digest: .[1]}] + ' < "$image_platforms") + image_operational_errors=$(jq -Rs ' + split("\n") | map(select(length > 0)) + ' "$image_errors") + jq -n \ + --arg artifact_prefix "$artifact_prefix" \ + --arg image_tag "$image_tag" \ + --arg pinned_image "$pinned_image" \ + --arg index_digest "$index_digest" \ + --arg current_digest "$current_digest" \ + --argjson image_updated "$image_updated" \ + --argjson platforms "$platforms" \ + --argjson total_vulnerabilities "$image_total" \ + --argjson critical_vulnerabilities "$image_critical" \ + --argjson fixable_vulnerabilities "$image_fixable" \ + --argjson license_rejected "$image_license_rejected" \ + --argjson operational_errors "$image_operational_errors" \ + '{ + artifact_prefix: $artifact_prefix, + image_tag: $image_tag, + pinned_image: $pinned_image, + index_digest: $index_digest, + current_digest: $current_digest, + image_updated: $image_updated, + platforms: $platforms, + total_vulnerabilities: $total_vulnerabilities, + critical_vulnerabilities: $critical_vulnerabilities, + fixable_vulnerabilities: $fixable_vulnerabilities, + license_rejected: $license_rejected, + operational_errors: $operational_errors + }' >> "$output/image-results.jsonl" + done < <(jq -c '.[]' "$output/images.json") errors=$(jq -Rs 'split("\n") | map(select(length > 0))' "$output/errors.txt") - platforms=$(jq -Rn ' - [inputs | split("\t") | {platform: .[0], digest: .[1]}] - ' < "$output/platforms.tsv") + images=$(jq -s '.' "$output/image-results.jsonl") jq -n \ - --arg image_tag "$image_tag" \ - --arg pinned_image "$pinned_image" \ - --arg index_digest "$index_digest" \ - --arg current_digest "$current_digest" \ - --argjson image_updated "$image_updated" \ - --argjson platforms "$platforms" \ + --argjson images "$images" \ --argjson total_vulnerabilities "$total_vulnerabilities" \ --argjson critical_vulnerabilities "$critical_vulnerabilities" \ --argjson fixable_vulnerabilities "$fixable_vulnerabilities" \ --argjson license_rejected "$license_rejected" \ --argjson operational_errors "$errors" \ '{ - image_tag: $image_tag, - pinned_image: $pinned_image, - index_digest: $index_digest, - current_digest: $current_digest, - image_updated: $image_updated, - platforms: $platforms, + images: $images, total_vulnerabilities: $total_vulnerabilities, critical_vulnerabilities: $critical_vulnerabilities, fixable_vulnerabilities: $fixable_vulnerabilities, @@ -323,8 +377,8 @@ jobs: if: always() uses: actions/upload-artifact@v7.0.1 with: - name: squid-image-scan - path: ${{ runner.temp }}/squid-image-scan + name: container-image-scan + path: ${{ runner.temp }}/container-image-scan if-no-files-found: error retention-days: 14 sandbox: @@ -333,24 +387,28 @@ sandbox: timeout-minutes: 20 --- -# Daily Squid Image Security Scan +# Daily Container Image Security Scan Review the deterministic Syft, Grype, and Grant results in `/tmp/gh-aw/agent/image-scan/`. 1. Read `summary.json` first. -2. If the image tag has not changed, no vulnerabilities were found, all licenses - are accepted, and there are no operational errors, call `noop`. -3. Otherwise, create one issue. Use the title +2. For each image with digest drift, vulnerabilities, rejected licenses, or + operational errors, create one issue. Use the title `Container findings for ` so repeated - findings for the same immutable image are deduplicated. -4. Include: - - the pinned image, current tag digest, and both platform child digests; + findings for the same immutable image are deduplicated. If there are only + global operational errors, create one `Container scan operational failure` + issue. +3. If no image has findings and there are no global operational errors, call + `noop`. +4. In each image issue, include: + - the pinned image, current tag digest, and platform child digests; - scanner versions and Grype database status; - - every vulnerability from both `grype-linux-*.json` files, with platform, - severity, vulnerability ID, package, installed version, and fixed versions; + - every vulnerability from its `grype--*.json` files, with + platform, severity, vulnerability ID, package, installed version, and fixed + versions; - every rejected or unknown license shown in the - `grant-check-linux-*.json` files; + `grant-check--*.json` files; - image digest drift, operational errors, and actionable remediation. 5. Keep the report factual and compact. Never omit lower-severity vulnerabilities. From 9015c60941518e071a353b6d359306b98c335d59 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:47:17 +0000 Subject: [PATCH 4/5] chore: start PR finisher pass Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/skills/agentic-workflows/SKILL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/skills/agentic-workflows/SKILL.md b/.github/skills/agentic-workflows/SKILL.md index acec3f146cd..615a51e551e 100644 --- a/.github/skills/agentic-workflows/SKILL.md +++ b/.github/skills/agentic-workflows/SKILL.md @@ -15,6 +15,7 @@ Repository overlay (optional): Read only the files you need: Load these files from `github/gh-aw` (they are not available locally). +- `.github/aw/action-container-substitutions.md` - `.github/aw/agentic-chat.md` - `.github/aw/agentic-workflows-mcp.md` - `.github/aw/asciicharts.md` From d8d512337859646c069438dc2cbcb399fecb0d6d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:57:51 +0000 Subject: [PATCH 5/5] Harden daily image scan workflow behavior Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../workflows/daily-squid-image-scan.lock.yml | 71 ++++++++++++------- .github/workflows/daily-squid-image-scan.md | 65 +++++++++++------ 2 files changed, 88 insertions(+), 48 deletions(-) diff --git a/.github/workflows/daily-squid-image-scan.lock.yml b/.github/workflows/daily-squid-image-scan.lock.yml index 79d19cca9b0..56521350037 100644 --- a/.github/workflows/daily-squid-image-scan.lock.yml +++ b/.github/workflows/daily-squid-image-scan.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"6387a21ad6c74389e8295dfa770a140aa17e4682e7a47b39e1f3804c5464ba2e","body_hash":"7a1f8b7d3ba999bc2945954c493e41a43a33b260a3374e9b0d67958617da65db","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"2fdca74fe22075f66004cc1d12d83c98bce3aee24e184763ac92e74333ec743c","body_hash":"7a1f8b7d3ba999bc2945954c493e41a43a33b260a3374e9b0d67958617da65db","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"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":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.37","digest":"sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.37@sha256:0d35e8682845f183c1c634699a8e8a6cbe2c271b867031410df74533243c5f67"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.37","digest":"sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.37@sha256:fc2970aadaeae05993e76697d29f03dc8bfb9248ff87a8f3d8b0975485a4b317"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.37","digest":"sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.37@sha256:5abc51995e5901c5d1daeefc957301ee409980e2e607391ec22c06cb2513327b"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"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.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} # 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 # @@ -466,7 +466,7 @@ jobs: DOWNLOAD_OUTCOME: ${{ steps.download_scan.outcome }} if: always() name: Ensure image scan summary exists - run: "results=\"/tmp/gh-aw/agent/image-scan\"\nmkdir -p \"$results\"\nif [ \"$DOWNLOAD_OUTCOME\" != \"success\" ] ||\n ! jq -e 'type == \"object\"' \"$results/summary.json\" > /dev/null 2>&1; then\n jq -n '{\n images: [],\n total_vulnerabilities: 0,\n critical_vulnerabilities: 0,\n fixable_vulnerabilities: 0,\n license_rejected: false,\n operational_errors: [\"The scan job did not publish a valid result artifact.\"],\n tools: {\n syft: \"1.49.0\",\n grype: \"0.116.0\",\n grant: \"0.6.8\"\n }\n }' > \"$results/summary.json\"\nfi" + run: "results=\"/tmp/gh-aw/agent/image-scan\"\nmkdir -p \"$results\"\nif [ \"$DOWNLOAD_OUTCOME\" != \"success\" ] ||\n ! jq -e 'type == \"object\"' \"$results/summary.json\" > /dev/null 2>&1; then\n jq -n '{\n images: [],\n total_vulnerabilities: 0,\n critical_vulnerabilities: 0,\n fixable_vulnerabilities: 0,\n license_rejected: false,\n operational_errors: [\"The scan job did not publish a valid result artifact.\"],\n tools: {\n syft: \"unknown\",\n grype: \"unknown\",\n grant: \"unknown\"\n }\n }' > \"$results/summary.json\"\nfi" - name: Configure Git credentials env: @@ -526,9 +526,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_c6f758a872410540_EOF' - {"create_issue":{"deduplicate_by_title":true,"labels":["cookie","security"],"max":25,"title_prefix":"[container-image-scan] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_c6f758a872410540_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_d3b8ee62b5517e41_EOF' + {"create_issue":{"deduplicate_by_title":true,"labels":["cookie","security"],"max":25,"title_prefix":"[container-image-scan] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_d3b8ee62b5517e41_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -1185,7 +1185,7 @@ jobs: GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/daily-squid-image-scan.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" + GH_AW_NOOP_REPORT_AS_ISSUE: "false" GH_AW_AIC: ${{ needs.agent.outputs.aic }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} @@ -1636,7 +1636,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "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" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"deduplicate_by_title\":true,\"labels\":[\"cookie\",\"security\"],\"max\":25,\"title_prefix\":\"[container-image-scan] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"deduplicate_by_title\":true,\"labels\":[\"cookie\",\"security\"],\"max\":25,\"title_prefix\":\"[container-image-scan] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1663,6 +1663,16 @@ jobs: packages: read timeout-minutes: 120 + env: + GRANT_SHA256_AMD64: 6500f8bbf0f20fb993de8084686e199f0ba1eb494769ff75454286d5ef63f919 + GRANT_SHA256_ARM64: 15ec0b4346a64b5580958dc62c4e7c25ca9e59b7582bab9706679f6b9d2288b8 + GRANT_VERSION: 0.6.8 + GRYPE_SHA256_AMD64: 40aff724297312f91ea390d003bed8d8651c74cc7f5b26732db80b3a408d2fc5 + GRYPE_SHA256_ARM64: 7af3eed24f469b0cf3ab5ec4508d9c12f4bb9c2c6be714f32973c7b5d63cb6a5 + GRYPE_VERSION: 0.116.0 + SYFT_SHA256_AMD64: 7aa2f03ee92739cf643279ba3990548b9925d4e22cae13f46831ee62821147fe + SYFT_SHA256_ARM64: c7c32de183c32368de197edba75e8dba7632915f7761bacd55149a9ca7fe0fa4 + SYFT_VERSION: 1.49.0 steps: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config @@ -1681,7 +1691,19 @@ jobs: id: install_tools run: | set -euo pipefail - test "$(uname -m)" = "x86_64" + arch="$(uname -m)" + case "$arch" in + x86_64) + suffix="amd64" + ;; + aarch64|arm64) + suffix="arm64" + ;; + *) + echo "Unsupported runner architecture: $arch" >&2 + exit 1 + ;; + esac tools_dir="$RUNNER_TEMP/image-scan-tools" mkdir -p "$tools_dir" @@ -1691,23 +1713,20 @@ jobs: checksum="$3" archive="$RUNNER_TEMP/${tool}.tar.gz" curl -fsSL \ - "https://github.com/anchore/${tool}/releases/download/v${version}/${tool}_${version}_linux_amd64.tar.gz" \ + "https://github.com/anchore/${tool}/releases/download/v${version}/${tool}_${version}_linux_${suffix}.tar.gz" \ -o "$archive" echo "${checksum} ${archive}" | sha256sum -c - tar -xzf "$archive" --no-same-owner -C "$tools_dir" "$tool" } - install_tool syft "$SYFT_VERSION" "$SYFT_SHA256" - install_tool grype "$GRYPE_VERSION" "$GRYPE_SHA256" - install_tool grant "$GRANT_VERSION" "$GRANT_SHA256" + syft_sha256_var="SYFT_SHA256_${suffix^^}" + grype_sha256_var="GRYPE_SHA256_${suffix^^}" + grant_sha256_var="GRANT_SHA256_${suffix^^}" + + install_tool syft "$SYFT_VERSION" "${!syft_sha256_var}" + install_tool grype "$GRYPE_VERSION" "${!grype_sha256_var}" + install_tool grant "$GRANT_VERSION" "${!grant_sha256_var}" echo "$tools_dir" >> "$GITHUB_PATH" - env: - GRANT_SHA256: 6500f8bbf0f20fb993de8084686e199f0ba1eb494769ff75454286d5ef63f919 - GRANT_VERSION: 0.6.8 - GRYPE_SHA256: 40aff724297312f91ea390d003bed8d8651c74cc7f5b26732db80b3a408d2fc5 - GRYPE_VERSION: 0.116.0 - SYFT_SHA256: 7aa2f03ee92739cf643279ba3990548b9925d4e22cae13f46831ee62821147fe - SYFT_VERSION: 1.49.0 continue-on-error: true - name: Scan compiled workflow images if: always() @@ -1804,7 +1823,7 @@ jobs: jq -r ' .manifests[]? - | select(.platform.os == "linux") + | select(.platform.os == "linux" and (.platform.architecture == "amd64" or .platform.architecture == "arm64")) | [ .platform.os + "/" + .platform.architecture + (if .platform.variant then "/" + .platform.variant else "" end), @@ -1814,11 +1833,11 @@ jobs: ' "$image_manifest" > "$image_platforms" if [ ! -s "$image_platforms" ]; then if jq -e 'has("manifests")' "$image_manifest" > /dev/null; then - record_image_error "The image index for ${pinned_image} has no Linux manifests." + record_image_error "The image index for ${pinned_image} has no linux/amd64 or linux/arm64 manifests." elif platform=$( docker buildx imagetools inspect "$pinned_image" --format '{{json .Image}}' | jq -er ' - select(.os == "linux") + select(.os == "linux" and (.architecture == "amd64" or .architecture == "arm64")) | .os + "/" + .architecture + (if .variant then "/" + .variant else "" end) ' @@ -1878,7 +1897,7 @@ jobs: GRYPE_DB_AUTO_UPDATE=false grype "sbom:${syft_json}" \ --fail-on critical -o table > "$output/grype-${suffix}.txt" 2>&1 grype_exit=$? - if [ "$grype_exit" -ne 0 ] && [ "$platform_critical" -eq 0 ]; then + if [ "$grype_exit" -ne 0 ] && [ "$grype_exit" -ne 1 ]; then record_image_error "Grype table scan failed unexpectedly for ${image_tag} on ${platform}." fi @@ -1964,9 +1983,9 @@ jobs: license_rejected: $license_rejected, operational_errors: $operational_errors, tools: { - syft: "1.49.0", - grype: "0.116.0", - grant: "0.6.8" + syft: $ENV.SYFT_VERSION, + grype: $ENV.GRYPE_VERSION, + grant: $ENV.GRANT_VERSION } }' > "$output/summary.json" env: diff --git a/.github/workflows/daily-squid-image-scan.md b/.github/workflows/daily-squid-image-scan.md index 02567702b70..02fa00413fc 100644 --- a/.github/workflows/daily-squid-image-scan.md +++ b/.github/workflows/daily-squid-image-scan.md @@ -25,6 +25,8 @@ safe-outputs: labels: [cookie, security] max: 25 deduplicate-by-title: true + noop: + report-as-issue: false steps: - name: Download image scan results id: download_scan @@ -50,9 +52,9 @@ steps: license_rejected: false, operational_errors: ["The scan job did not publish a valid result artifact."], tools: { - syft: "1.49.0", - grype: "0.116.0", - grant: "0.6.8" + syft: "unknown", + grype: "unknown", + grant: "unknown" } }' > "$results/summary.json" fi @@ -74,6 +76,16 @@ jobs: permissions: contents: read packages: read + env: + SYFT_VERSION: "1.49.0" + SYFT_SHA256_AMD64: "7aa2f03ee92739cf643279ba3990548b9925d4e22cae13f46831ee62821147fe" + SYFT_SHA256_ARM64: "c7c32de183c32368de197edba75e8dba7632915f7761bacd55149a9ca7fe0fa4" + GRYPE_VERSION: "0.116.0" + GRYPE_SHA256_AMD64: "40aff724297312f91ea390d003bed8d8651c74cc7f5b26732db80b3a408d2fc5" + GRYPE_SHA256_ARM64: "7af3eed24f469b0cf3ab5ec4508d9c12f4bb9c2c6be714f32973c7b5d63cb6a5" + GRANT_VERSION: "0.6.8" + GRANT_SHA256_AMD64: "6500f8bbf0f20fb993de8084686e199f0ba1eb494769ff75454286d5ef63f919" + GRANT_SHA256_ARM64: "15ec0b4346a64b5580958dc62c4e7c25ca9e59b7582bab9706679f6b9d2288b8" steps: - name: Checkout repository uses: actions/checkout@v7.0.0 @@ -82,16 +94,21 @@ jobs: - name: Install pinned security tools id: install_tools continue-on-error: true - env: - SYFT_VERSION: "1.49.0" - SYFT_SHA256: "7aa2f03ee92739cf643279ba3990548b9925d4e22cae13f46831ee62821147fe" - GRYPE_VERSION: "0.116.0" - GRYPE_SHA256: "40aff724297312f91ea390d003bed8d8651c74cc7f5b26732db80b3a408d2fc5" - GRANT_VERSION: "0.6.8" - GRANT_SHA256: "6500f8bbf0f20fb993de8084686e199f0ba1eb494769ff75454286d5ef63f919" run: | set -euo pipefail - test "$(uname -m)" = "x86_64" + arch="$(uname -m)" + case "$arch" in + x86_64) + suffix="amd64" + ;; + aarch64|arm64) + suffix="arm64" + ;; + *) + echo "Unsupported runner architecture: $arch" >&2 + exit 1 + ;; + esac tools_dir="$RUNNER_TEMP/image-scan-tools" mkdir -p "$tools_dir" @@ -101,15 +118,19 @@ jobs: checksum="$3" archive="$RUNNER_TEMP/${tool}.tar.gz" curl -fsSL \ - "https://github.com/anchore/${tool}/releases/download/v${version}/${tool}_${version}_linux_amd64.tar.gz" \ + "https://github.com/anchore/${tool}/releases/download/v${version}/${tool}_${version}_linux_${suffix}.tar.gz" \ -o "$archive" echo "${checksum} ${archive}" | sha256sum -c - tar -xzf "$archive" --no-same-owner -C "$tools_dir" "$tool" } - install_tool syft "$SYFT_VERSION" "$SYFT_SHA256" - install_tool grype "$GRYPE_VERSION" "$GRYPE_SHA256" - install_tool grant "$GRANT_VERSION" "$GRANT_SHA256" + syft_sha256_var="SYFT_SHA256_${suffix^^}" + grype_sha256_var="GRYPE_SHA256_${suffix^^}" + grant_sha256_var="GRANT_SHA256_${suffix^^}" + + install_tool syft "$SYFT_VERSION" "${!syft_sha256_var}" + install_tool grype "$GRYPE_VERSION" "${!grype_sha256_var}" + install_tool grant "$GRANT_VERSION" "${!grant_sha256_var}" echo "$tools_dir" >> "$GITHUB_PATH" - name: Scan compiled workflow images if: always() @@ -208,7 +229,7 @@ jobs: jq -r ' .manifests[]? - | select(.platform.os == "linux") + | select(.platform.os == "linux" and (.platform.architecture == "amd64" or .platform.architecture == "arm64")) | [ .platform.os + "/" + .platform.architecture + (if .platform.variant then "/" + .platform.variant else "" end), @@ -218,11 +239,11 @@ jobs: ' "$image_manifest" > "$image_platforms" if [ ! -s "$image_platforms" ]; then if jq -e 'has("manifests")' "$image_manifest" > /dev/null; then - record_image_error "The image index for ${pinned_image} has no Linux manifests." + record_image_error "The image index for ${pinned_image} has no linux/amd64 or linux/arm64 manifests." elif platform=$( docker buildx imagetools inspect "$pinned_image" --format '{{json .Image}}' | jq -er ' - select(.os == "linux") + select(.os == "linux" and (.architecture == "amd64" or .architecture == "arm64")) | .os + "/" + .architecture + (if .variant then "/" + .variant else "" end) ' @@ -282,7 +303,7 @@ jobs: GRYPE_DB_AUTO_UPDATE=false grype "sbom:${syft_json}" \ --fail-on critical -o table > "$output/grype-${suffix}.txt" 2>&1 grype_exit=$? - if [ "$grype_exit" -ne 0 ] && [ "$platform_critical" -eq 0 ]; then + if [ "$grype_exit" -ne 0 ] && [ "$grype_exit" -ne 1 ]; then record_image_error "Grype table scan failed unexpectedly for ${image_tag} on ${platform}." fi @@ -368,9 +389,9 @@ jobs: license_rejected: $license_rejected, operational_errors: $operational_errors, tools: { - syft: "1.49.0", - grype: "0.116.0", - grant: "0.6.8" + syft: $ENV.SYFT_VERSION, + grype: $ENV.GRYPE_VERSION, + grant: $ENV.GRANT_VERSION } }' > "$output/summary.json" - name: Upload image scan results