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