diff --git a/.github/workflows/daily-firewall-report.lock.yml b/.github/workflows/daily-firewall-report.lock.yml
deleted file mode 100644
index 576631b24ef..00000000000
--- a/.github/workflows/daily-firewall-report.lock.yml
+++ /dev/null
@@ -1,1634 +0,0 @@
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"487f54586e79a43667e7dfc3765a1a9820d8a665f682306518226d4b66d4f626","strict":true,"agent_id":"copilot"}
-# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-go","sha":"4a3601121dd01d1626a1e23e37211e3254c1c06c","version":"v6.4.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/setup-python","sha":"a309ff8b426b58ec0e2a45f0f869d46889d02405","version":"v6.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"docker/build-push-action","sha":"bcafcacb16a39f128d818304e6c9c0c18556b85f","version":"v7.1.0"},{"repo":"docker/setup-buildx-action","sha":"4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd","version":"v4"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28","digest":"sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28","digest":"sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28","digest":"sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.0"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
-# ___ _ _
-# / _ \ | | (_)
-# | |_| | __ _ ___ _ __ | |_ _ ___
-# | _ |/ _` |/ _ \ '_ \| __| |/ __|
-# | | | | (_| | __/ | | | |_| | (__
-# \_| |_/\__, |\___|_| |_|\__|_|\___|
-# __/ |
-# _ _ |___/
-# | | | | / _| |
-# | | | | ___ _ __ _ __| |_| | _____ ____
-# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___|
-# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
-# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
-#
-# This file was automatically generated by gh-aw. DO NOT EDIT.
-#
-# 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/
-#
-# Collects and reports on firewall log events to monitor network security and access patterns
-#
-# Resolved workflow manifest:
-# Imports:
-# - shared/daily-audit-discussion.md
-# - shared/observability-otlp.md
-# - shared/reporting.md
-# - shared/daily-audit-base.md
-# - shared/trending-charts-simple.md
-# - shared/daily-audit-charts.md
-# Includes:
-# - shared/noop-reminder.md
-#
-# Secrets used:
-# - COPILOT_GITHUB_TOKEN
-# - GH_AW_GITHUB_MCP_SERVER_TOKEN
-# - GH_AW_GITHUB_TOKEN
-# - GH_AW_OTEL_ENDPOINT
-# - GH_AW_OTEL_HEADERS
-# - GITHUB_TOKEN
-#
-# Custom actions used:
-# - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
-# - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
-# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
-# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
-# - actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
-# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
-# - actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
-# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
-# - docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
-# - docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
-#
-# Container images used:
-# - ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a
-# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb
-# - ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474
-# - ghcr.io/github/gh-aw-mcpg:v0.3.0
-# - ghcr.io/github/github-mcp-server:v1.0.3
-# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f
-
-name: "Daily Firewall Logs Collector and Reporter"
-"on":
- schedule:
- - cron: "30 11 * * *"
- # 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 Firewall Logs Collector and Reporter"
-
-env:
- OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.GH_AW_OTEL_ENDPOINT }}
- OTEL_SERVICE_NAME: gh-aw
- OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.GH_AW_OTEL_HEADERS }}
-
-jobs:
- activation:
- runs-on: ubuntu-slim
- permissions:
- actions: read
- contents: read
- outputs:
- comment_id: ""
- comment_repo: ""
- 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 }}
- secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
- 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- repository: github/gh-aw
- sparse-checkout: |
- actions
- persist-credentials: false
- - name: Setup Scripts
- id: setup
- uses: ./actions/setup
- with:
- destination: ${{ runner.temp }}/gh-aw/actions
- job-name: ${{ github.job }}
- - name: Mask OTLP telemetry headers
- run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- - 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 || 'auto' }}
- GH_AW_INFO_VERSION: "1.0.36"
- GH_AW_INFO_AGENT_VERSION: "1.0.36"
- GH_AW_INFO_WORKFLOW_NAME: "Daily Firewall Logs Collector and Reporter"
- GH_AW_INFO_EXPERIMENTAL: "false"
- GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
- GH_AW_INFO_STAGED: "false"
- GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","python"]'
- GH_AW_INFO_FIREWALL_ENABLED: "true"
- GH_AW_INFO_AWF_VERSION: "v0.25.28"
- GH_AW_INFO_AWMG_VERSION: ""
- GH_AW_INFO_FIREWALL_TYPE: "squid"
- GH_AW_COMPILED_STRICT: "true"
- uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- 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: Validate COPILOT_GITHUB_TOKEN secret
- id: validate-secret
- run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
- env:
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- - name: Checkout .github and .agents folders
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
- sparse-checkout: |
- .github
- .agents
- actions/setup
- .claude
- .codex
- .crush
- .gemini
- .opencode
- sparse-checkout-cone-mode: true
- fetch-depth: 1
- - name: Save agent config folders for base branch restoration
- env:
- GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode"
- GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.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@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- env:
- GH_AW_WORKFLOW_FILE: "daily-firewall-report.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: 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_GITHUB_ACTOR: ${{ github.actor }}
- GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
- GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
- GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
- GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
- 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_01e0a608c28ca7bb_EOF'
-
- GH_AW_PROMPT_01e0a608c28ca7bb_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/agentic_workflows_guide.md"
- cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md"
- cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_01e0a608c28ca7bb_EOF'
-
- Tools: create_discussion, upload_asset(max:3), missing_tool, missing_data, noop
-
- upload_asset: provide a file path; returns a URL; assets are published after the workflow completes (safeoutputs).
-
- GH_AW_PROMPT_01e0a608c28ca7bb_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_01e0a608c28ca7bb_EOF'
-
- The following GitHub context information is available for this workflow:
- {{#if __GH_AW_GITHUB_ACTOR__ }}
- - **actor**: __GH_AW_GITHUB_ACTOR__
- {{/if}}
- {{#if __GH_AW_GITHUB_REPOSITORY__ }}
- - **repository**: __GH_AW_GITHUB_REPOSITORY__
- {{/if}}
- {{#if __GH_AW_GITHUB_WORKSPACE__ }}
- - **workspace**: __GH_AW_GITHUB_WORKSPACE__
- {{/if}}
- {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }}
- - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__
- {{/if}}
- {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }}
- - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__
- {{/if}}
- {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }}
- - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__
- {{/if}}
- {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }}
- - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__
- {{/if}}
- {{#if __GH_AW_GITHUB_RUN_ID__ }}
- - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
- {{/if}}
-
-
- GH_AW_PROMPT_01e0a608c28ca7bb_EOF
- cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_01e0a608c28ca7bb_EOF'
-
- {{#runtime-import .github/workflows/shared/trending-charts-simple.md}}
- {{#runtime-import .github/workflows/shared/reporting.md}}
- {{#runtime-import .github/workflows/shared/observability-otlp.md}}
- {{#runtime-import .github/workflows/daily-firewall-report.md}}
- GH_AW_PROMPT_01e0a608c28ca7bb_EOF
- } > "$GH_AW_PROMPT"
- - name: Interpolate variables and render templates
- uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- 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@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- env:
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_ALLOWED_EXTENSIONS: ''
- GH_AW_CACHE_DESCRIPTION: ''
- GH_AW_CACHE_DIR: '/tmp/gh-aw/cache-memory/'
- GH_AW_GITHUB_ACTOR: ${{ github.actor }}
- GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
- GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
- GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
- GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
- 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: "- `agenticworkflows` — run `agenticworkflows --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_ALLOWED_EXTENSIONS: process.env.GH_AW_ALLOWED_EXTENSIONS,
- GH_AW_CACHE_DESCRIPTION: process.env.GH_AW_CACHE_DESCRIPTION,
- GH_AW_CACHE_DIR: process.env.GH_AW_CACHE_DIR,
- GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
- GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
- GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
- GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER,
- GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER,
- 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
- path: |
- /tmp/gh-aw/aw_info.json
- /tmp/gh-aw/aw-prompts/prompt.txt
- /tmp/gh-aw/github_rate_limits.jsonl
- /tmp/gh-aw/base
- if-no-files-found: ignore
- retention-days: 1
-
- agent:
- needs: activation
- runs-on: ubuntu-latest
- permissions:
- actions: read
- contents: read
- discussions: read
- issues: read
- pull-requests: read
- security-events: read
- concurrency:
- group: "gh-aw-copilot-${{ github.workflow }}"
- env:
- DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
- GH_AW_ASSETS_ALLOWED_EXTS: ".png,.jpg,.jpeg,.svg"
- GH_AW_ASSETS_BRANCH: "assets/${{ github.workflow }}"
- GH_AW_ASSETS_MAX_SIZE_KB: 10240
- GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
- GH_AW_WORKFLOW_ID_SANITIZED: dailyfirewallreport
- outputs:
- agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }}
- 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 }}
- inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }}
- mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }}
- model: ${{ needs.activation.outputs.model }}
- model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }}
- output: ${{ steps.collect_output.outputs.output }}
- output_types: ${{ steps.collect_output.outputs.output_types }}
- setup-trace-id: ${{ steps.setup.outputs.trace-id }}
- steps:
- - name: Checkout actions folder
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- repository: github/gh-aw
- sparse-checkout: |
- actions
- 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 }}
- - 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: Mask OTLP telemetry headers
- run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- - name: Checkout repository
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
- - name: Setup Go for CLI build
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
- with:
- go-version-file: go.mod
- cache: true
- - name: Build gh-aw CLI
- run: |
- echo "Building gh-aw CLI for linux/amd64..."
- mkdir -p dist
- VERSION=$(git describe --tags --always --dirty)
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
- -ldflags "-s -w -X main.version=${VERSION}" \
- -o dist/gh-aw-linux-amd64 \
- ./cmd/gh-aw
- # Copy binary to root for direct execution in user-defined steps
- cp dist/gh-aw-linux-amd64 ./gh-aw
- chmod +x ./gh-aw
- echo "✓ Built gh-aw CLI successfully"
- - name: Setup Docker Buildx
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
- - name: Build gh-aw Docker image
- uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
- with:
- context: .
- platforms: linux/amd64
- push: false
- load: true
- tags: localhost/gh-aw:dev
- build-args: |
- BINARY=dist/gh-aw-linux-amd64
- - name: Setup Python
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- with:
- python-version: '3.12'
- - 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: Setup Python environment
- run: "mkdir -p /tmp/gh-aw/python/{data,charts,artifacts}\n# Create a virtual environment for proper package isolation (avoids --break-system-packages)\nif [ ! -d /tmp/gh-aw/venv ]; then\n python3 -m venv /tmp/gh-aw/venv\nfi\necho \"/tmp/gh-aw/venv/bin\" >> \"$GITHUB_PATH\"\n/tmp/gh-aw/venv/bin/pip install --quiet numpy pandas matplotlib seaborn scipy\n"
- - if: always()
- name: Upload source files and data
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
- with:
- if-no-files-found: warn
- name: trending-source-and-data
- path: |
- /tmp/gh-aw/python/*.py
- /tmp/gh-aw/python/data/*
- retention-days: 30
-
- # Cache memory file share configuration from frontmatter processed below
- - name: Create cache-memory directory
- run: bash "${RUNNER_TEMP}/gh-aw/actions/create_cache_memory_dir.sh"
- - name: Restore cache-memory file share data
- uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
- with:
- key: memory-none-nopolicy-trending-data-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }}
- path: /tmp/gh-aw/cache-memory
- restore-keys: |
- memory-none-nopolicy-trending-data-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-
- - name: Setup cache-memory git repository
- env:
- GH_AW_CACHE_DIR: /tmp/gh-aw/cache-memory
- GH_AW_MIN_INTEGRITY: none
- run: bash "${RUNNER_TEMP}/gh-aw/actions/setup_cache_memory_git.sh"
- - name: Configure Git credentials
- env:
- REPO_NAME: ${{ github.repository }}
- SERVER_URL: ${{ github.server_url }}
- GITHUB_TOKEN: ${{ github.token }}
- run: |
- git config --global user.email "github-actions[bot]@users.noreply.github.com"
- git config --global user.name "github-actions[bot]"
- git config --global am.keepcr true
- # Re-authenticate git with GitHub token
- SERVER_URL_STRIPPED="${SERVER_URL#https://}"
- git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
- echo "Git configured with standard GitHub Actions identity"
- - name: Checkout PR branch
- id: checkout-pr
- if: |
- github.event.pull_request || github.event.issue.pull_request
- uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- 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.36
- env:
- GH_HOST: github.com
- - name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28
- - name: Determine automatic lockdown mode for GitHub MCP Server
- id: determine-automatic-lockdown
- uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # 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: Download activation artifact
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- name: activation
- path: /tmp/gh-aw
- - name: Restore agent config folders from base branch
- if: steps.checkout-pr.outcome == 'success'
- env:
- GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode"
- GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md opencode.jsonc"
- run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh"
- - name: Download container images
- run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 ghcr.io/github/gh-aw-mcpg:v0.3.0 ghcr.io/github/github-mcp-server:v1.0.3 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f
- - name: Install gh-aw extension
- env:
- GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
- run: |
- # Check if gh-aw extension is already installed
- if gh extension list | grep -q "github/gh-aw"; then
- echo "gh-aw extension already installed, upgrading..."
- gh extension upgrade gh-aw || true
- else
- echo "Installing gh-aw extension..."
- gh extension install github/gh-aw
- fi
- gh aw --version
- # Copy the gh-aw binary to ${RUNNER_TEMP}/gh-aw for MCP server containerization
- mkdir -p "${RUNNER_TEMP}/gh-aw"
- GH_AW_BIN=""
- GH_AW_BIN=$(command -v gh-aw 2>/dev/null) || true
- if [ -z "$GH_AW_BIN" ]; then
- GH_AW_BIN=$(find "${HOME}/.local/share/gh/extensions/gh-aw" -name 'gh-aw' -type f 2>/dev/null | head -1) || true
- fi
- if [ -n "$GH_AW_BIN" ] && [ -f "$GH_AW_BIN" ]; then
- cp "$GH_AW_BIN" "${RUNNER_TEMP}/gh-aw/gh-aw"
- chmod +x "${RUNNER_TEMP}/gh-aw/gh-aw"
- echo "Copied gh-aw binary to ${RUNNER_TEMP}/gh-aw/gh-aw"
- else
- echo "::error::Failed to find gh-aw binary for MCP server"
- exit 1
- fi
- - name: Write Safe Outputs Config
- env:
- GITHUB_WORKFLOW: ${{ github.workflow }}
- 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_72c5fa97b3412236_EOF
- {"create_discussion":{"category":"audits","close_older_discussions":true,"expires":72,"fallback_to_issue":true,"max":1,"title_prefix":"[daily-firewall-report] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"upload_asset":{"allowed-exts":[".png",".jpg",".jpeg",".svg"],"branch":"assets/${GITHUB_WORKFLOW}","max":3,"max-size":10240}}
- GH_AW_SAFE_OUTPUTS_CONFIG_72c5fa97b3412236_EOF
- - name: Write Safe Outputs Tools
- env:
- GH_AW_TOOLS_META_JSON: |
- {
- "description_suffixes": {
- "create_discussion": " CONSTRAINTS: Maximum 1 discussion(s) can be created. Title will be prefixed with \"[daily-firewall-report] \". Discussions will be created in category \"audits\".",
- "upload_asset": " CONSTRAINTS: Maximum 3 asset(s) can be uploaded. Maximum file size: 10240KB. Allowed file extensions: [.png .jpg .jpeg .svg]."
- },
- "repo_params": {},
- "dynamic_tools": []
- }
- GH_AW_VALIDATION_JSON: |
- {
- "create_discussion": {
- "defaultMax": 1,
- "fields": {
- "body": {
- "required": true,
- "type": "string",
- "sanitize": true,
- "maxLength": 65000
- },
- "category": {
- "type": "string",
- "sanitize": true,
- "maxLength": 128
- },
- "repo": {
- "type": "string",
- "maxLength": 256
- },
- "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
- }
- }
- },
- "upload_asset": {
- "defaultMax": 10,
- "fields": {
- "path": {
- "required": true,
- "type": "string"
- }
- }
- }
- }
- uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- 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: Generate Safe Outputs MCP Server Config
- id: safe-outputs-config
- run: |
- # Generate a secure random API key (360 bits of entropy, 40+ chars)
- # Mask immediately to prevent timing vulnerabilities
- API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
- echo "::add-mask::${API_KEY}"
-
- PORT=3001
-
- # Set outputs for next steps
- {
- echo "safe_outputs_api_key=${API_KEY}"
- echo "safe_outputs_port=${PORT}"
- } >> "$GITHUB_OUTPUT"
-
- echo "Safe Outputs MCP server will run on port ${PORT}"
-
- - name: Start Safe Outputs MCP HTTP Server
- id: safe-outputs-start
- env:
- DEBUG: '*'
- GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
- GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }}
- GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }}
- GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json
- GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json
- GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
- run: |
- # Environment variables are set above to prevent template injection
- export DEBUG
- export GH_AW_SAFE_OUTPUTS
- export GH_AW_SAFE_OUTPUTS_PORT
- export GH_AW_SAFE_OUTPUTS_API_KEY
- export GH_AW_SAFE_OUTPUTS_TOOLS_PATH
- export GH_AW_SAFE_OUTPUTS_CONFIG_PATH
- export GH_AW_MCP_LOG_DIR
-
- bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh"
-
- - name: Start MCP Gateway
- id: start-mcp-gateway
- env:
- GH_AW_ASSETS_ALLOWED_EXTS: ${{ env.GH_AW_ASSETS_ALLOWED_EXTS }}
- GH_AW_ASSETS_BRANCH: ${{ env.GH_AW_ASSETS_BRANCH }}
- GH_AW_ASSETS_MAX_SIZE_KB: ${{ env.GH_AW_ASSETS_MAX_SIZE_KB }}
- GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
- GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }}
- GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }}
- 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="host.docker.internal"
- 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='["agenticworkflows"]'
- echo 'GH_AW_MCP_CLI_SERVERS=["agenticworkflows"]' >> "$GITHUB_ENV"
- MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0')
- MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0')
- DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0')
- export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/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 DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.0'
-
- mkdir -p /home/runner/.copilot
- GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_ec81959415ea581b_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
- {
- "mcpServers": {
- "agenticworkflows": {
- "type": "stdio",
- "container": "localhost/gh-aw:dev",
- "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"],
- "args": ["--network", "host", "-w", "\${GITHUB_WORKSPACE}"],
- "env": {
- "DEBUG": "*",
- "GITHUB_TOKEN": "\${GITHUB_TOKEN}",
- "GITHUB_ACTOR": "\${GITHUB_ACTOR}",
- "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}"
- },
- "guard-policies": {
- "write-sink": {
- "accept": [
- "*"
- ]
- }
- }
- },
- "github": {
- "type": "stdio",
- "container": "ghcr.io/github/github-mcp-server:v1.0.3",
- "env": {
- "GITHUB_HOST": "\${GITHUB_SERVER_URL}",
- "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}",
- "GITHUB_READ_ONLY": "1",
- "GITHUB_TOOLSETS": "all"
- },
- "guard-policies": {
- "allow-only": {
- "min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY",
- "repos": "$GITHUB_MCP_GUARD_REPOS"
- }
- }
- },
- "safeoutputs": {
- "type": "http",
- "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT",
- "headers": {
- "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}"
- },
- "guard-policies": {
- "write-sink": {
- "accept": [
- "*"
- ]
- }
- }
- }
- },
- "gateway": {
- "port": $MCP_GATEWAY_PORT,
- "domain": "${MCP_GATEWAY_DOMAIN}",
- "apiKey": "${MCP_GATEWAY_API_KEY}",
- "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}",
- "opentelemetry": {
- "endpoint": "${{ secrets.GH_AW_OTEL_ENDPOINT }}",
- "headers": "${OTEL_EXPORTER_OTLP_HEADERS}",
- "traceId": "${GITHUB_AW_OTEL_TRACE_ID}",
- "spanId": "${GITHUB_AW_OTEL_PARENT_SPAN_ID}"
- }
- }
- }
- GH_AW_MCP_CONFIG_ec81959415ea581b_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@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- 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 git credentials
- continue-on-error: true
- run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh"
- - name: Execute GitHub Copilot CLI
- id: agentic_execution
- # Copilot CLI tool arguments (sorted):
- timeout-minutes: 45
- run: |
- set -o pipefail
- touch /tmp/gh-aw/agent-step-summary.md
- GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
- export GH_AW_NODE_BIN
- (umask 177 && touch /tmp/gh-aw/agent-stdio.log)
- # shellcheck disable=SC1003
- sudo -E awf --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" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.pythonhosted.org,anaconda.org,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,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,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,files.pythonhosted.org,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,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,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' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \
- -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; 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 /tmp/gh-aw/cache-memory/ --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:
- COPILOT_AGENT_RUNNER_TYPE: STANDALONE
- COPILOT_API_KEY: dummy-byok-key-for-offline-mode
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }}
- GH_AW_ASSETS_ALLOWED_EXTS: ".png,.jpg,.jpeg,.svg"
- GH_AW_ASSETS_BRANCH: "assets/${{ github.workflow }}"
- GH_AW_ASSETS_MAX_SIZE_KB: 10240
- GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json
- 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_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]
- XDG_CONFIG_HOME: /home/runner
- - name: Detect Copilot errors
- id: detect-copilot-errors
- if: always()
- continue-on-error: true
- run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs"
- - name: Configure Git credentials
- env:
- REPO_NAME: ${{ github.repository }}
- SERVER_URL: ${{ github.server_url }}
- GITHUB_TOKEN: ${{ github.token }}
- run: |
- git config --global user.email "github-actions[bot]@users.noreply.github.com"
- git config --global user.name "github-actions[bot]"
- git config --global am.keepcr true
- # Re-authenticate git with GitHub token
- SERVER_URL_STRIPPED="${SERVER_URL#https://}"
- git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
- echo "Git configured with standard GitHub Actions identity"
- - 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@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- 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: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GH_AW_OTEL_ENDPOINT,GITHUB_TOKEN'
- SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_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_GH_AW_OTEL_ENDPOINT: ${{ secrets.GH_AW_OTEL_ENDPOINT }}
- 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@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- env:
- GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
- GH_AW_ALLOWED_DOMAINS: "*.pythonhosted.org,anaconda.org,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,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,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,files.pythonhosted.org,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,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,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@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- env:
- GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
- 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@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- 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: |
- # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts
- # AWF runs with sudo, creating files owned by root
- sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall 2>/dev/null || true
- # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step)
- if command -v awf &> /dev/null; then
- awf logs summary | tee -a "$GITHUB_STEP_SUMMARY"
- else
- echo 'AWF binary not installed, skipping firewall log summary'
- fi
- - name: Parse token usage for step summary
- if: always()
- continue-on-error: true
- uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- 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: Generate observability summary
- if: always()
- uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- 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_observability_summary.cjs');
- await main(core);
- - 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
- - name: Commit cache-memory changes
- if: always()
- env:
- GH_AW_CACHE_DIR: /tmp/gh-aw/cache-memory
- run: bash "${RUNNER_TEMP}/gh-aw/actions/commit_cache_memory_git.sh"
- - name: Upload cache-memory data as artifact
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
- if: always()
- with:
- name: cache-memory
- path: /tmp/gh-aw/cache-memory
- # Upload safe-outputs assets for upload_assets job
- - name: Upload Safe Outputs Assets
- if: always()
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
- with:
- name: safe-outputs-assets
- path: /tmp/gh-aw/safeoutputs/assets/
- retention-days: 1
- if-no-files-found: ignore
- - 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/agent/
- /tmp/gh-aw/github_rate_limits.jsonl
- /tmp/gh-aw/otel.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/sandbox/firewall/logs/
- /tmp/gh-aw/sandbox/firewall/audit/
- if-no-files-found: ignore
-
- conclusion:
- needs:
- - activation
- - agent
- - detection
- - safe_outputs
- - update_cache_memory
- - upload_assets
- if: >
- always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
- needs.activation.outputs.stale_lock_file_failed == 'true')
- runs-on: ubuntu-slim
- permissions:
- contents: read
- discussions: write
- issues: write
- concurrency:
- group: "gh-aw-conclusion-daily-firewall-report"
- cancel-in-progress: false
- 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- repository: github/gh-aw
- sparse-checkout: |
- actions
- 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 }}
- - 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: Process no-op messages
- id: noop
- uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- 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 Firewall Logs Collector and Reporter"
- GH_AW_TRACKER_ID: "daily-firewall-report"
- 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"
- 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@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- env:
- GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
- GH_AW_WORKFLOW_NAME: "Daily Firewall Logs Collector and Reporter"
- GH_AW_TRACKER_ID: "daily-firewall-report"
- 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@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- 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 Firewall Logs Collector and Reporter"
- GH_AW_TRACKER_ID: "daily-firewall-report"
- 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@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- 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 Firewall Logs Collector and Reporter"
- GH_AW_TRACKER_ID: "daily-firewall-report"
- 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@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- env:
- GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
- GH_AW_WORKFLOW_NAME: "Daily Firewall Logs Collector and Reporter"
- GH_AW_TRACKER_ID: "daily-firewall-report"
- 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-firewall-report"
- GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "12"
- GH_AW_ENGINE_ID: "copilot"
- GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }}
- GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
- 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_CREATE_DISCUSSION_ERRORS: ${{ needs.safe_outputs.outputs.create_discussion_errors }}
- GH_AW_CREATE_DISCUSSION_ERROR_COUNT: ${{ needs.safe_outputs.outputs.create_discussion_error_count }}
- GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
- GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }}
- GH_AW_GROUP_REPORTS: "false"
- GH_AW_FAILURE_REPORT_AS_ISSUE: "true"
- GH_AW_TIMEOUT_MINUTES: "45"
- GH_AW_CACHE_MEMORY_ENABLED: "true"
- 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' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true')
- runs-on: ubuntu-latest
- permissions:
- contents: read
- outputs:
- 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- repository: github/gh-aw
- sparse-checkout: |
- actions
- 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 }}
- - 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- 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.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474
- - 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/runner/.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
- cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
- 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@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- env:
- WORKFLOW_NAME: "Daily Firewall Logs Collector and Reporter"
- WORKFLOW_DESCRIPTION: "Collects and reports on firewall log events to monitor network security and access patterns"
- 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@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.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.36
- env:
- GH_HOST: github.com
- - name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28
- - name: Execute GitHub Copilot CLI
- if: always() && steps.detection_guard.outputs.run_detection == 'true'
- id: detection_agentic_execution
- # Copilot CLI tool arguments (sorted):
- timeout-minutes: 20
- run: |
- set -o pipefail
- touch /tmp/gh-aw/agent-step-summary.md
- GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
- export GH_AW_NODE_BIN
- (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
- # shellcheck disable=SC1003
- sudo -E awf --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" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \
- -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; 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:
- COPILOT_AGENT_RUNNER_TYPE: STANDALONE
- COPILOT_API_KEY: dummy-byok-key-for-offline-mode
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }}
- GH_AW_PHASE: detection
- GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- 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]
- XDG_CONFIG_HOME: /home/runner
- - 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()
- uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- env:
- RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
- GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
- 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_threat_detection_results.cjs');
- await main();
-
- safe_outputs:
- needs:
- - activation
- - agent
- - detection
- if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
- runs-on: ubuntu-slim
- permissions:
- contents: read
- discussions: write
- issues: write
- timeout-minutes: 15
- env:
- GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/daily-firewall-report"
- 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.36"
- GH_AW_TRACKER_ID: "daily-firewall-report"
- GH_AW_WORKFLOW_ID: "daily-firewall-report"
- GH_AW_WORKFLOW_NAME: "Daily Firewall Logs Collector and Reporter"
- 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 }}
- 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- repository: github/gh-aw
- sparse-checkout: |
- actions
- 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 }}
- - name: Mask OTLP telemetry headers
- run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
- - 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: |
- # 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@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- env:
- GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
- GH_AW_ALLOWED_DOMAINS: "*.pythonhosted.org,anaconda.org,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,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,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,files.pythonhosted.org,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,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,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_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":72,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[daily-firewall-report] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"upload_asset\":{\"allowed-exts\":[\".png\",\".jpg\",\".jpeg\",\".svg\"],\"branch\":\"assets/${{ github.workflow }}\",\"max\":3,\"max-size\":10240}}"
- 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
-
- update_cache_memory:
- needs:
- - activation
- - agent
- - detection
- if: >
- always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') &&
- needs.agent.result == 'success'
- runs-on: ubuntu-slim
- permissions:
- contents: read
- env:
- GH_AW_WORKFLOW_ID_SANITIZED: dailyfirewallreport
- steps:
- - name: Checkout actions folder
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- repository: github/gh-aw
- sparse-checkout: |
- actions
- 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 }}
- - name: Download cache-memory artifact (default)
- id: download_cache_default
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- continue-on-error: true
- with:
- name: cache-memory
- path: /tmp/gh-aw/cache-memory
- - name: Check if cache-memory folder has content (default)
- id: check_cache_default
- shell: bash
- run: |
- if [ -d "/tmp/gh-aw/cache-memory" ] && [ "$(ls -A /tmp/gh-aw/cache-memory 2>/dev/null)" ]; then
- echo "has_content=true" >> "$GITHUB_OUTPUT"
- else
- echo "has_content=false" >> "$GITHUB_OUTPUT"
- fi
- - name: Save cache-memory to cache (default)
- if: steps.check_cache_default.outputs.has_content == 'true'
- uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
- with:
- key: memory-none-nopolicy-trending-data-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }}
- path: /tmp/gh-aw/cache-memory
-
- upload_assets:
- needs:
- - activation
- - agent
- if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'upload_asset')
- runs-on: ubuntu-slim
- permissions:
- contents: write
- timeout-minutes: 10
- outputs:
- branch_name: ${{ steps.upload_assets.outputs.branch_name }}
- published_count: ${{ steps.upload_assets.outputs.published_count }}
- steps:
- - name: Checkout actions folder
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- repository: github/gh-aw
- sparse-checkout: |
- actions
- 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 }}
- - name: Checkout repository
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
- fetch-depth: 0
- - name: Configure Git credentials
- env:
- REPO_NAME: ${{ github.repository }}
- SERVER_URL: ${{ github.server_url }}
- GITHUB_TOKEN: ${{ github.token }}
- run: |
- git config --global user.email "github-actions[bot]@users.noreply.github.com"
- git config --global user.name "github-actions[bot]"
- git config --global am.keepcr true
- # Re-authenticate git with GitHub token
- SERVER_URL_STRIPPED="${SERVER_URL#https://}"
- git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
- echo "Git configured with standard GitHub Actions identity"
- - name: Download assets
- continue-on-error: true
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with:
- name: safe-outputs-assets
- path: /tmp/gh-aw/safeoutputs/assets/
- - name: List downloaded asset files
- continue-on-error: true
- run: |
- echo "Downloaded asset files:"
- find /tmp/gh-aw/safeoutputs/assets/ -maxdepth 1 -ls
- - 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: Push assets
- id: upload_assets
- uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
- env:
- GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
- GH_AW_ASSETS_BRANCH: "assets/${{ github.workflow }}"
- GH_AW_ASSETS_MAX_SIZE_KB: 10240
- GH_AW_ASSETS_ALLOWED_EXTS: ".png,.jpg,.jpeg,.svg"
- GH_AW_WORKFLOW_NAME: "Daily Firewall Logs Collector and Reporter"
- GH_AW_TRACKER_ID: "daily-firewall-report"
- GH_AW_ENGINE_ID: "copilot"
- GH_AW_ENGINE_VERSION: "1.0.36"
- GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
- 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/upload_assets.cjs');
- await main();
- - name: Restore actions folder
- if: always()
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- repository: github/gh-aw
- sparse-checkout: |
- actions/setup
- sparse-checkout-cone-mode: true
- persist-credentials: false
-
diff --git a/.github/workflows/daily-firewall-report.md b/.github/workflows/daily-firewall-report.md
deleted file mode 100644
index 1ee82bfea44..00000000000
--- a/.github/workflows/daily-firewall-report.md
+++ /dev/null
@@ -1,513 +0,0 @@
----
-description: Collects and reports on firewall log events to monitor network security and access patterns
-on:
- schedule:
- # Every day at 10am UTC
- - cron: daily
- workflow_dispatch:
-
-permissions:
- contents: read
- actions: read
- issues: read
- pull-requests: read
- discussions: read
- security-events: read
-
-tracker-id: daily-firewall-report
-timeout-minutes: 45
-
-safe-outputs:
- upload-asset:
- max: 3
- allowed-exts: [.png, .jpg, .jpeg, .svg]
-tools:
- mount-as-clis: true
- agentic-workflows:
- github:
- toolsets:
- - all
- bash:
- - "*"
- edit:
-imports:
- - uses: shared/daily-audit-charts.md
- with:
- title-prefix: "[daily-firewall-report] "
-
-features:
- mcp-cli: true
----
-
-{{#runtime-import? .github/shared-instructions.md}}
-
-# Daily Firewall Logs Collector and Reporter
-
-Collect and analyze firewall logs from all agentic workflows that use the firewall feature.
-
-## 📊 Trend Charts Requirement
-
-**IMPORTANT**: Generate exactly 2 trend charts that showcase firewall activity patterns over time.
-
-### Chart Generation Process
-
-**Phase 1: Data Collection**
-
-Collect data for the past 30 days (or available data) from firewall audit logs:
-
-1. **Firewall Request Data**:
- - Count of allowed requests per day
- - Count of blocked requests per day
- - Total requests per day
-
-2. **Top Blocked Domains Data**:
- - Frequency of top 10 blocked domains over the period
- - Trends in blocking patterns by domain category
-
-**Phase 2: Data Preparation**
-
-1. Create CSV files in `/tmp/gh-aw/python/data/` with the collected data:
- - `firewall_requests.csv` - Daily allowed/blocked request counts
- - `blocked_domains.csv` - Top blocked domains with frequencies
-
-2. Each CSV should have a date column and metric columns with appropriate headers
-
-**Phase 3: Chart Generation**
-
-Generate exactly **2 high-quality trend charts**:
-
-**Chart 1: Firewall Request Trends**
-- Stacked area chart or multi-line chart showing:
- - Allowed requests (area/line, green)
- - Blocked requests (area/line, red)
- - Total requests trend line
-- X-axis: Date (last 30 days)
-- Y-axis: Request count
-- Save as: `/tmp/gh-aw/python/charts/firewall_requests_trends.png`
-
-**Chart 2: Top Blocked Domains Frequency**
-- Horizontal bar chart showing:
- - Top 10-15 most frequently blocked domains
- - Total block count for each domain
- - Color-coded by domain category if applicable
-- X-axis: Block count
-- Y-axis: Domain names
-- Save as: `/tmp/gh-aw/python/charts/blocked_domains_frequency.png`
-
-**Chart Quality Requirements**:
-- DPI: 300 minimum
-- Figure size: 12x7 inches for better readability
-- Use seaborn styling with a professional color palette
-- Include grid lines for easier reading
-- Clear, large labels and legend
-- Title with context (e.g., "Firewall Activity - Last 30 Days")
-- Annotations for significant spikes or patterns
-
-**Phase 4: Upload Charts**
-
-1. Call the `upload_asset` safe-output tool for each chart using absolute paths:
- - `/tmp/gh-aw/python/charts/firewall_trends.png`
- - `/tmp/gh-aw/python/charts/blocked_domains.png`
-2. Record the returned asset URLs
-
-**Phase 5: Embed Charts in Discussion**
-
-Include the charts in your firewall report with this structure:
-
-```markdown
-### 📈 Firewall Activity Trends
-
-### Request Patterns
-
-
-
-[Brief 2-3 sentence analysis of firewall activity trends, noting increases in blocked traffic or changes in patterns]
-
-### Top Blocked Domains
-
-
-
-[Brief 2-3 sentence analysis of frequently blocked domains, identifying potential security concerns or overly restrictive rules]
-```
-
-### Python Implementation Notes
-
-- Use pandas for data manipulation and date handling
-- Use matplotlib.pyplot and seaborn for visualization
-- Set appropriate date formatters for x-axis labels
-- Use `plt.xticks(rotation=45)` for readable date labels
-- Apply `plt.tight_layout()` before saving
-- Handle cases where data might be sparse or missing
-
-### Error Handling
-
-If insufficient data is available (less than 7 days):
-- Generate the charts with available data
-- Add a note in the analysis mentioning the limited data range
-- Consider using a bar chart instead of line chart for very sparse data
-
----
-
----
-
-## Objective
-
-Generate a comprehensive daily report of all rejected domains across all agentic workflows that use the firewall feature. This helps identify:
-- Which domains are being blocked
-- Patterns in blocked traffic
-- Potential issues with network permissions
-- Security insights from blocked requests
-
-## Instructions
-
-### MCP Servers are Pre-loaded
-
-**IMPORTANT**: The MCP servers configured in this workflow (including `gh-aw` with tools like `logs` and `audit`) are automatically loaded and available at agent startup. You do NOT need to:
-- Use the inspector tool to discover MCP servers
-- Run any external tools to check available MCP servers
-- Verify or list MCP servers before using them
-
-Simply call the MCP tools directly as described in the steps below. If you want to know what tools are available, you can list them using your built-in tool listing capability.
-
-### Step 0: Fresh Analysis - No Caching
-
-**ALWAYS PERFORM FRESH ANALYSIS**: This report must always use fresh data from the audit tool.
-
-**DO NOT**:
-- Skip analysis based on cached results
-- Reuse aggregated statistics from previous runs
-- Check for or use any cached run IDs, counts, or domain lists
-
-**ALWAYS**:
-- Collect all workflow runs fresh using the `logs` tool
-- Fetch complete firewall data from the `audit` tool for each run
-- Compute all statistics fresh (blocked counts, allowed counts, domain lists)
-
-This ensures accurate, up-to-date reporting for every run of this workflow.
-
-### Step 1: Collect Recent Firewall-Enabled Workflow Runs
-
-Use the `logs` tool from the agentic-workflows MCP server to efficiently collect workflow runs that have firewall enabled (see `workflow_runs_analyzed` in scratchpad/metrics-glossary.md - Scope: Last 7 days):
-
-**Using the logs tool:**
-Call the `logs` tool with the following parameters:
-- `firewall`: true (boolean - to filter only runs with firewall enabled)
-- `start_date`: "-7d" (to get runs from the past 7 days)
-- `count`: 100 (to get up to 100 matching runs)
-
-The tool will:
-1. Filter runs based on the `steps.firewall` field in `aw_info.json` (e.g., "squid" when enabled)
-2. Return only runs where firewall was enabled
-3. Limit to runs from the past 7 days
-4. Return up to 100 matching runs
-
-**Tool call example:**
-```json
-{
- "firewall": true,
- "start_date": "-7d",
- "count": 100
-}
-```
-
-### Step 1.5: Early Exit if No Data
-
-**IMPORTANT**: If Step 1 returns zero workflow runs (no firewall-enabled workflows ran in the past 7 days):
-
-1. **Do NOT create a discussion or report**
-2. **Exit early** with a brief log message: "No firewall-enabled workflow runs found in the past 7 days. Exiting without creating a report."
-3. **Stop processing** - do not proceed to Step 2 or any subsequent steps
-
-This prevents creating empty or meaningless reports when there's no data to analyze.
-
-### Step 2: Analyze Firewall Logs from Collected Runs
-
-For each run collected in Step 1:
-1. Use the `audit` tool from the agentic-workflows MCP server to get detailed firewall information
-2. Store the run ID, workflow name, and timestamp for tracking
-
-**Using the audit tool:**
-Call the `audit` tool with the run_id parameter for each run from Step 1.
-
-**Tool call example:**
-```json
-{
- "run_id": 12345678
-}
-```
-
-The audit tool returns structured firewall analysis data including:
-- Total requests, allowed requests, blocked requests
-- Lists of allowed and blocked domains
-- Request statistics per domain
-- **Policy rule attribution** (when `policy-manifest.json` and `audit.jsonl` artifacts are present)
-
-**Example of extracting firewall data from audit result:**
-```javascript
-// From the audit tool result, access:
-result.firewall_analysis.blocked_domains // Array of blocked domain names
-result.firewall_analysis.allowed_domains // Array of allowed domain names
-result.firewall_analysis.total_requests // Total number of network requests
-result.firewall_analysis.blocked_requests // Number of blocked requests
-
-// Policy rule attribution (enriched data — may be null if artifacts are absent):
-result.policy_analysis.policy_summary // e.g., "12 rules, SSL Bump disabled, DLP disabled"
-result.policy_analysis.rule_hits // Array of {rule: {id, action, description, ...}, hits: N}
-result.policy_analysis.denied_requests // Array of {ts, host, status, rule_id, action, reason}
-result.policy_analysis.total_requests // Total enriched request count
-result.policy_analysis.allowed_count // Allowed requests (rule-attributed)
-result.policy_analysis.denied_count // Denied requests (rule-attributed)
-result.policy_analysis.unique_domains // Unique domain count
-```
-
-**Important:** Do NOT manually download and parse firewall log files. Always use the `audit` tool which provides structured firewall analysis data.
-
-### Step 3: Parse and Analyze Firewall Logs
-
-Use the JSON output from the `audit` tool to extract firewall information.
-
-**Basic firewall analysis** — The `firewall_analysis` field in the audit JSON contains:
-- `total_requests` - Total number of network requests
-- `allowed_requests` - Count of allowed requests
-- `blocked_requests` - Count of blocked requests
-- `allowed_domains` - Array of unique allowed domains
-- `blocked_domains` - Array of unique blocked domains
-- `requests_by_domain` - Object mapping domains to request statistics (allowed/blocked counts)
-
-**Policy rule attribution** — The `policy_analysis` field (when present) contains enriched data that attributes each request to a specific firewall policy rule:
-- `policy_summary` - Human-readable summary (e.g., "12 rules, SSL Bump disabled, DLP disabled")
-- `rule_hits` - Array of objects: `{rule: {id, order, action, aclName, protocol, domains, description}, hits: N}` — how many requests each rule handled
-- `denied_requests` - Array of objects: `{ts, host, status, rule_id, action, reason}` — every denied request with its matching rule and reason
-- `total_requests` - Total enriched request count
-- `allowed_count` - Allowed requests count (rule-attributed)
-- `denied_count` - Denied requests count (rule-attributed)
-- `unique_domains` - Unique domain count
-
-**Note:** `policy_analysis` is only present when the workflow run produced `policy-manifest.json` and `audit.jsonl` artifacts. If absent, fall back to `firewall_analysis` for basic domain-count data.
-
-**Example jq filter for aggregating blocked domains:**
-```bash
-# Get only blocked domains across multiple runs
-gh aw audit --json | jq -r '.firewall_analysis.blocked_domains[]? // empty'
-
-# Get blocked domain statistics with counts
-gh aw audit --json | jq -r '
- .firewall_analysis.requests_by_domain // {} |
- to_entries[] |
- select(.value.blocked > 0) |
- "\(.key): \(.value.blocked) blocked, \(.value.allowed) allowed"
-'
-
-# Get policy rule hit counts (when policy_analysis is available)
-gh aw audit --json | jq -r '
- .policy_analysis.rule_hits // [] |
- .[] | select(.hits > 0) |
- "\(.rule.id) (\(.rule.action)): \(.hits) hits"
-'
-
-# Get denied requests with rule attribution
-gh aw audit --json | jq -r '
- .policy_analysis.denied_requests // [] |
- .[] | "\(.host) → \(.rule_id): \(.reason)"
-'
-```
-
-For each workflow run with firewall data (see standardized metric names in scratchpad/metrics-glossary.md):
-1. Extract the firewall analysis from the audit JSON output
-2. Track the following metrics per workflow:
- - Total requests (`firewall_requests_total`)
- - Allowed requests count (`firewall_requests_allowed`)
- - Blocked requests count (`firewall_requests_blocked`)
- - List of unique blocked domains (`firewall_domains_blocked`)
- - Domain-level statistics (from `requests_by_domain`)
-3. If `policy_analysis` is present, also track:
- - Policy rule hit counts (which rules are handling traffic)
- - Denied requests with rule attribution (which rule denied each request and why)
- - Policy summary (rules count, SSL Bump/DLP status)
-
-### Step 4: Aggregate Results
-
-Combine data from all workflows (using standardized metric names):
-1. Create a master list of all blocked domains across all workflows
-2. Track how many times each domain was blocked
-3. Track which workflows blocked which domains
-4. Calculate overall statistics:
- - Total workflows analyzed (`workflow_runs_analyzed` - Scope: Last 7 days)
- - Total runs analyzed
- - Total blocked domains (`firewall_domains_blocked`) - unique count
- - Total blocked requests (`firewall_requests_blocked`)
-
-**Policy rule attribution aggregation** (when `policy_analysis` data is available):
-5. Aggregate policy rule hit counts across all runs:
- - Build a cross-run rule hit table: rule ID → total hits across all runs
- - Identify the most active allow rules and deny rules
-6. Aggregate denied requests with rule attribution:
- - Collect all denied requests across runs with their matching rule and reason
- - Group by rule ID to show which deny rules are doing the most work
- - Group by domain to show which domains trigger which deny rules
-7. Track policy configuration across runs:
- - Note any runs with SSL Bump or DLP enabled
- - Note any differences in policy rule counts between runs
-
-### Step 5: Generate Report
-
-Create a comprehensive markdown report following the formatting guidelines above. Structure your report as follows:
-
-#### Section 1: Executive Summary (Always Visible)
-A brief 1-2 paragraph overview including:
-- Date of report (today's date)
-- Total workflows analyzed (`workflow_runs_analyzed`)
-- Total runs analyzed
-- Overall firewall activity snapshot (key highlights, trends, concerns)
-
-#### Section 2: Key Metrics (Always Visible)
-Present the core statistics:
-- Total network requests monitored (`firewall_requests_total`)
- - ✅ **Allowed** (`firewall_requests_allowed`): Count of successful requests
- - 🚫 **Blocked** (`firewall_requests_blocked`): Count of blocked requests
-- **Block rate**: Percentage of blocked requests (blocked / total * 100)
-- Total unique blocked domains (`firewall_domains_blocked`)
-
-> **Terminology Note**:
-> - **Allowed requests** = Requests that successfully reached their destination
-> - **Blocked requests** = Requests that were prevented by the firewall
-> - A 0% block rate with listed blocked domains indicates domains that would
-> be blocked if accessed, but weren't actually accessed during this period
-
-#### Section 3: Top Blocked Domains (Always Visible)
-A table showing the most frequently blocked domains:
-- Domain name
-- Number of times blocked
-- Workflows that blocked it
-- Domain category (Development Services, Social Media, Analytics/Tracking, CDN, Other)
-
-Sort by frequency (most blocked first), show top 20.
-
-#### Section 4: Policy Rule Attribution (Always Visible — when data available)
-
-**Include this section when `policy_analysis` data was available for at least one run.**
-
-This section provides rule-level insights that go beyond simple domain counts, showing *which policy rules* are handling traffic and *why* specific requests were denied.
-
-**4a. Policy Configuration**
-
-Show the policy summary from the most recent run:
-- Number of rules, SSL Bump status, DLP status
-- Example: "📋 Policy: 12 rules, SSL Bump disabled, DLP disabled"
-
-**4b. Policy Rule Hit Table**
-
-Show aggregated rule hit counts across all analyzed runs:
-
-```markdown
-| Rule | Action | Description | Total Hits |
-|------|--------|-------------|------------|
-| allow-github | 🟢 allow | Allow GitHub domains | 523 |
-| allow-npm | 🟢 allow | Allow npm registry | 187 |
-| deny-blocked-plain | 🔴 deny | Deny all other HTTP/HTTPS | 12 |
-| deny-default | 🔴 deny | Default deny | 3 |
-```
-
-- Sort by hits (descending)
-- Include all rules that had at least 1 hit
-- Use 🟢 for allow rules and 🔴 for deny rules in the Action column
-
-**4c. Denied Requests with Rule Attribution**
-
-Show denied requests grouped by rule, with domain details:
-
-```markdown
-| Domain | Deny Rule | Reason | Occurrences |
-|--------|-----------|--------|-------------|
-| evil.com:443 | deny-blocked-plain | Domain not in allowlist | 5 |
-| tracker.io:443 | deny-blocked-plain | Domain not in allowlist | 3 |
-| unknown.host:80 | deny-default | Default deny | 1 |
-```
-
-- Group by domain + rule combination
-- Sort by occurrences (descending)
-- Show top 30 entries; wrap the full list in `` if more than 30
-
-**4d. Rule Effectiveness Summary**
-
-Provide a brief analysis:
-- Which deny rules are doing the most work (catching the most unauthorized traffic)
-- Which allow rules handle the most traffic (busiest legitimate pathways)
-- Any rules with zero hits that could be removed or indicate unused policy entries
-- Any `(implicit-deny)` attributions that indicate gaps in the policy (traffic denied without matching any explicit rule)
-
-#### Section 5: Detailed Request Patterns (In `` Tags)
-**IMPORTANT**: Wrap this entire section in a collapsible `` block:
-
-```markdown
-
-View Detailed Request Patterns by Workflow
-
-For each workflow that had blocked domains, provide a detailed breakdown:
-
-#### Workflow: [workflow-name] (X runs analyzed)
-
-| Domain | Blocked Count | Allowed Count | Block Rate | Category |
-|--------|---------------|---------------|------------|----------|
-| example.com | 15 | 5 | 75% | Social Media |
-| api.example.org | 10 | 0 | 100% | Development |
-
-- Total blocked requests: [count]
-- Total unique blocked domains: [count]
-- Most frequently blocked domain: [domain]
-
-[Repeat for all workflows with blocked domains]
-
-
-```
-
-#### Section 6: Complete Blocked Domains List (In `` Tags)
-**IMPORTANT**: Wrap this entire section in a collapsible `` block:
-
-```markdown
-
-View Complete Blocked Domains List
-
-An alphabetically sorted list of all unique blocked domains:
-
-| Domain | Total Blocks | First Seen | Workflows |
-|--------|--------------|------------|-----------|
-| [domain] | [count] | [date] | [workflow-list] |
-| ... | ... | ... | ... |
-
-
-```
-
-#### Section 7: Security Recommendations (Always Visible)
-Based on the analysis, provide actionable insights:
-- Domains that appear to be legitimate services that should be allowlisted
-- Potential security concerns (e.g., suspicious domains)
-- Suggestions for network permission improvements
-- Workflows that might need their network permissions updated
-- Policy rule suggestions (e.g., rules with zero hits that could be removed, domains that should be added to allow rules)
-
-### Step 6: Create Discussion
-
-Create a new GitHub discussion with:
-- **Title**: "Daily Firewall Report - [Today's Date]"
-- **Category**: audits
-- **Body**: The complete markdown report following the formatting guidelines and structure defined in Step 5
-
-Ensure the discussion body:
-- Uses h3 (###) for main section headers
-- Uses h4 (####) for subsection headers
-- Wraps detailed data (per-workflow breakdowns, complete domain list) in `` tags
-- Keeps critical information visible (summary, key metrics, top domains, recommendations)
-
-## Notes
-
-- **Early exit**: If no firewall-enabled workflow runs are found in the past 7 days, exit early without creating a report (see Step 1.5)
-- Include timestamps and run URLs for traceability
-- Use tables and formatting for better readability
-- Add emojis to make the report more engaging (🔥 for firewall, 🚫 for blocked, ✅ for allowed)
-
-## Expected Output
-
-A GitHub discussion in the "audits" category containing a comprehensive daily firewall analysis report.
-
-{{#import shared/noop-reminder.md}}
diff --git a/.github/workflows/daily-integrity-analysis.lock.yml b/.github/workflows/daily-security-observability.lock.yml
similarity index 94%
rename from .github/workflows/daily-integrity-analysis.lock.yml
rename to .github/workflows/daily-security-observability.lock.yml
index 1db06626398..7d8ed110338 100644
--- a/.github/workflows/daily-integrity-analysis.lock.yml
+++ b/.github/workflows/daily-security-observability.lock.yml
@@ -1,4 +1,4 @@
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"fd6f03b68e4944b9c8268aaa0577cf0b5e632f8a22dba48bfe64e9c7907107be","strict":true,"agent_id":"copilot"}
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"dc3a6e50e93f0560950620e72f8d5e183211e734fe1fa47c06fcfa2093c45fd0","strict":true,"agent_id":"copilot"}
# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-go","sha":"4a3601121dd01d1626a1e23e37211e3254c1c06c","version":"v6.4.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/setup-python","sha":"a309ff8b426b58ec0e2a45f0f869d46889d02405","version":"v6.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"docker/build-push-action","sha":"bcafcacb16a39f128d818304e6c9c0c18556b85f","version":"v7.1.0"},{"repo":"docker/setup-buildx-action","sha":"4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd","version":"v4"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28","digest":"sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28","digest":"sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28","digest":"sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.0"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
# ___ _ _
# / _ \ | | (_)
@@ -22,7 +22,7 @@
#
# For more information: https://github.github.com/gh-aw/introduction/overview/
#
-# Daily analysis of DIFC integrity-filtered events with statistical charts and actionable tuning recommendations
+# Daily unified security observability report combining firewall traffic analysis and DIFC integrity-filtered event analysis
#
# Resolved workflow manifest:
# Imports:
@@ -31,6 +31,8 @@
# - shared/python-dataviz.md
# - shared/reporting.md
# - shared/daily-audit-base.md
+# - shared/trending-charts-simple.md
+# - shared/daily-audit-charts.md
#
# Secrets used:
# - COPILOT_GITHUB_TOKEN
@@ -61,10 +63,10 @@
# - ghcr.io/github/github-mcp-server:v1.0.3
# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f
-name: "Daily DIFC Integrity-Filtered Events Analyzer"
+name: "Daily Security Observability Report"
"on":
schedule:
- - cron: "54 19 * * *"
+ - cron: "26 10 * * *"
# Friendly format: daily (scattered)
workflow_dispatch:
inputs:
@@ -79,7 +81,7 @@ permissions: {}
concurrency:
group: "gh-aw-${{ github.workflow }}"
-run-name: "Daily DIFC Integrity-Filtered Events Analyzer"
+run-name: "Daily Security Observability Report"
env:
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.GH_AW_OTEL_ENDPOINT }}
@@ -125,7 +127,7 @@ jobs:
GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }}
GH_AW_INFO_VERSION: "1.0.36"
GH_AW_INFO_AGENT_VERSION: "1.0.36"
- GH_AW_INFO_WORKFLOW_NAME: "Daily DIFC Integrity-Filtered Events Analyzer"
+ GH_AW_INFO_WORKFLOW_NAME: "Daily Security Observability Report"
GH_AW_INFO_EXPERIMENTAL: "false"
GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
GH_AW_INFO_STAGED: "false"
@@ -172,7 +174,7 @@ jobs:
id: check-lock-file
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
env:
- GH_AW_WORKFLOW_FILE: "daily-integrity-analysis.lock.yml"
+ GH_AW_WORKFLOW_FILE: "daily-security-observability.lock.yml"
GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}"
with:
script: |
@@ -196,24 +198,24 @@ jobs:
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
{
- cat << 'GH_AW_PROMPT_3e2cf01d558d33ba_EOF'
+ cat << 'GH_AW_PROMPT_dc01b53441f2a085_EOF'
- GH_AW_PROMPT_3e2cf01d558d33ba_EOF
+ GH_AW_PROMPT_dc01b53441f2a085_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/agentic_workflows_guide.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_3e2cf01d558d33ba_EOF'
+ cat << 'GH_AW_PROMPT_dc01b53441f2a085_EOF'
Tools: create_discussion, upload_asset(max:5), missing_tool, missing_data, noop
upload_asset: provide a file path; returns a URL; assets are published after the workflow completes (safeoutputs).
- GH_AW_PROMPT_3e2cf01d558d33ba_EOF
+ GH_AW_PROMPT_dc01b53441f2a085_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_3e2cf01d558d33ba_EOF'
+ cat << 'GH_AW_PROMPT_dc01b53441f2a085_EOF'
The following GitHub context information is available for this workflow:
{{#if __GH_AW_GITHUB_ACTOR__ }}
@@ -242,15 +244,17 @@ jobs:
{{/if}}
- GH_AW_PROMPT_3e2cf01d558d33ba_EOF
+ GH_AW_PROMPT_dc01b53441f2a085_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_3e2cf01d558d33ba_EOF'
+ cat << 'GH_AW_PROMPT_dc01b53441f2a085_EOF'
{{#runtime-import .github/workflows/shared/python-dataviz.md}}
+ {{#runtime-import .github/workflows/shared/trending-charts-simple.md}}
{{#runtime-import .github/workflows/shared/reporting.md}}
{{#runtime-import .github/workflows/shared/observability-otlp.md}}
- {{#runtime-import .github/workflows/daily-integrity-analysis.md}}
- GH_AW_PROMPT_3e2cf01d558d33ba_EOF
+ {{#runtime-import .github/workflows/shared/noop-reminder.md}}
+ {{#runtime-import .github/workflows/daily-security-observability.md}}
+ GH_AW_PROMPT_dc01b53441f2a085_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
@@ -337,6 +341,7 @@ jobs:
discussions: read
issues: read
pull-requests: read
+ security-events: read
concurrency:
group: "gh-aw-copilot-${{ github.workflow }}"
env:
@@ -345,7 +350,7 @@ jobs:
GH_AW_ASSETS_BRANCH: "assets/${{ github.workflow }}"
GH_AW_ASSETS_MAX_SIZE_KB: 10240
GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
- GH_AW_WORKFLOW_ID_SANITIZED: dailyintegrityanalysis
+ GH_AW_WORKFLOW_ID_SANITIZED: dailysecurityobservability
outputs:
agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }}
checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }}
@@ -441,6 +446,18 @@ jobs:
/tmp/gh-aw/python/*.py
/tmp/gh-aw/python/data/*
retention-days: 30
+ - name: Setup Python environment
+ run: "mkdir -p /tmp/gh-aw/python/{data,charts,artifacts}\n# Create a virtual environment for proper package isolation (avoids --break-system-packages)\nif [ ! -d /tmp/gh-aw/venv ]; then\n python3 -m venv /tmp/gh-aw/venv\nfi\necho \"/tmp/gh-aw/venv/bin\" >> \"$GITHUB_PATH\"\n/tmp/gh-aw/venv/bin/pip install --quiet numpy pandas matplotlib seaborn scipy\n"
+ - if: always()
+ name: Upload source files and data
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ if-no-files-found: warn
+ name: trending-source-and-data
+ path: |
+ /tmp/gh-aw/python/*.py
+ /tmp/gh-aw/python/data/*
+ retention-days: 30
- env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
name: Install gh-aw CLI
@@ -562,15 +579,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_b01524064dfe8529_EOF
- {"create_discussion":{"category":"audits","close_older_discussions":true,"expires":72,"fallback_to_issue":true,"max":1,"title_prefix":"[integrity] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"upload_asset":{"allowed-exts":[".png",".jpg",".jpeg",".svg"],"branch":"assets/${GITHUB_WORKFLOW}","max":5,"max-size":10240}}
- GH_AW_SAFE_OUTPUTS_CONFIG_b01524064dfe8529_EOF
+ cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << GH_AW_SAFE_OUTPUTS_CONFIG_7a041f69348294a8_EOF
+ {"create_discussion":{"category":"audits","close_older_discussions":true,"expires":72,"fallback_to_issue":true,"max":1,"title_prefix":"[security-observability] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"upload_asset":{"allowed-exts":[".png",".jpg",".jpeg",".svg"],"branch":"assets/${GITHUB_WORKFLOW}","max":5,"max-size":10240}}
+ GH_AW_SAFE_OUTPUTS_CONFIG_7a041f69348294a8_EOF
- name: Write Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
{
"description_suffixes": {
- "create_discussion": " CONSTRAINTS: Maximum 1 discussion(s) can be created. Title will be prefixed with \"[integrity] \". Discussions will be created in category \"audits\".",
+ "create_discussion": " CONSTRAINTS: Maximum 1 discussion(s) can be created. Title will be prefixed with \"[security-observability] \". Discussions will be created in category \"audits\".",
"upload_asset": " CONSTRAINTS: Maximum 5 asset(s) can be uploaded. Maximum file size: 10240KB. Allowed file extensions: [.png .jpg .jpeg .svg]."
},
"repo_params": {},
@@ -772,7 +789,7 @@ jobs:
mkdir -p /home/runner/.copilot
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_71aabecb53ff8fbf_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_b755bf9a7f82cea6_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"agenticworkflows": {
@@ -801,7 +818,7 @@ jobs:
"GITHUB_HOST": "\${GITHUB_SERVER_URL}",
"GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}",
"GITHUB_READ_ONLY": "1",
- "GITHUB_TOOLSETS": "context,repos,issues,pull_requests"
+ "GITHUB_TOOLSETS": "all"
},
"guard-policies": {
"allow-only": {
@@ -838,7 +855,7 @@ jobs:
}
}
}
- GH_AW_MCP_CONFIG_71aabecb53ff8fbf_EOF
+ GH_AW_MCP_CONFIG_b755bf9a7f82cea6_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
@@ -859,7 +876,7 @@ jobs:
- name: Execute GitHub Copilot CLI
id: agentic_execution
# Copilot CLI tool arguments (sorted):
- timeout-minutes: 30
+ timeout-minutes: 60
run: |
set -o pipefail
touch /tmp/gh-aw/agent-step-summary.md
@@ -1090,7 +1107,7 @@ jobs:
discussions: write
issues: write
concurrency:
- group: "gh-aw-conclusion-daily-integrity-analysis"
+ group: "gh-aw-conclusion-daily-security-observability"
cancel-in-progress: false
outputs:
incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }}
@@ -1132,8 +1149,8 @@ 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 DIFC Integrity-Filtered Events Analyzer"
- GH_AW_TRACKER_ID: "daily-integrity-analysis"
+ GH_AW_WORKFLOW_NAME: "Daily Security Observability Report"
+ GH_AW_TRACKER_ID: "daily-security-observability"
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"
@@ -1149,8 +1166,8 @@ jobs:
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
- GH_AW_WORKFLOW_NAME: "Daily DIFC Integrity-Filtered Events Analyzer"
- GH_AW_TRACKER_ID: "daily-integrity-analysis"
+ GH_AW_WORKFLOW_NAME: "Daily Security Observability Report"
+ GH_AW_TRACKER_ID: "daily-security-observability"
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 }}
@@ -1167,8 +1184,8 @@ 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 DIFC Integrity-Filtered Events Analyzer"
- GH_AW_TRACKER_ID: "daily-integrity-analysis"
+ GH_AW_WORKFLOW_NAME: "Daily Security Observability Report"
+ GH_AW_TRACKER_ID: "daily-security-observability"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
@@ -1182,8 +1199,8 @@ 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 DIFC Integrity-Filtered Events Analyzer"
- GH_AW_TRACKER_ID: "daily-integrity-analysis"
+ GH_AW_WORKFLOW_NAME: "Daily Security Observability Report"
+ GH_AW_TRACKER_ID: "daily-security-observability"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
@@ -1197,11 +1214,11 @@ jobs:
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
- GH_AW_WORKFLOW_NAME: "Daily DIFC Integrity-Filtered Events Analyzer"
- GH_AW_TRACKER_ID: "daily-integrity-analysis"
+ GH_AW_WORKFLOW_NAME: "Daily Security Observability Report"
+ GH_AW_TRACKER_ID: "daily-security-observability"
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-integrity-analysis"
+ GH_AW_WORKFLOW_ID: "daily-security-observability"
GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "12"
GH_AW_ENGINE_ID: "copilot"
GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }}
@@ -1216,7 +1233,7 @@ jobs:
GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }}
GH_AW_GROUP_REPORTS: "false"
GH_AW_FAILURE_REPORT_AS_ISSUE: "true"
- GH_AW_TIMEOUT_MINUTES: "30"
+ GH_AW_TIMEOUT_MINUTES: "60"
GH_AW_CACHE_MEMORY_ENABLED: "true"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
@@ -1318,8 +1335,8 @@ jobs:
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
env:
- WORKFLOW_NAME: "Daily DIFC Integrity-Filtered Events Analyzer"
- WORKFLOW_DESCRIPTION: "Daily analysis of DIFC integrity-filtered events with statistical charts and actionable tuning recommendations"
+ WORKFLOW_NAME: "Daily Security Observability Report"
+ WORKFLOW_DESCRIPTION: "Daily unified security observability report combining firewall traffic analysis and DIFC integrity-filtered event analysis"
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
with:
script: |
@@ -1412,16 +1429,16 @@ jobs:
issues: write
timeout-minutes: 15
env:
- GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/daily-integrity-analysis"
+ GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/daily-security-observability"
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.36"
- GH_AW_TRACKER_ID: "daily-integrity-analysis"
- GH_AW_WORKFLOW_ID: "daily-integrity-analysis"
- GH_AW_WORKFLOW_NAME: "Daily DIFC Integrity-Filtered Events Analyzer"
+ GH_AW_TRACKER_ID: "daily-security-observability"
+ GH_AW_WORKFLOW_ID: "daily-security-observability"
+ GH_AW_WORKFLOW_NAME: "Daily Security Observability Report"
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 }}
@@ -1477,7 +1494,7 @@ jobs:
GH_AW_ALLOWED_DOMAINS: "*.pythonhosted.org,anaconda.org,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,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,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,files.pythonhosted.org,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,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,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_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":72,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[integrity] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"upload_asset\":{\"allowed-exts\":[\".png\",\".jpg\",\".jpeg\",\".svg\"],\"branch\":\"assets/${{ github.workflow }}\",\"max\":5,\"max-size\":10240}}"
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":72,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[security-observability] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"upload_asset\":{\"allowed-exts\":[\".png\",\".jpg\",\".jpeg\",\".svg\"],\"branch\":\"assets/${{ github.workflow }}\",\"max\":5,\"max-size\":10240}}"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
@@ -1507,7 +1524,7 @@ jobs:
permissions:
contents: read
env:
- GH_AW_WORKFLOW_ID_SANITIZED: dailyintegrityanalysis
+ GH_AW_WORKFLOW_ID_SANITIZED: dailysecurityobservability
steps:
- name: Checkout actions folder
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -1624,8 +1641,8 @@ jobs:
GH_AW_ASSETS_BRANCH: "assets/${{ github.workflow }}"
GH_AW_ASSETS_MAX_SIZE_KB: 10240
GH_AW_ASSETS_ALLOWED_EXTS: ".png,.jpg,.jpeg,.svg"
- GH_AW_WORKFLOW_NAME: "Daily DIFC Integrity-Filtered Events Analyzer"
- GH_AW_TRACKER_ID: "daily-integrity-analysis"
+ GH_AW_WORKFLOW_NAME: "Daily Security Observability Report"
+ GH_AW_TRACKER_ID: "daily-security-observability"
GH_AW_ENGINE_ID: "copilot"
GH_AW_ENGINE_VERSION: "1.0.36"
GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
diff --git a/.github/workflows/daily-integrity-analysis.md b/.github/workflows/daily-security-observability.md
similarity index 58%
rename from .github/workflows/daily-integrity-analysis.md
rename to .github/workflows/daily-security-observability.md
index d9579230bf3..10068d9cb71 100644
--- a/.github/workflows/daily-integrity-analysis.md
+++ b/.github/workflows/daily-security-observability.md
@@ -1,7 +1,8 @@
---
-description: Daily analysis of DIFC integrity-filtered events with statistical charts and actionable tuning recommendations
+description: Daily unified security observability report combining firewall traffic analysis and DIFC integrity-filtered event analysis
on:
schedule:
+ # Every day at 10am UTC
- cron: daily
workflow_dispatch:
@@ -11,8 +12,9 @@ permissions:
issues: read
pull-requests: read
discussions: read
+ security-events: read
-tracker-id: daily-integrity-analysis
+tracker-id: daily-security-observability
engine: copilot
steps:
@@ -46,15 +48,24 @@ steps:
tools:
mount-as-clis: true
agentic-workflows:
+ github:
+ toolsets:
+ - all
bash:
- "*"
+ edit:
-timeout-minutes: 30
+safe-outputs:
+ upload-asset:
+ max: 5
+ allowed-exts: [.png, .jpg, .jpeg, .svg]
+
+timeout-minutes: 60
imports:
- - uses: shared/daily-audit-base.md
+ - uses: shared/daily-audit-charts.md
with:
- title-prefix: "[integrity] "
+ title-prefix: "[security-observability] "
- shared/python-dataviz.md
features:
@@ -62,28 +73,105 @@ features:
---
{{#runtime-import? .github/shared-instructions.md}}
-# Daily DIFC Integrity-Filtered Events Analyzer
+# Daily Security Observability Report
+
+You are a security observability analyst. Your job is to produce a unified daily security intelligence report that combines two signals:
-You are an integrity-system analyst. Your job is to analyze DIFC (Data Integrity and Flow Control) filtered events collected from agentic workflow runs, produce statistical charts that reveal patterns, and provide actionable tuning recommendations.
+1. **Firewall traffic analysis** — which domains and requests were allowed or blocked across all agentic workflow runs
+2. **DIFC integrity-filtered event analysis** — which tool calls were blocked by the Data Integrity and Flow Control system, with statistical charts and actionable tuning recommendations
+
+Both datasets cover the **last 7 days** and share the cache-memory path `/tmp/gh-aw/cache-memory/security-observability/`.
## Context
- **Repository**: ${{ github.repository }}
- **Run ID**: ${{ github.run_id }}
-- **Data file**: `/tmp/gh-aw/integrity/filtered-logs.json` (pre-downloaded runs with DIFC integrity-filtered events)
- **Analysis window**: Last 7 days
-## Step 0: Check for Data
+---
+
+## Phase 1: Collect Firewall-Enabled Workflow Runs
+
+### Step 1.1: Collect Recent Firewall-Enabled Workflow Runs
+
+**ALWAYS PERFORM FRESH ANALYSIS**: This report must always use fresh data from the audit tool. Do NOT skip analysis based on cached results or reuse aggregated statistics from previous runs.
+
+Use the `logs` tool from the agentic-workflows MCP server to collect workflow runs that have firewall enabled:
+
+**Tool call:**
+```json
+{
+ "firewall": true,
+ "start_date": "-7d",
+ "count": 100
+}
+```
+
+### Step 1.2: Early Exit if No Firewall Data
+
+If Step 1.1 returns zero workflow runs, note this in the final report as "No firewall-enabled workflow runs found in the past 7 days." and proceed directly to Phase 3 (DIFC analysis). Do not skip the final report.
+
+---
+
+## Phase 2: Analyze Firewall Logs
+
+### Step 2.1: Fetch Firewall Audit Data
+
+For each run collected in Phase 1, call the `audit` tool with the run_id to get detailed firewall information:
+
+```json
+{
+ "run_id": 12345678
+}
+```
+
+The audit tool returns:
+- `firewall_analysis.blocked_domains` — blocked domain names
+- `firewall_analysis.allowed_domains` — allowed domain names
+- `firewall_analysis.total_requests` / `blocked_requests` / `allowed_requests`
+- `firewall_analysis.requests_by_domain` — per-domain statistics
+- `policy_analysis` (when present) — rule-level attribution with `rule_hits`, `denied_requests`, `policy_summary`
+
+**Important:** Do NOT manually download and parse firewall log files. Always use the `audit` tool.
+
+### Step 2.2: Aggregate Firewall Results
+
+Combine data from all runs:
+1. Build a master list of all blocked domains with frequency counts and which workflows blocked them
+2. Calculate overall statistics:
+ - Total workflows analyzed (`workflow_runs_analyzed`)
+ - Total blocked domains (`firewall_domains_blocked`) — unique count
+ - Total blocked requests (`firewall_requests_blocked`)
+ - Total allowed requests (`firewall_requests_allowed`)
+3. If `policy_analysis` is present, aggregate rule hit counts and denied requests with rule attribution across all runs
-Read `/tmp/gh-aw/integrity/filtered-logs.json`. If the array is empty (no runs found in the last 7 days), call `noop` with the message "No DIFC integrity-filtered events found in the last 7 days." and stop.
+### Step 2.3: Generate Firewall Trend Charts
-## Step 1: Parse and Bucketize Events
+Create CSV files in `/tmp/gh-aw/python/data/` and generate exactly **2 firewall charts**:
-The JSON file is an array of workflow run objects. Each run object contains `databaseId` (the numeric run ID) and `workflowName`. Use the `audit` tool from the agentic-workflows MCP server to get the detailed gateway data for each run. Then write a Python script to bucketize events.
+**Chart 1: Firewall Request Trends**
+- Stacked area or multi-line chart: allowed requests (green) vs blocked requests (red) over the last 30 days
+- Save as: `/tmp/gh-aw/python/charts/firewall_requests_trends.png`
-### 1.1 Fetch Detailed Gateway Data
+**Chart 2: Top Blocked Domains Frequency**
+- Horizontal bar chart: top 10–15 most frequently blocked domains with block counts
+- Save as: `/tmp/gh-aw/python/charts/blocked_domains_frequency.png`
-1. Read `/tmp/gh-aw/integrity/filtered-logs.json` and extract all run IDs by iterating over the array and collecting each entry's `databaseId` field.
+**Chart quality**: DPI 300 minimum, 12×7 inches, seaborn styling, clear labels.
+
+Upload both charts using `upload_asset` and record the returned URLs.
+
+---
+
+## Phase 3: Collect DIFC Integrity-Filtered Events
+
+### Step 3.1: Check for DIFC Data
+
+Read `/tmp/gh-aw/integrity/filtered-logs.json`. If the array is empty (no runs found in the last 7 days), note "No DIFC integrity-filtered events found in the last 7 days." and proceed directly to Phase 5 (combined report).
+
+### Step 3.2: Fetch Detailed DIFC Gateway Data
+
+1. Read `/tmp/gh-aw/integrity/filtered-logs.json` and extract all run IDs from each entry's `databaseId` field.
2. For each run ID, call the `audit` tool to get its detailed DIFC filtered events:
```json
@@ -102,10 +190,10 @@ The audit result contains `gateway_analysis.filtered_events[]` with fields:
- `author_association` — contributor association of the triggering actor
- `author_login` — login of the triggering actor
-3. For each event returned, annotate it with two additional fields from the corresponding run entry in `filtered-logs.json`: `workflow_name` (from `workflowName`) and `run_id` (from `databaseId`). This allows the Python analysis to group events by workflow.
-4. Collect all annotated filtered events across all runs and save them to `/tmp/gh-aw/integrity/all-events.json`.
+3. Annotate each event with `workflow_name` (from `workflowName`) and `run_id` (from `databaseId`).
+4. Save all annotated events to `/tmp/gh-aw/integrity/all-events.json`.
-### 1.2 Python Bucketization Script
+### Step 3.3: Bucketize DIFC Events
Create and run `/tmp/gh-aw/integrity/bucketize.py`:
@@ -120,7 +208,6 @@ from datetime import datetime, timedelta
DATA_DIR = "/tmp/gh-aw/integrity"
os.makedirs(DATA_DIR, exist_ok=True)
-# Load all events
with open(f"{DATA_DIR}/all-events.json") as f:
events = json.load(f)
@@ -131,21 +218,18 @@ if not events:
json.dump(summary, f, indent=2)
exit(0)
-# Parse timestamps
for e in events:
try:
e["_dt"] = datetime.fromisoformat(e["timestamp"].replace("Z", "+00:00"))
except Exception:
e["_dt"] = None
-# Buckets
by_tool = Counter(e["tool_name"] for e in events if e.get("tool_name"))
by_server = Counter(e["server_id"] for e in events if e.get("server_id"))
by_reason = Counter(e["reason"] for e in events if e.get("reason"))
by_workflow = Counter(e.get("workflow_name", "unknown") for e in events)
by_user = Counter(e.get("author_login", "unknown") for e in events)
-# Time-based buckets
by_hour = Counter()
by_day = Counter()
for e in events:
@@ -153,7 +237,6 @@ for e in events:
by_hour[e["_dt"].strftime("%Y-%m-%dT%H:00")] += 1
by_day[e["_dt"].strftime("%Y-%m-%d")] += 1
-# Integrity tag breakdown
all_integrity_tags = Counter()
all_secrecy_tags = Counter()
for e in events:
@@ -184,7 +267,9 @@ print(json.dumps(summary, indent=2))
Run the script: `python3 /tmp/gh-aw/integrity/bucketize.py`
-## Step 2: Generate Statistical Charts
+---
+
+## Phase 4: Generate DIFC Statistical Charts
Create and run chart scripts using matplotlib/seaborn. Save all charts to `/tmp/gh-aw/integrity/charts/`.
@@ -192,13 +277,13 @@ Create and run chart scripts using matplotlib/seaborn. Save all charts to `/tmp/
mkdir -p /tmp/gh-aw/integrity/charts
```
-### Chart 1: Events Over Time (Daily)
+### Chart 3: DIFC Events Over Time (Daily)
Create `/tmp/gh-aw/integrity/chart_timeline.py`:
```python
#!/usr/bin/env python3
-"""Chart 1: DIFC filtered events per day."""
+"""Chart 3: DIFC filtered events per day."""
import json, os
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
@@ -214,7 +299,7 @@ with open(f"{DATA_DIR}/summary.json") as f:
by_day = summary.get("by_day", {})
if not by_day:
- print("No daily data; skipping chart 1.")
+ print("No daily data; skipping chart 3.")
exit(0)
dates = [datetime.strptime(d, "%Y-%m-%d") for d in sorted(by_day)]
@@ -232,18 +317,18 @@ ax.set_ylabel("Event Count", fontsize=13)
ax.grid(True, axis="y", alpha=0.4)
plt.tight_layout()
plt.savefig(f"{CHARTS_DIR}/events_timeline.png", dpi=300, bbox_inches="tight", facecolor="white")
-print("Chart 1 saved.")
+print("Chart 3 saved.")
```
Run: `python3 /tmp/gh-aw/integrity/chart_timeline.py`
-### Chart 2: Top Filtered Tools (Horizontal Bar)
+### Chart 4: Top Filtered Tools (Horizontal Bar)
Create `/tmp/gh-aw/integrity/chart_tools.py`:
```python
#!/usr/bin/env python3
-"""Chart 2: Top tools that trigger DIFC filtering."""
+"""Chart 4: Top tools that trigger DIFC filtering."""
import json, os
import matplotlib.pyplot as plt
import seaborn as sns
@@ -257,7 +342,7 @@ with open(f"{DATA_DIR}/summary.json") as f:
by_tool = summary.get("by_tool", {})
if not by_tool:
- print("No tool data; skipping chart 2.")
+ print("No tool data; skipping chart 4.")
exit(0)
items = sorted(by_tool.items(), key=lambda x: x[1], reverse=True)[:15]
@@ -276,18 +361,18 @@ ax.set_ylabel("Tool Name", fontsize=13)
ax.grid(True, axis="x", alpha=0.4)
plt.tight_layout()
plt.savefig(f"{CHARTS_DIR}/top_tools.png", dpi=300, bbox_inches="tight", facecolor="white")
-print("Chart 2 saved.")
+print("Chart 4 saved.")
```
Run: `python3 /tmp/gh-aw/integrity/chart_tools.py`
-### Chart 3: Filter Reason Breakdown (Pie / Donut)
+### Chart 5: Filter Reason Breakdown (Pie / Donut)
Create `/tmp/gh-aw/integrity/chart_reasons.py`:
```python
#!/usr/bin/env python3
-"""Chart 3: Breakdown of filter reasons and integrity/secrecy tags."""
+"""Chart 5: Breakdown of filter reasons and integrity/secrecy tags."""
import json, os
import matplotlib.pyplot as plt
import seaborn as sns
@@ -306,7 +391,6 @@ secrecy_tags = summary.get("secrecy_tags", {})
sns.set_style("whitegrid")
fig, axes = plt.subplots(1, 2, figsize=(14, 6), dpi=300)
-# Left: filter reasons pie
if by_reason:
labels = list(by_reason.keys())
values = list(by_reason.values())
@@ -319,7 +403,6 @@ else:
axes[0].text(0.5, 0.5, "No reason data", ha="center", va="center")
axes[0].set_title("Filter Reason Distribution", fontsize=14, fontweight="bold")
-# Right: top integrity/secrecy tags bar
all_tags = {**{f"[I] {k}": v for k, v in integrity_tags.items()},
**{f"[S] {k}": v for k, v in secrecy_tags.items()}}
if all_tags:
@@ -338,88 +421,98 @@ else:
fig.suptitle("DIFC Filter Analysis — Reason & Tag Breakdown", fontsize=16, fontweight="bold", y=1.01)
plt.tight_layout()
plt.savefig(f"{CHARTS_DIR}/reasons_tags.png", dpi=300, bbox_inches="tight", facecolor="white")
-print("Chart 3 saved.")
+print("Chart 5 saved.")
```
Run: `python3 /tmp/gh-aw/integrity/chart_reasons.py`
-### Chart 4: Per-User Filtered Events (Horizontal Bar)
+### Upload DIFC Charts
-Create `/tmp/gh-aw/integrity/chart_users.py`:
+Upload each generated DIFC chart using the `upload asset` tool and collect the returned URLs:
+1. Upload `/tmp/gh-aw/integrity/charts/events_timeline.png`
+2. Upload `/tmp/gh-aw/integrity/charts/top_tools.png`
+3. Upload `/tmp/gh-aw/integrity/charts/reasons_tags.png`
-```python
-#!/usr/bin/env python3
-"""Chart 4: Top users that trigger DIFC filtering."""
-import json, os
-import matplotlib.pyplot as plt
-import seaborn as sns
+---
-DATA_DIR = "/tmp/gh-aw/integrity"
-CHARTS_DIR = f"{DATA_DIR}/charts"
-BAR_COLOR = "#4CAF50"
-os.makedirs(CHARTS_DIR, exist_ok=True)
+## Phase 5: Generate Combined Security Observability Report
-with open(f"{DATA_DIR}/summary.json") as f:
- summary = json.load(f)
+Create a single GitHub discussion combining both signals.
-by_user = summary.get("by_user", {})
-if not by_user:
- print("No user data; skipping chart 4.")
- exit(0)
+**Title**: `[security-observability] Daily Security Observability Report — YYYY-MM-DD`
-items = sorted(by_user.items(), key=lambda x: x[1], reverse=True)[:20]
-users = [i[0] for i in items]
-counts = [i[1] for i in items]
+**Body** (use h3 and lower for all headers per reporting guidelines):
-sns.set_style("whitegrid")
-fig, ax = plt.subplots(figsize=(12, max(5, len(users) * 0.55)), dpi=300)
-bars = ax.barh(users[::-1], counts[::-1], color=BAR_COLOR, edgecolor="white", linewidth=0.8)
-for bar, val in zip(bars, counts[::-1]):
- ax.text(bar.get_width() + 0.1, bar.get_y() + bar.get_height() / 2,
- str(val), va="center", fontsize=11, fontweight="bold")
-ax.set_title("Top Users Triggering DIFC Filtering (Top 20)", fontsize=16, fontweight="bold", pad=14)
-ax.set_xlabel("Event Count", fontsize=13)
-ax.set_ylabel("Author Login", fontsize=13)
-ax.grid(True, axis="x", alpha=0.4)
-plt.tight_layout()
-plt.savefig(f"{CHARTS_DIR}/top_users.png", dpi=300, bbox_inches="tight", facecolor="white")
-print("Chart 4 saved.")
-```
+```markdown
+### Executive Summary
-Run: `python3 /tmp/gh-aw/integrity/chart_users.py`
+[2–3 paragraph overview combining both signals: firewall traffic patterns and DIFC integrity filtering activity for the last 7 days. Highlight the most significant findings from each domain and any cross-cutting themes (e.g., the same workflow appearing in both firewall blocks and DIFC filtering).]
-## Step 3: Upload Charts
+---
-Upload each generated chart using the `upload asset` tool and collect the returned URLs:
-1. Upload `/tmp/gh-aw/integrity/charts/events_timeline.png`
-2. Upload `/tmp/gh-aw/integrity/charts/top_tools.png`
-3. Upload `/tmp/gh-aw/integrity/charts/reasons_tags.png`
-4. Upload `/tmp/gh-aw/integrity/charts/top_users.png`
+## 🔥 Firewall Analysis
-## Step 4: Generate Tuning Recommendations
+### Key Firewall Metrics
-Based on the summary data, derive actionable recommendations. For each top filtered tool or server:
-- Is the tool legitimately called on untrusted data? If yes, recommend applying integrity tags.
-- Are secrecy-tagged artifacts being passed to tools that don't need them? Recommend narrowing tool access.
-- Is the filter rate increasing? Recommend reviewing recent prompt changes.
-- Are there bursts (many events in one hour)? Investigate the workflow(s) involved.
+| Metric | Value |
+|--------|-------|
+| Workflows analyzed (firewall-enabled) | [N] |
+| Total network requests monitored | [N] |
+| ✅ Allowed requests | [N] |
+| 🚫 Blocked requests | [N] |
+| Block rate | [N]% |
+| Total unique blocked domains | [N] |
-## Step 5: Create Discussion Report
+### 📈 Firewall Request Trends
-Create a GitHub discussion with the full analysis.
+
-**Title**: `[integrity] DIFC Integrity-Filtered Events Report — YYYY-MM-DD`
+[2–3 sentence analysis of firewall activity trends, noting increases in blocked traffic or changes in patterns]
-**Body** (use h3 and lower for all headers per reporting guidelines):
+### Top Blocked Domains
-```markdown
-### Executive Summary
+
+
+[Brief 2–3 sentence analysis of frequently blocked domains, identifying potential security concerns or overly restrictive rules]
+
+#### Most Frequently Blocked Domains
+
+| Domain | Times Blocked | Workflows | Category |
+|--------|--------------|-----------|----------|
+[Top 20 domains sorted by block count descending]
+
+[When policy_analysis is available:]
+#### Policy Rule Attribution
+
+📋 Policy: [policy_summary from most recent run]
+
+| Rule | Action | Description | Total Hits |
+|------|--------|-------------|------------|
+[Rules sorted by hits descending, 🟢 for allow / 🔴 for deny]
+
+
+View Detailed Request Patterns by Workflow
+
+[Per-workflow firewall breakdown]
+
+
-In the last 7 days, **[N]** DIFC integrity-filtered events were detected across **[W]** workflow runs. The most frequently filtered tool was **[tool_name]** ([X] events), and the dominant filter reason was **[reason]**. [Describe the overall trend: stable/increasing/decreasing. Highlight any notable spike or pattern that warrants attention.]
+
+View Complete Blocked Domains List
+
+[Alphabetical full list of unique blocked domains]
+
+
-[Describe which workflows or MCP servers contributed the most events and what that suggests about how the integrity system is being exercised. If the filtering rate is high for a particular server, explain whether this is expected or a sign of misconfiguration.]
+### 🔒 Firewall Security Recommendations
+
+[Actionable recommendations: domains to allowlist, suspicious domains, policy rule improvements, workflows needing network permission updates]
+
+---
-### Key Metrics
+## 🔒 DIFC Integrity Analysis
+
+### Key DIFC Metrics
| Metric | Value |
|--------|-------|
@@ -429,26 +522,26 @@ In the last 7 days, **[N]** DIFC integrity-filtered events were detected across
| Most common filter reason | [reason] |
| Busiest day | [YYYY-MM-DD] ([N] events) |
-### 📈 Events Over Time
+### 📈 DIFC Events Over Time
-
+
[2–3 sentence analysis: is there a trend? any spikes?]
### 🔧 Top Filtered Tools
-
+
[Brief analysis: which tools trigger the most filtering and why]
### 🏷️ Filter Reasons and Tags
-
+
[Analysis of integrity vs. secrecy filtering and top tags]
-📋 Per-Workflow Breakdown
+📋 Per-Workflow DIFC Breakdown
| Workflow | Filtered Events |
|----------|----------------|
@@ -457,7 +550,7 @@ In the last 7 days, **[N]** DIFC integrity-filtered events were detected across
-📋 Per-Server Breakdown
+📋 Per-Server DIFC Breakdown
| MCP Server | Filtered Events |
|------------|----------------|
@@ -466,7 +559,7 @@ In the last 7 days, **[N]** DIFC integrity-filtered events were detected across
-👤 Per-User Breakdown
+👤 Per-User DIFC Breakdown
| Author Login | Filtered Events |
|--------------|----------------|
@@ -474,30 +567,23 @@ In the last 7 days, **[N]** DIFC integrity-filtered events were detected across
-### 🔍 Per-User Analysis
-
-
-
-[Analysis of per-user filtering: identify whether spikes are driven by automated actors (e.g., `github-actions[bot]`, Copilot agents) or human contributors. Highlight any single user or bot account responsible for a disproportionate share of filtered events and suggest whether this indicates expected automation behaviour or warrants investigation.]
+### 💡 DIFC Tuning Recommendations
-### 💡 Tuning Recommendations
-
-[Numbered list of specific, actionable recommendations derived from the analysis. Examples:
-- Tools appearing in top filtered: consider whether they need access to integrity-tagged data
-- High secrecy filter rate: review which workflows pass secrets to tools
-- Increasing trend: monitor and review recent prompt or tool permission changes
-- Workflow-specific spikes: examine the triggering events for that workflow]
+[Numbered list of specific, actionable recommendations derived from DIFC analysis.]
---
-*Generated by the Daily Integrity Analysis workflow*
+
+*Generated by the Daily Security Observability workflow (consolidated from Daily Firewall Reporter + Daily DIFC Analyzer)*
*Analysis window: Last 7 days | Repository: ${{ github.repository }}*
*Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}*
```
## Important
-**Always** call a safe-output tool at the end of your run. If no events were found, call `noop`:
+**Always** call a safe-output tool at the end of your run. If both datasets are empty, call `noop`:
```json
-{"noop": {"message": "No DIFC integrity-filtered events found in the last 7 days; no report generated."}}
+{"noop": {"message": "No firewall-enabled runs and no DIFC integrity-filtered events found in the last 7 days; no report generated."}}
```
+
+{{#runtime-import shared/noop-reminder.md}}
diff --git a/docs/src/content/docs/agent-factory-status.mdx b/docs/src/content/docs/agent-factory-status.mdx
index fd6dc1675a9..3feef8fd9db 100644
--- a/docs/src/content/docs/agent-factory-status.mdx
+++ b/docs/src/content/docs/agent-factory-status.mdx
@@ -64,12 +64,11 @@ These are experimental agentic workflows used by the GitHub Next team to learn,
| [Daily Compiler Quality Check](https://github.com/github/gh-aw/blob/main/.github/workflows/daily-compiler-quality.md) | copilot | [](https://github.com/github/gh-aw/actions/workflows/daily-compiler-quality.lock.yml) | - | - |
| [Daily Copilot PR Merged Report](https://github.com/github/gh-aw/blob/main/.github/workflows/copilot-pr-merged-report.md) | copilot | [](https://github.com/github/gh-aw/actions/workflows/copilot-pr-merged-report.lock.yml) | `daily around 15:00 on weekdays` | - |
| [Daily Copilot Token Usage Audit](https://github.com/github/gh-aw/blob/main/.github/workflows/copilot-token-audit.md) | copilot | [](https://github.com/github/gh-aw/actions/workflows/copilot-token-audit.lock.yml) | `daily around 12:00 on weekdays` | - |
-| [Daily DIFC Integrity-Filtered Events Analyzer](https://github.com/github/gh-aw/blob/main/.github/workflows/daily-integrity-analysis.md) | copilot | [](https://github.com/github/gh-aw/actions/workflows/daily-integrity-analysis.lock.yml) | - | - |
+| [Daily Security Observability Report](https://github.com/github/gh-aw/blob/main/.github/workflows/daily-security-observability.md) | copilot | [](https://github.com/github/gh-aw/actions/workflows/daily-security-observability.lock.yml) | - | - |
| [Daily Documentation Healer](https://github.com/github/gh-aw/blob/main/.github/workflows/daily-doc-healer.md) | claude | [](https://github.com/github/gh-aw/actions/workflows/daily-doc-healer.lock.yml) | - | - |
| [Daily Documentation Updater](https://github.com/github/gh-aw/blob/main/.github/workflows/daily-doc-updater.md) | claude | [](https://github.com/github/gh-aw/actions/workflows/daily-doc-updater.lock.yml) | - | - |
| [Daily Fact About gh-aw](https://github.com/github/gh-aw/blob/main/.github/workflows/daily-fact.md) | codex | [](https://github.com/github/gh-aw/actions/workflows/daily-fact.lock.yml) | `daily around 11:00 on weekdays` | - |
| [Daily File Diet](https://github.com/github/gh-aw/blob/main/.github/workflows/daily-file-diet.md) | copilot | [](https://github.com/github/gh-aw/actions/workflows/daily-file-diet.lock.yml) | `daily around 13:00 on weekdays` | - |
-| [Daily Firewall Logs Collector and Reporter](https://github.com/github/gh-aw/blob/main/.github/workflows/daily-firewall-report.md) | copilot | [](https://github.com/github/gh-aw/actions/workflows/daily-firewall-report.lock.yml) | - | - |
| [Daily Go Function Namer](https://github.com/github/gh-aw/blob/main/.github/workflows/daily-function-namer.md) | claude | [](https://github.com/github/gh-aw/actions/workflows/daily-function-namer.lock.yml) | - | - |
| [Daily Hippo Learn](https://github.com/github/gh-aw/blob/main/.github/workflows/daily-hippo-learn.md) | copilot | [](https://github.com/github/gh-aw/actions/workflows/daily-hippo-learn.lock.yml) | `daily around 7:00` | - |
| [Daily Issues Report Generator](https://github.com/github/gh-aw/blob/main/.github/workflows/daily-issues-report.md) | copilot | [](https://github.com/github/gh-aw/actions/workflows/daily-issues-report.lock.yml) | - | - |
diff --git a/docs/src/content/docs/patterns/daily-ops.md b/docs/src/content/docs/patterns/daily-ops.md
index 513f0fb5360..76016d33339 100644
--- a/docs/src/content/docs/patterns/daily-ops.md
+++ b/docs/src/content/docs/patterns/daily-ops.md
@@ -73,7 +73,7 @@ This repository implements several DailyOps workflows demonstrating different us
- **daily-doc-updater.md** - Keeps documentation synchronized with merged code changes
- **daily-team-status** (from [agentics](https://github.com/githubnext/agentics)) - Creates daily team status reports with activity summaries
- **daily-repo-chronicle.md** - Produces newspaper-style repository updates
-- **daily-firewall-report.md** - Analyzes and reports on firewall activity
+- **daily-security-observability.md** - Unified security observability report combining firewall traffic analysis and DIFC integrity-filtered event analysis
All follow the phased approach with discussions for tracking and draft pull requests for review.