Skip to content

Transient Security Explanation #270256

Transient Security Explanation

Transient Security Explanation #270256

# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"da9213bafa2172beea74a2a6b019414c39b2a3c543147d0bb02fac9edde03a58","body_hash":"7993207944074340b2ec6e8de3397e3691510b10062a8ecd3a8ba5d5b822d79a","compiler_version":"v0.88.7","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}}

Check warning on line 1 in .github/workflows/transient-security-explanation.lock.yml

View workflow run for this annotation

GitHub Actions / Transient Security Explanation

Workflow execution policy warning (evaluate mode)

On December 2, 2026, GitHub will restrict `pull_request_target` on public repositories by default. To continue allowing the event trigger, configure an Actions policy. Learn more: https://gh.io/securely-using-pull_request_target#default-policy-for-pull_request_target
# gh-aw-manifest: {"version":1,"secrets":["GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"5e508589e03a7757a7e05b26e834292f5445bfb6","version":"v0.88.7"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.14","digest":"sha256:f7df036c86575527b61f3f7df91c4412349a12b2a74988d929eafa2999230c98","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.14@sha256:f7df036c86575527b61f3f7df91c4412349a12b2a74988d929eafa2999230c98"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.14","digest":"sha256:6f95e2234dd9bd6333a8ff28ccea7ecf0204acd4a09108723844dbd2bf6268c5","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.14@sha256:6f95e2234dd9bd6333a8ff28ccea7ecf0204acd4a09108723844dbd2bf6268c5"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.14","digest":"sha256:2ce8df3abf3e9b76e9c0cf5863da41f1ab3f89b20ad14b988806ab89e7bf2cd5","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.14@sha256:2ce8df3abf3e9b76e9c0cf5863da41f1ab3f89b20ad14b988806ab89e7bf2cd5"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.18","digest":"sha256:85b940556a8faa4e1fdbef124bfd75f2c4ebd855a10b88a1c3b6f3e97f6f1a53","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.18@sha256:85b940556a8faa4e1fdbef124bfd75f2c4ebd855a10b88a1c3b6f3e97f6f1a53"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"has_pull_request_target":true,"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["noop","post_transient_security_comment"]}]}
# This file was automatically generated by gh-aw (v0.88.7). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
#
# ___ _ _
# / _ \ | | (_)
# | |_| | __ _ ___ _ __ | |_ _ ___
# | _ |/ _` |/ _ \ '_ \| __| |/ __|
# | | | | (_| | __/ | | | |_| | (__
# \_| |_/\__, |\___|_| |_|\__|_|\___|
# __/ |
# _ _ |___/
# | | | | / _| |
# | | | | ___ _ __ _ __| |_| | _____ ____
# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___|
# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
#
#
# To update this file, edit the corresponding .md file and run:
# gh aw compile
# Not all edits will cause changes to this file.
#
# For more information: https://github.github.com/gh-aw/introduction/overview/
#
# Closed-world author assist for one confirmed Defender scan contention.
#
# Secrets used:
# - GH_AW_DEFAULT_OTLP_HEADERS
# - GH_AW_GITHUB_MCP_SERVER_TOKEN
# - GH_AW_GITHUB_TOKEN
# - GITHUB_TOKEN
#
# Custom actions used:
# - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
# - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
# - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 (source v8)
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9)
# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 (source v7)
# - github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7
#
# Container images used:
# - ghcr.io/github/gh-aw-firewall/agent:0.28.14@sha256:f7df036c86575527b61f3f7df91c4412349a12b2a74988d929eafa2999230c98
# - ghcr.io/github/gh-aw-firewall/api-proxy:0.28.14@sha256:6f95e2234dd9bd6333a8ff28ccea7ecf0204acd4a09108723844dbd2bf6268c5
# - ghcr.io/github/gh-aw-firewall/squid:0.28.14@sha256:2ce8df3abf3e9b76e9c0cf5863da41f1ab3f89b20ad14b988806ab89e7bf2cd5
# - ghcr.io/github/gh-aw-mcpg:v0.4.18@sha256:85b940556a8faa4e1fdbef124bfd75f2c4ebd855a10b88a1c3b6f3e97f6f1a53
# - ghcr.io/github/gh-aw-node@sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23
# - ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
name: "Transient Security Explanation"
on:
# bots: # Bots processed as bot check in pre-activation job
# - wingetvalidator-prod[bot] # Bots processed as bot check in pre-activation job
pull_request_target:
types:
- labeled
# roles: # Roles processed as role check in pre-activation job
# - admin # Roles processed as role check in pre-activation job
# - maintainer # Roles processed as role check in pre-activation job
# - write # Roles processed as role check in pre-activation job
permissions: {}
concurrency:
cancel-in-progress: false
group: gh-aw-${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
queue: max
run-name: "Transient Security Explanation"
env:
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}
OTEL_SERVICE_NAME: gh-aw.transient-security-explanation
OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=Transient%20Security%20Explanation,gh-aw.repository=${{ github.repository }},gh-aw.run.id=${{ github.run_id }},github.run_id=${{ github.run_id }},gh-aw.engine.id=copilot'
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}
GH_AW_OTLP_ENDPOINTS: '[{"url":"${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}","headers":"${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}"}]'
GH_AW_OTLP_IF_MISSING: ignore
jobs:
activation:
needs: pre_activation
if: >
needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'pull_request_target' && github.event.action == 'labeled' &&
github.event.label.name == 'Validation-Defender-Error' && github.actor == 'wingetvalidator-prod[bot]' &&
github.event.pull_request.user.login != 'wingetbot')
runs-on: ubuntu-slim
permissions:
actions: read
contents: read
env:
GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }}
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
outputs:
body: ${{ steps.sanitized.outputs.body }}
comment_id: ""
comment_repo: ""
daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }}
daily_ai_credits_guardrail_status: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_guardrail_status || '' }}
daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }}
daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }}
engine_id: ${{ steps.generate_aw_info.outputs.engine_id }}
lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }}
model: ${{ steps.generate_aw_info.outputs.model }}
oauth_token_check_failed: ${{ steps.check-oauth-tokens.outputs.oauth_token_check_failed == 'true' }}
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
setup-span-id: ${{ steps.setup.outputs.span-id }}
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }}
text: ${{ steps.sanitized.outputs.text }}
title: ${{ steps.sanitized.outputs.title }}
steps:
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }}
parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }}
safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }}
env:
GH_AW_SETUP_WORKFLOW_NAME: "Transient Security Explanation"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/transient-security-explanation.lock.yml@${{ github.ref }}
GH_AW_INFO_VERSION: "1.0.80"
GH_AW_INFO_AWF_VERSION: "v0.28.14"
GH_AW_INFO_ENGINE_ID: "copilot"
- 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 || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
GH_AW_INFO_VERSION: "1.0.80"
GH_AW_INFO_AGENT_VERSION: "1.0.80"
GH_AW_INFO_CLI_VERSION: "v0.88.7"
GH_AW_INFO_WORKFLOW_NAME: "Transient Security Explanation"
GH_AW_INFO_EXPERIMENTAL: "false"
GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
GH_AW_INFO_STAGED: "false"
GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","cdn.winget.microsoft.com"]'
GH_AW_INFO_FIREWALL_ENABLED: "true"
GH_AW_INFO_AWF_VERSION: "v0.28.14"
GH_AW_INFO_AWMG_VERSION: ""
GH_AW_INFO_FIREWALL_TYPE: "squid"
GH_AW_INFO_AGENT_RUNTIME: ""
GH_AW_INFO_FRONTMATTER_EMOJI: "🛡️"
GH_AW_COMPILED_STRICT: "true"
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'generate_aw_info.cjs'));
await main(core, context);
- name: Restore daily AIC usage cache
id: restore-daily-aic-cache
if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }}
continue-on-error: true
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
key: agentic-workflow-usage-transientsecurityexplanation-${{ github.run_id }}
restore-keys: agentic-workflow-usage-transientsecurityexplanation-
path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl
- name: Restore daily AIC usage cache (artifact fallback)
id: restore-daily-aic-cache-fallback
if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }}
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }}
GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'restore_aic_usage_cache_fallback.cjs'));
await main();
- name: Check daily workflow token guardrail
id: daily-effective-workflow-guardrail
if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_WORKFLOW_NAME: "Transient Security Explanation"
GH_AW_WORKFLOW_ID: "transient-security-explanation"
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }}
GH_AW_HAS_SLASH_COMMAND: "false"
GH_AW_HAS_LABEL_COMMAND: "false"
GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'check_daily_aic_workflow_guardrail.cjs'));
await main();
- name: Check for OAuth tokens
id: check-oauth-tokens
run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh"
env:
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
- name: Checkout .github and .agents folders
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
sparse-checkout: |
.github
.agents
.claude
.codex
.gemini
.pi
sparse-checkout-cone-mode: true
fetch-depth: 1
- name: Save agent config folders for base branch restoration
env:
GH_AW_AGENT_FOLDERS: ".agents .github"
GH_AW_AGENT_FILES: "AGENTS.md"
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
- name: Check workflow lock file
id: check-lock-file
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_WORKFLOW_FILE: "transient-security-explanation.lock.yml"
GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}"
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'check_workflow_timestamp_api.cjs'));
await main();
- name: Check compile-agentic version
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_COMPILED_VERSION: "v0.88.7"
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'check_version_updates.cjs'));
await main();
- name: Compute current body text
id: sanitized
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_ALLOWED_BOTS: "wingetvalidator-prod[bot]"
GH_AW_ALLOWED_DOMAINS: "api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,cdn.winget.microsoft.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'compute_text.cjs'));
await main();
- name: Log runtime features
if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }}
run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh"
- name: Create prompt with built-in context
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_ACTIONS_DIR: ${{ runner.temp }}/gh-aw/actions
GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl
GH_AW_PROMPT_CONFIG: "{\"items\":[{\"content_env\":\"GH_AW_PROMPT_CONTENT_0000\"},{\"file\":\"xpia.md\"},{\"file\":\"temp_folder_prompt.md\"},{\"file\":\"markdown.md\"},{\"file\":\"safe_outputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0001\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0002\"},{\"file\":\"mcp_cli_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0003\"},{\"file\":\"github_mcp_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0004\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0005\"}]}"
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }}
GH_AW_GITHUB_ACTOR: ${{ github.actor }}
GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
GH_AW_PROMPT_CONTENT_0000: "<system>\n"
GH_AW_PROMPT_CONTENT_0001: "<safe-output-tools>\nTools: noop, post_transient_security_comment\n"
GH_AW_PROMPT_CONTENT_0002: "</safe-output-tools>\n"
GH_AW_PROMPT_CONTENT_0003: "<github-context>\nThe following GitHub context information is available for this workflow:\n{{#if github.actor}}\n- **actor**: __GH_AW_GITHUB_ACTOR__\n{{/if}}\n{{#if github.repository}}\n- **repository**: __GH_AW_GITHUB_REPOSITORY__\n{{/if}}\n{{#if github.workspace}}\n- **workspace**: __GH_AW_GITHUB_WORKSPACE__\n{{/if}}\n{{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}\n- **issue-number**: #__GH_AW_EXPR_802A9F6A__\n{{/if}}\n{{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}\n- **discussion-number**: #__GH_AW_EXPR_1A3A194A__\n{{/if}}\n{{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}\n- **pull-request-number**: #__GH_AW_EXPR_463A214A__\n{{/if}}\n{{#if github.event.comment.id || github.aw.context.comment_id}}\n- **comment-id**: __GH_AW_EXPR_FF1D34CE__\n{{/if}}\n{{#if github.run_id}}\n- **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__\n{{/if}}\n</github-context>\n\n"
GH_AW_PROMPT_CONTENT_0004: "</system>\n"
GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import .github/workflows/transient-security-explanation.md}}\n"
with:
script: |
const { setupGlobals } = require(process.env.GH_AW_ACTIONS_DIR + '/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(process.env.GH_AW_ACTIONS_DIR + '/create_prompt.cjs');
await main(core);
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_ENGINE_ID: "copilot"
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'interpolate_prompt.cjs'));
await main();
- name: Substitute placeholders
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }}
GH_AW_GITHUB_ACTOR: ${{ github.actor }}
GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools"
GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }}
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const substitutePlaceholders = require(path.join(actionsDir, 'substitute_placeholders.cjs'));
// Call the substitution function
return await substitutePlaceholders({
file: process.env.GH_AW_PROMPT,
substitutions: {
GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A,
GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A,
GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A,
GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE,
GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE,
GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST,
GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED
}
});
- name: Validate prompt placeholders
env:
GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
- name: Print prompt
env:
GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
- name: Stage prompt files for artifact upload
run: |
mkdir -p /tmp/gh-aw/aw-prompts
cp -a "${RUNNER_TEMP}/gh-aw/aw-prompts/." /tmp/gh-aw/aw-prompts/
- name: Upload activation artifact
if: success() || failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: activation
include-hidden-files: true
path: |
/tmp/gh-aw/aw_info.json
/tmp/gh-aw/models.json
/tmp/gh-aw/aw-prompts/prompt.txt
/tmp/gh-aw/aw-prompts/prompt-template.txt
/tmp/gh-aw/aw-prompts/prompt-import-tree.json
/tmp/gh-aw/github_rate_limits.jsonl
/tmp/gh-aw/base
/tmp/gh-aw/.github/agents
/tmp/gh-aw/.github/skills
if-no-files-found: ignore
retention-days: 1
agent:
needs: activation
if: needs.activation.outputs.daily_ai_credits_exceeded != 'true'
runs-on: ubuntu-latest
permissions:
checks: read
contents: read
copilot-requests: write
issues: read
pull-requests: read
timeout-minutes: 60
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_AW_ASSETS_ALLOWED_EXTS: ""
GH_AW_ASSETS_BRANCH: ""
GH_AW_ASSETS_MAX_SIZE_KB: 0
GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
GH_AW_PR_HEAD_BASE_BRANCH: ""
GH_AW_PR_HEAD_BASE_PR_NUMBER: ""
GH_AW_PR_HEAD_BASE_REF: ""
GH_AW_PR_HEAD_BASE_REPO: ""
GH_AW_PR_HEAD_BASE_SHA: ""
GH_AW_PR_HEAD_REPO: ""
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
GH_AW_WORKFLOW_ID_SANITIZED: transientsecurityexplanation
outputs:
agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }}
ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }}
aic: ${{ steps.parse-mcp-gateway.outputs.aic }}
ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }}
effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }}
has_patch: ${{ steps.collect_output.outputs.has_patch }}
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
model: ${{ needs.activation.outputs.model }}
model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }}
output: ${{ steps.collect_output.outputs.output }}
output_types: ${{ steps.collect_output.outputs.output_types }}
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
setup-span-id: ${{ steps.setup.outputs.span-id }}
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
shell_expansion_guard_rejected: ${{ steps.detect-agent-errors.outputs.shell_expansion_guard_rejected || 'false' }}
unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }}
steps:
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
trace-id: ${{ needs.activation.outputs.setup-trace-id }}
parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }}
env:
GH_AW_SETUP_WORKFLOW_NAME: "Transient Security Explanation"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/transient-security-explanation.lock.yml@${{ github.ref }}
GH_AW_INFO_VERSION: "1.0.80"
GH_AW_INFO_AWF_VERSION: "v0.28.14"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Set runtime paths
id: set-runtime-paths
env:
GH_AW_RUNNER_TOOL_CACHE: ${{ runner.tool_cache }}
run: |
if [ -z "${RUNNER_TOOL_CACHE:-}" ]; then
echo "RUNNER_TOOL_CACHE=${GH_AW_RUNNER_TOOL_CACHE}" >> "$GITHUB_ENV"
fi
{
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: Check OTLP telemetry configuration
run: bash "${RUNNER_TEMP}/gh-aw/actions/check_otlp_default_credentials.sh"
- name: Create gh-aw temp directory
run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh"
- name: Configure gh CLI for GitHub Enterprise
run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh"
env:
GH_TOKEN: ${{ github.token }}
- name: Download activation artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: activation
path: /tmp/gh-aw
- name: Install GitHub Copilot CLI
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
GH_AW_COMPILED_VERSION: v0.88.7
- name: Install AWF binary
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.14 --rootless
- name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9)
env:
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
GH_AW_GITHUB_MIN_INTEGRITY: 'none'
GH_AW_GITHUB_REPOS: '["microsoft/winget-pkgs"]'
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const determineAutomaticLockdown = require(path.join(actionsDir, 'determine_automatic_lockdown.cjs'));
await determineAutomaticLockdown(github, context, core);
- name: Parse integrity filter lists
id: parse-guard-vars
env:
GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }}
GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }}
GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }}
run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh"
- name: Restore inline sub-agents from activation artifact
env:
GH_AW_SUB_AGENT_DIR: ".github/agents"
GH_AW_SUB_AGENT_EXT: ".agent.md"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh"
- name: Restore inline skills from activation artifact
env:
GH_AW_SKILL_DIR: ".github/skills"
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh"
- env:
TARGET_PR: ${{ github.event.pull_request.number || '' }}
TRIGGER_HEAD_SHA: ${{ github.event.pull_request.head.sha || '' }}
name: Classify trusted validation operation
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9)
with:
github-token: ${{ github.token }}
script: "const fs = require(\"fs\");\nconst zlib = require(\"zlib\");\nconst outputPath = \"/tmp/gh-aw/transient-security-evidence.json\";\nfs.mkdirSync(\"/tmp/gh-aw\", { recursive: true });\nconst owner = \"microsoft\";\nconst repo = \"winget-pkgs\";\nconst appId = 1451866;\nconst appSlug = \"wingetvalidator-prod\";\nconst targetLabel = \"Validation-Defender-Error\";\nconst conflicts = new Set([\n \"Binary-Validation-Error\", \"Blocking-Issue\",\n \"Error-Analysis-Timeout\", \"Error-Hash-Mismatch\",\n \"Internal-Error\", \"Internal-Error-AppsAndFeaturesVersion\",\n \"Internal-Error-Dependencies\", \"Internal-Error-Domain\",\n \"Internal-Error-Dynamic-Scan\", \"Internal-Error-Keyword-Policy\",\n \"Internal-Error-Manifest\", \"Internal-Error-Manifest-Installer\",\n \"Internal-Error-NoArchitectures\",\n \"Internal-Error-NoSupportedArchitectures\", \"Internal-Error-PR\",\n \"Internal-Error-Static-Scan\", \"Internal-Error-URL\",\n \"Internal-Error-Webhook\", \"Needs-SmartScreen-Investigation\",\n \"Network-Blocker\", \"Package-Flagged\", \"PUA-Detection\",\n \"PullRequest-Error\", \"Scripted-Application\",\n \"URL-Validation-Error\", \"Validation-Certificate-Root\",\n \"Validation-Defender-Error\", \"Validation-Executable-Error\",\n \"Validation-Hash-Flagged\", \"Validation-Hash-Verification-Failed\",\n \"Validation-HTTP-Error\", \"Validation-No-Executables\",\n \"Validation-Shell-Execute\", \"Validation-SmartScreen\",\n \"Validation-SmartScreen-Error\", \"Validation-Submission-Expired\",\n \"Validation-Submission-Failed\", \"Validation-Submission-Mismatch\",\n \"Validation-Submission-Missing\",\n \"Validation-Submission-Unsupported\",\n \"Validation-Virus-Scan-Error\",\n]);\nconst outcomes = new Map([\n [\"01. Pull Request Validation\", \"success\"],\n [\"02. Manifest Validation\", \"success\"],\n [\"03. URLs Validation\", \"success\"],\n [\"04. URL Domain Validation\", \"success\"],\n [\"05. Manifest Policy Validation\", \"success\"],\n [\"06. Catalog Content Verification\", \"success\"],\n [\"07. Installers Scan\", \"success\"],\n [\"08. Installation Validation\", \"failure|action_required\"],\n [\"09. Installer Metadata Validation\", \"skipped\"],\n [\"10. Validation Completed\", \"success\"],\n]);\nconst prNumber = Number(process.env.TARGET_PR);\nconst triggerHead = String(process.env.TRIGGER_HEAD_SHA ?? \"\").trim();\nconst evidence = {\n schemaVersion: 1, verdict: \"noop\", reasonCode: \"unclassified\",\n pullRequestNumber: Number.isSafeInteger(prNumber) ? prNumber : null,\n headSha: null, operationId: null, completionCheckId: null,\n checkRunIds: null, facts: null,\n};\nconst finish = (reason) => { evidence.reasonCode = reason; };\nconst trusted = (c, head) =>\n c?.app?.id === appId && c?.app?.slug === appSlug && c?.head_sha === head;\nconst boundedOutput = (c) => {\n const o = c?.output;\n const skippedText = c?.name === \"09. Installer Metadata Validation\" &&\n (o?.text === null || o?.text === \"\");\n const values = [o?.title, o?.summary, skippedText ? \"\" : o?.text];\n return values.every((v) => typeof v === \"string\") &&\n o.title.length > 0 && o.summary.length > 0 &&\n o.title.length <= 1000 && o.summary.length <= 10000 &&\n values[2].length <= 30000 &&\n (skippedText || values[2].length > 0) &&\n !/\\b(?:output (?:was )?truncated|truncated due to)\\b|\\[\\s*(?:output\\s+)?truncated\\s*\\]/i\n .test(values.join(\"\\n\"));\n};\nconst finding = (text) => [\n /\\b(?:detected|found|blocked|quarantined)\\b.{0,100}\\b(?:threat|malware|virus|trojan|pua)\\b/is,\n /\\b(?:threat|malware|virus|trojan|pua)\\b.{0,100}\\b(?:detected|found|blocked|quarantined)\\b/is,\n /\\bsmartscreen\\b.{0,100}\\b(?:blocked|malicious|unsafe|warning|failed)\\b/is,\n /\\b(?:hash|signature|integrity)\\b.{0,100}\\b(?:mismatch|invalid|tampered|compromised)\\b/is,\n].some((pattern) => pattern.test(text));\nconst safePath = (name) =>\n typeof name === \"string\" && name.length > 0 && name.length <= 240 &&\n !/[\\\\:\\u0000-\\u001f\\u007f]/.test(name) && !name.startsWith(\"/\") &&\n name.split(\"/\").every((part) => part && part !== \".\" && part !== \"..\");\nconst crcTable = Array.from({ length: 256 }, (_, n) => {\n let c = n;\n for (let i = 0; i < 8; i++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;\n return c >>> 0;\n});\nconst crc32 = (buffer) => {\n let crc = 0xffffffff;\n for (const byte of buffer) crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8);\n return (crc ^ 0xffffffff) >>> 0;\n};\nconst readNamedLogs = (zip, artifact, declared, wanted) => {\n let eocd = -1;\n for (let i = zip.length - 22; i >= Math.max(0, zip.length - 65557); i--) {\n if (zip.readUInt32LE(i) === 0x06054b50) { eocd = i; break; }\n }\n if (eocd < 0 || eocd + 22 + zip.readUInt16LE(eocd + 20) !== zip.length ||\n zip.readUInt16LE(eocd + 4) !== 0 || zip.readUInt16LE(eocd + 6) !== 0 ||\n zip.readUInt16LE(eocd + 8) !== artifact.TotalFilesCount ||\n zip.readUInt16LE(eocd + 10) !== artifact.TotalFilesCount) throw new Error(\"zip_eocd\");\n const centralSize = zip.readUInt32LE(eocd + 12);\n const centralOffset = zip.readUInt32LE(eocd + 16);\n if (centralOffset + centralSize !== eocd) throw new Error(\"zip_central\");\n const decoder = new TextDecoder(\"utf-8\", { fatal: true });\n const entries = new Map();\n let offset = centralOffset;\n let totalSize = 0;\n for (let i = 0; i < artifact.TotalFilesCount; i++) {\n if (offset + 46 > eocd || zip.readUInt32LE(offset) !== 0x02014b50)\n throw new Error(\"zip_entry\");\n const flags = zip.readUInt16LE(offset + 8);\n const method = zip.readUInt16LE(offset + 10);\n const crc = zip.readUInt32LE(offset + 16);\n const compressedSize = zip.readUInt32LE(offset + 20);\n const size = zip.readUInt32LE(offset + 24);\n const nameLength = zip.readUInt16LE(offset + 28);\n const extraLength = zip.readUInt16LE(offset + 30);\n const commentLength = zip.readUInt16LE(offset + 32);\n const localOffset = zip.readUInt32LE(offset + 42);\n const end = offset + 46 + nameLength + extraLength + commentLength;\n if (end > eocd || (flags & ~0x800) !== 0 || ![0, 8].includes(method) ||\n extraLength > 256 || commentLength > 256 ||\n [compressedSize, size, localOffset].includes(0xffffffff) ||\n size > 100000 || compressedSize > artifact.ZipFileSizeBytes ||\n (compressedSize === 0 ? size !== 0 : size > compressedSize * 100 + 1024))\n throw new Error(\"zip_limits\");\n const name = decoder.decode(zip.subarray(offset + 46, offset + 46 + nameLength));\n const expected = declared.get(name);\n if (!safePath(name) || entries.has(name) || !expected || expected.SizeBytes !== size)\n throw new Error(\"zip_declaration\");\n entries.set(name, { flags, method, crc, compressedSize, size, localOffset, name });\n totalSize += size;\n offset = end;\n }\n if (offset !== eocd || entries.size !== declared.size ||\n totalSize !== artifact.TotalSizeBytes) throw new Error(\"zip_completeness\");\n const logs = new Map();\n for (const name of wanted) {\n const e = entries.get(name);\n if (!e || e.localOffset + 30 > centralOffset ||\n zip.readUInt32LE(e.localOffset) !== 0x04034b50 ||\n zip.readUInt16LE(e.localOffset + 6) !== e.flags ||\n zip.readUInt16LE(e.localOffset + 8) !== e.method) throw new Error(\"zip_local\");\n const nameLength = zip.readUInt16LE(e.localOffset + 26);\n const extraLength = zip.readUInt16LE(e.localOffset + 28);\n const dataOffset = e.localOffset + 30 + nameLength + extraLength;\n const localName = decoder.decode(\n zip.subarray(e.localOffset + 30, e.localOffset + 30 + nameLength));\n if (localName !== name || extraLength > 256 ||\n dataOffset + e.compressedSize > centralOffset) throw new Error(\"zip_local\");\n const compressed = zip.subarray(dataOffset, dataOffset + e.compressedSize);\n const value = e.method === 0 ? Buffer.from(compressed) :\n zlib.inflateRawSync(compressed, { maxOutputLength: e.size });\n if (value.length !== e.size || crc32(value) !== e.crc) throw new Error(\"zip_crc\");\n logs.set(name, value);\n }\n return logs;\n};\nconst progressPrefixes = (text, operationId) => {\n const clean = text.split(/\\r?\\n/)\n .map((line) => line.replace(/^\\d{4}-\\d\\d-\\d\\d[ T]\\d\\d:\\d\\d:\\d\\dZ\\s+/, \"\"))\n .join(\"\\n\");\n const blocks = [...clean.matchAll(/\\{[^{}]*\\}/g)].map((m) => {\n try { return JSON.parse(m[0]); } catch { return null; }\n });\n if (!blocks.length || blocks.some((v) => !v)) return null;\n const groups = new Map();\n for (const row of blocks) {\n if (row.OperationId !== operationId ||\n !/^[A-Za-z0-9.-]+$/.test(row.Arch) ||\n !/^[A-Za-z0-9.-]+$/.test(row.InstallerType) ||\n typeof row.InstallerUrl !== \"string\" || typeof row.InstallerHash !== \"string\" ||\n ![\"Waiting\", \"Completed\"].includes(row.Status)) return null;\n const scope = row.Scope == null ? \"Undefined\" : String(row.Scope);\n const locale = row.Locale == null ? \"Undefined\" : String(row.Locale);\n if (!/^[A-Za-z0-9.-]+$/.test(scope) || !/^[A-Za-z0-9.-]+$/.test(locale)) return null;\n const key = `${row.Arch}-Scope_${scope}-Locale_${locale}`.toLowerCase();\n if (!groups.has(key)) groups.set(key, []);\n groups.get(key).push(row.Status);\n }\n for (const states of groups.values())\n if (states.length !== 2 || !states.includes(\"Waiting\") || !states.includes(\"Completed\"))\n return null;\n return new Set(groups.keys());\n};\nconst classifyLogs = (logs) => {\n let contentionCount = 0;\n for (const bytes of logs.values()) {\n if (bytes.length < 2 || bytes.length % 2 || bytes.length > 20000) return false;\n let zeroes = 0;\n for (let i = 1; i < bytes.length; i += 2) if (bytes[i] === 0) zeroes++;\n if (zeroes < bytes.length / 8) return false;\n const text = bytes.toString(\"utf16le\");\n if (text.includes(\"\\0\") || finding(text)) return false;\n const starts = [...text.matchAll(/^MpCmdRun: Command Line:.*$/gmi)];\n let scanCount = 0;\n for (let i = 0; i < starts.length; i++) {\n const record = text.slice(starts[i].index, starts[i + 1]?.index ?? text.length);\n const command = starts[i][0];\n if (!/(?:^|\\s)-Scan(?:\\s|$)/i.test(command)) continue;\n scanCount++;\n const results = [...record.matchAll(/^MpCmdRun\\.exe:\\s*hr\\s*=\\s*(0x[0-9a-f]+)\\.\\s*$/gmi)];\n const errors = [...record.matchAll(/^ERROR:\\s*(.+?)\\s*$/gmi)].map((m) => m[1]);\n if (results.length !== 1 || !/^MpCmdRun: End Time:/mi.test(record)) return false;\n const code = results[0][1].toLowerCase();\n if (code === \"0x0\") {\n if (errors.length) return false;\n } else if (code === \"0x8050111c\" && errors.length === 1 &&\n /^Another scan already in progress,\\s*cannot start scan$/i.test(errors[0])) {\n contentionCount++;\n } else {\n return false;\n }\n }\n if (scanCount !== 1) return false;\n }\n return contentionCount > 0;\n};\ntry {\n if (!Number.isSafeInteger(prNumber) || prNumber <= 0 ||\n !/^[0-9a-f]{40}$/.test(triggerHead)) return finish(\"invalid_target\");\n const getPull = () => github.rest.pulls.get({ owner, repo, pull_number: prNumber });\n const initial = (await getPull()).data;\n const head = String(initial.head?.sha ?? \"\");\n const labels = new Set((initial.labels ?? []).map((l) => l.name));\n evidence.headSha = head || null;\n if (initial.state !== \"open\" || head !== triggerHead) return finish(\"stale_head\");\n if (!labels.has(targetLabel)) return finish(\"inactive_label\");\n if ([...conflicts].some((l) => l !== targetLabel && labels.has(l)))\n return finish(\"conflicting_label\");\n\n const response = await github.rest.checks.listForRef({\n owner, repo, ref: head, app_id: appId, filter: \"latest\", per_page: 100,\n });\n const runs = response.data.check_runs;\n if (!Array.isArray(runs) || response.data.total_count !== runs.length || runs.length > 100)\n return finish(\"check_response_incomplete\");\n const completions = runs.filter((c) =>\n trusted(c, head) && c.name === \"10. Validation Completed\" &&\n c.status === \"completed\" && Number.isFinite(Date.parse(c.completed_at)))\n .sort((a, b) => Date.parse(b.completed_at) - Date.parse(a.completed_at) ||\n Number(b.id) - Number(a.id));\n const completion = completions[0];\n if (!completion || !boundedOutput(completion)) return finish(\"completion_missing\");\n const operationId = String(completion.external_id ?? \"\").trim();\n if (!new RegExp(`^WinGetSvc-Validation-${prNumber}-[0-9]+$`).test(operationId))\n return finish(\"completion_untrusted\");\n const blocks = [...completion.output.text.matchAll(/```json\\s*([\\s\\S]*?)```/gi)];\n let payload = null;\n try { if (blocks.length === 1) payload = JSON.parse(blocks[0][1]); } catch {}\n if (payload?.PullRequestNumber !== prNumber || payload?.OperationId !== operationId ||\n !Array.isArray(payload.Labels) || payload.Labels.length !== 1 ||\n payload.Labels[0]?.Name !== targetLabel || payload.Labels[0]?.Result !== \"TestPlan\")\n return finish(\"completion_untrusted\");\n\n const operation = runs.filter((c) =>\n trusted(c, head) && String(c.external_id ?? \"\").trim() === operationId);\n if (operation.length !== outcomes.size) return finish(\"operation_incomplete\");\n const byName = new Map();\n for (const check of operation) {\n if (!outcomes.has(check.name) || byName.has(check.name) || !boundedOutput(check))\n return finish(\"evidence_unavailable\");\n byName.set(check.name, check);\n }\n for (const [name, allowed] of outcomes) {\n const check = byName.get(name);\n if (check?.status !== \"completed\" ||\n !new RegExp(`^(?:${allowed})$`).test(String(check.conclusion).toLowerCase()))\n return finish(\"unsupported_check_outcome\");\n }\n if (finding(operation.map((c) =>\n `${c.output.title}\\n${c.output.summary}\\n${c.output.text}`).join(\"\\n\")))\n return finish(\"security_finding\");\n\n const artifact = payload.Artifacts;\n if (!artifact || artifact.OperationId !== operationId ||\n artifact.ZipFileName !== `${operationId}-artifacts.zip` ||\n !Number.isSafeInteger(artifact.ZipFileSizeBytes) ||\n artifact.ZipFileSizeBytes <= 0 || artifact.ZipFileSizeBytes > 1000000 ||\n !Number.isSafeInteger(artifact.TotalFilesCount) ||\n artifact.TotalFilesCount <= 0 || artifact.TotalFilesCount > 64 ||\n !Number.isSafeInteger(artifact.TotalSizeBytes) ||\n artifact.TotalSizeBytes <= 0 || artifact.TotalSizeBytes > 1000000 ||\n !Array.isArray(artifact.ValidationResults) ||\n !Array.isArray(artifact.InstallationLogs)) return finish(\"artifact_untrusted\");\n const declarations = [...artifact.ValidationResults, ...artifact.InstallationLogs];\n if (declarations.length !== artifact.TotalFilesCount) return finish(\"artifact_untrusted\");\n const declared = new Map();\n for (const item of declarations) {\n if (!safePath(item?.RelativePath) || item.FileName !== item.RelativePath.split(\"/\").at(-1) ||\n !Number.isSafeInteger(item.SizeBytes) || item.SizeBytes < 0 ||\n item.SizeBytes > 100000 || declared.has(item.RelativePath))\n return finish(\"artifact_untrusted\");\n declared.set(item.RelativePath, item);\n }\n if ([...declared.values()].reduce((sum, item) => sum + item.SizeBytes, 0) !==\n artifact.TotalSizeBytes) return finish(\"artifact_untrusted\");\n const analysisItems = artifact.InstallationLogs.filter((item) =>\n /^[A-Za-z0-9._-]+-MpCmdRunanalysis\\.log$/.test(item.FileName) &&\n item.RelativePath === `InstallationVerificationLogs/${item.FileName}`);\n if (!analysisItems.length || analysisItems.length > 8 ||\n analysisItems.length !== artifact.InstallationLogs.filter((item) =>\n /MpCmdRunanalysis/i.test(String(item?.FileName))).length ||\n analysisItems.some((item) => item.SizeBytes <= 0 || item.SizeBytes > 20000))\n return finish(\"artifact_untrusted\");\n const expectedPrefixes = progressPrefixes(\n byName.get(\"08. Installation Validation\").output.text, operationId);\n const logPrefixes = new Set(analysisItems.map((item) =>\n item.FileName.slice(0, -\"-MpCmdRunanalysis.log\".length).toLowerCase()));\n if (!expectedPrefixes || expectedPrefixes.size !== logPrefixes.size ||\n [...expectedPrefixes].some((prefix) => !logPrefixes.has(prefix)))\n return finish(\"record_binding_failed\");\n\n let url;\n try { url = new URL(artifact.ArtifactDownloadUrl); } catch {\n return finish(\"artifact_untrusted\");\n }\n if (url.protocol !== \"https:\" || url.hostname !== \"cdn.winget.microsoft.com\" ||\n url.port || url.username || url.password || url.search || url.hash ||\n url.pathname !== `/artifacts/${artifact.ZipFileName}`)\n return finish(\"artifact_untrusted\");\n const download = await fetch(url, {\n redirect: \"error\", signal: AbortSignal.timeout(15000),\n headers: { accept: \"application/octet-stream\" },\n });\n if (download.status !== 200 ||\n download.headers.get(\"content-type\") !== \"application/octet-stream\" ||\n Number(download.headers.get(\"content-length\")) !== artifact.ZipFileSizeBytes)\n return finish(\"artifact_download_failed\");\n const reader = download.body?.getReader();\n if (!reader) return finish(\"artifact_download_failed\");\n const chunks = []; let received = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n received += value.byteLength;\n if (received > artifact.ZipFileSizeBytes) {\n await reader.cancel(); return finish(\"artifact_download_failed\"); }\n chunks.push(Buffer.from(value));\n }\n if (received !== artifact.ZipFileSizeBytes) return finish(\"artifact_download_failed\");\n const zip = Buffer.concat(chunks, received);\n let logs;\n try {\n logs = readNamedLogs(zip, artifact, declared,\n analysisItems.map((item) => item.RelativePath));\n } catch {\n return finish(\"artifact_invalid\");\n }\n if (!classifyLogs(logs)) return finish(\"unsupported_scan_result\");\n\n const final = (await getPull()).data;\n const finalLabels = new Set((final.labels ?? []).map((l) => l.name));\n if (final.state !== \"open\" || final.head?.sha !== head ||\n !finalLabels.has(targetLabel) ||\n [...conflicts].some((l) => l !== targetLabel && finalLabels.has(l)))\n return finish(\"stale_after_classification\");\n evidence.verdict = \"transient_defender_scan_contention\";\n evidence.reasonCode = \"supported\";\n evidence.operationId = operationId;\n evidence.completionCheckId = completion.id;\n evidence.checkRunIds = operation.map((check) => check.id).sort(\n (left, right) => left - right,\n );\n evidence.facts = {\n completeTrustedOperation: true,\n allInstallationRecordsBound: true,\n allSecurityScanFailuresExactContention: true,\n transientScanContentionConfirmed: true,\n actualSecurityFindingExcluded: true,\n };\n} catch {\n finish(\"retrieval_failed\");\n} finally {\n fs.writeFileSync(outputPath, JSON.stringify(evidence));\n}\n"
- env:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
name: Skip agent when transient-security evidence is ineligible
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9)
with:
script: "const fs = require(\"fs\");\nconst path = require(\"path\");\nconst evidence = JSON.parse(\n fs.readFileSync(\n \"/tmp/gh-aw/transient-security-evidence.json\",\n \"utf8\",\n ),\n);\nif (evidence.verdict !== \"transient_defender_scan_contention\") {\n const safeOutputsPath =\n String(process.env.GH_AW_SAFE_OUTPUTS ?? \"\").trim() ||\n path.join(\n process.env.RUNNER_TEMP || \"/tmp\",\n \"gh-aw\",\n \"safeoutputs\",\n \"outputs.jsonl\",\n );\n fs.mkdirSync(path.dirname(safeOutputsPath), { recursive: true });\n fs.appendFileSync(\n safeOutputsPath,\n `${JSON.stringify({\n type: \"noop\",\n message: \"No eligible transient-security evidence is available.\",\n })}\\n`,\n );\n}\n"
- name: Seal deterministic transient-security evidence
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 (source v7)
with:
compression-level: 0
if-no-files-found: error
name: transient-security-evidence-${{ github.run_id }}-${{ github.run_attempt }}
path: /tmp/gh-aw/transient-security-evidence.json
retention-days: 1
- name: Download container images
run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.14@sha256:f7df036c86575527b61f3f7df91c4412349a12b2a74988d929eafa2999230c98 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.14@sha256:6f95e2234dd9bd6333a8ff28ccea7ecf0204acd4a09108723844dbd2bf6268c5 ghcr.io/github/gh-aw-firewall/squid:0.28.14@sha256:2ce8df3abf3e9b76e9c0cf5863da41f1ab3f89b20ad14b988806ab89e7bf2cd5 ghcr.io/github/gh-aw-mcpg:v0.4.18@sha256:85b940556a8faa4e1fdbef124bfd75f2c4ebd855a10b88a1c3b6f3e97f6f1a53 ghcr.io/github/gh-aw-node@sha256:0daa8971fa4732b647150cb6524a6b0804b68d5d24f6f58b5dd1af23bd63fb23 ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
- name: Prepare Safe Outputs Directories
run: |
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- name: Generate Safe Outputs Config
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw"
GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}"
GH_AW_SAFE_OUTPUTS_CONFIG: "{\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"post-transient-security-comment\":{\"description\":\"Post the fixed transient-security explanation to the triggering PR.\",\"inputs\":{\"body\":{\"default\":null,\"description\":\"Exact body without the footer\",\"required\":true,\"type\":\"string\"}},\"output\":\"Transient-security explanation posted.\"},\"report_incomplete\":{}}"
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'create_files.cjs'));
await main();
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
{
"description_suffixes": {},
"repo_params": {},
"dynamic_tools": [
{
"description": "Post the fixed transient-security explanation to the triggering PR.",
"inputSchema": {
"additionalProperties": false,
"properties": {
"body": {
"description": "Exact body without the footer",
"type": "string"
}
},
"required": [
"body"
],
"type": "object"
},
"name": "post_transient_security_comment"
}
]
}
GH_AW_VALIDATION_JSON: |
{
"noop": {
"defaultMax": 1,
"fields": {
"message": {
"required": true,
"type": "string",
"sanitize": true,
"maxLength": 65000
}
}
},
"report_incomplete": {
"defaultMax": 5,
"fields": {
"details": {
"type": "string",
"sanitize": true,
"maxLength": 65000
},
"reason": {
"required": true,
"type": "string",
"sanitize": true,
"maxLength": 1024
}
}
}
}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'generate_safe_outputs_tools.cjs'));
await main();
- name: Start MCP Gateway
id: start-mcp-gateway
env:
GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }}
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }}
GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }}
GH_AW_SINK_VISIBILITY: ${{ steps.determine-automatic-lockdown.outputs.visibility }}
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"
if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -r "${GITHUB_EVENT_PATH}" ]; then
GH_AW_SAFEOUTPUTS_EVENT_PATH="${RUNNER_TEMP}/gh-aw/safeoutputs/github_event.json"
cp "${GITHUB_EVENT_PATH}" "${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
export GITHUB_EVENT_PATH="${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
fi
# Export gateway environment variables for MCP config and gateway script
export MCP_GATEWAY_PORT="8080"
export MCP_GATEWAY_DOMAIN="awmg-mcpg"
export MCP_GATEWAY_HOST_DOMAIN="localhost"
MCP_GATEWAY_AGENT_ID=$(openssl rand -base64 45 | tr -d '/+=')
echo "::add-mask::${MCP_GATEWAY_AGENT_ID}"
export MCP_GATEWAY_AGENT_ID
export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
export MCP_GATEWAY_ALLOWED_MOUNT_ROOTS="${GITHUB_WORKSPACE}:rw,${RUNNER_TEMP}/gh-aw:ro,${RUNNER_TEMP}/gh-aw/safeoutputs:rw,/opt:ro,/tmp:rw,/usr/bin/gh:ro"
export GH_AW_PR_HEAD_BASE_BRANCH="${GH_AW_PR_HEAD_BASE_BRANCH:-}"
export GH_AW_PR_HEAD_BASE_SHA="${GH_AW_PR_HEAD_BASE_SHA:-}"
export GH_AW_PR_HEAD_BASE_REPO="${GH_AW_PR_HEAD_BASE_REPO:-}"
export GH_AW_PR_HEAD_BASE_PR_NUMBER="${GH_AW_PR_HEAD_BASE_PR_NUMBER:-}"
export GH_AW_PR_HEAD_BASE_REF="${GH_AW_PR_HEAD_BASE_REF:-}"
export GH_AW_PR_HEAD_REPO="${GH_AW_PR_HEAD_REPO:-}"
export DEBUG="*"
export GH_AW_ENGINE="copilot"
MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0')
MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0')
source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh"
export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_AGENT_ID -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_PR_HEAD_BASE_BRANCH -e GH_AW_PR_HEAD_BASE_SHA -e GH_AW_PR_HEAD_BASE_REPO -e GH_AW_PR_HEAD_BASE_PR_NUMBER -e GH_AW_PR_HEAD_BASE_REF -e GH_AW_PR_HEAD_REPO -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GH_AW_SINK_VISIBILITY -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -e RUNNER_TOOL_CACHE -e MCP_GATEWAY_ALLOWED_MOUNT_ROOTS -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.18'
mkdir -p "$HOME/.copilot"
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
cat << GH_AW_MCP_CONFIG_c5c8d9e37ec43bcb_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
"type": "stdio",
"container": "ghcr.io/github/github-mcp-server:v1.11.0",
"env": {
"GITHUB_FEATURES": "fields_param",
"GITHUB_HOST": "${GITHUB_SERVER_URL}",
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}",
"GITHUB_READ_ONLY": "1",
"GITHUB_TOOLSETS": "context,repos,issues,pull_requests"
},
"guard-policies": {
"allow-only": {
"approval-labels": ${{ steps.parse-guard-vars.outputs.approval_labels }},
"blocked-users": ${{ steps.parse-guard-vars.outputs.blocked_users }},
"min-integrity": "none",
"repos": [
"microsoft/winget-pkgs"
],
"trusted-users": ${{ steps.parse-guard-vars.outputs.trusted_users }}
}
}
},
"safeoutputs": {
"type": "stdio",
"container": "ghcr.io/github/gh-aw-node",
"mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"],
"args": ["-w", "\${GITHUB_WORKSPACE}"],
"entrypoint": "sh",
"entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"],
"env": {
"DEBUG": "*",
"DEFAULT_BRANCH": "\${DEFAULT_BRANCH}",
"GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}",
"GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}",
"GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}",
"GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}",
"GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}",
"GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}",
"GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}",
"GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}",
"GH_AW_PR_HEAD_BASE_BRANCH": "\${GH_AW_PR_HEAD_BASE_BRANCH}",
"GH_AW_PR_HEAD_BASE_SHA": "\${GH_AW_PR_HEAD_BASE_SHA}",
"GH_AW_PR_HEAD_BASE_REPO": "\${GH_AW_PR_HEAD_BASE_REPO}",
"GH_AW_PR_HEAD_BASE_PR_NUMBER": "\${GH_AW_PR_HEAD_BASE_PR_NUMBER}",
"GH_AW_PR_HEAD_BASE_REF": "\${GH_AW_PR_HEAD_BASE_REF}",
"GH_AW_PR_HEAD_REPO": "\${GH_AW_PR_HEAD_REPO}",
"GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}",
"GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}",
"GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
"GITHUB_SHA": "\${GITHUB_SHA}",
"GITHUB_TOKEN": "\${GITHUB_TOKEN}",
"GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}",
"RUNNER_TEMP": "\${RUNNER_TEMP}"
},
"guard-policies": {
"write-sink": {
"accept": [
"private:microsoft/winget-pkgs"
],
"sink-visibility": "${GH_AW_SINK_VISIBILITY}"
}
}
}
},
"gateway": {
"port": $MCP_GATEWAY_PORT,
"domain": "${MCP_GATEWAY_DOMAIN}",
"agentId": "${MCP_GATEWAY_AGENT_ID}",
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}",
"startupTimeout": 120,
"opentelemetry": {
"endpoint": "${OTEL_EXPORTER_OTLP_ENDPOINT}",
"traceId": "${GITHUB_AW_OTEL_TRACE_ID}",
"spanId": "${GITHUB_AW_OTEL_PARENT_SPAN_ID}"
}
}
}
GH_AW_MCP_CONFIG_c5c8d9e37ec43bcb_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
env:
MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }}
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io);
const { main } = require(path.join(actionsDir, 'mount_mcp_as_cli.cjs'));
await main();
- name: Clean credentials
continue-on-error: true
run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh"
- name: Audit pre-agent workspace
id: pre_agent_audit
continue-on-error: true
run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh"
- name: Execute GitHub Copilot CLI
id: agentic_execution
# Copilot CLI tool arguments (sorted):
# --allow-tool github
# --allow-tool safeoutputs
# --allow-tool shell(cat)
# --allow-tool shell(date)
# --allow-tool shell(echo)
# --allow-tool shell(github:*)
# --allow-tool shell(grep)
# --allow-tool shell(head)
# --allow-tool shell(ls)
# --allow-tool shell(printf)
# --allow-tool shell(pwd)
# --allow-tool shell(safeoutputs:*)
# --allow-tool shell(sort)
# --allow-tool shell(tail)
# --allow-tool shell(uniq)
# --allow-tool shell(wc)
# --allow-tool shell(yq)
# --allow-tool write
timeout-minutes: ${{ fromJSON(vars.GH_AW_DEFAULT_TIMEOUT_MINUTES || '20') }}
run: |
set -o pipefail
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"; if [ "$gh_aw_exit_code" -ne 0 ]; then echo "::error::Agent execution exited with code $gh_aw_exit_code"; fi' EXIT
mkdir -p "$HOME/.copilot"
printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
export XDG_CONFIG_HOME="$HOME"
export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json"
GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
exit 127
fi
GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
fi
chmod 755 "$GH_AW_COPILOT_BIN"
touch /tmp/gh-aw/agent-step-summary.md
GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
export GH_AW_NODE_BIN
export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
(umask 177 && touch /tmp/gh-aw/agent-stdio.log)
GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}"
if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
GH_AW_MAX_AI_CREDITS="1000"
fi
printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.14/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"cdn.winget.microsoft.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.14,squid=sha256:2ce8df3abf3e9b76e9c0cf5863da41f1ab3f89b20ad14b988806ab89e7bf2cd5,agent=sha256:f7df036c86575527b61f3f7df91c4412349a12b2a74988d929eafa2999230c98,api-proxy=sha256:6f95e2234dd9bd6333a8ff28ccea7ecf0204acd4a09108723844dbd2bf6268c5,cli-proxy=sha256:3a379c5e96e29499c815e9dd2a71334d01c326a9b73991c76544fda9cae35c34\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
GH_AW_DOCKER_HOST=""
if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
GH_AW_DOCKER_HOST="${DOCKER_HOST}"
fi
if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
GH_AW_CHROOT_BINARIES_SOURCE_PATH="${RUNNER_TEMP}/gh-aw" GH_AW_CHROOT_IDENTITY_HOME="${RUNNER_TEMP}/gh-aw/home" node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs"
fi
GH_AW_TOOL_CACHE_MOUNT=""
GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
if [ -d "$GH_AW_TOOL_CACHE" ]; then
if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
fi
fi
# shellcheck disable=SC1003,SC2016,SC2086
GH_AW_AWF_ENGINE_NAME=copilot \
GH_AW_AWF_HARNESS_MARKER='[copilot-harness]' \
GH_AW_AWF_LOG_FILE=/tmp/gh-aw/agent-stdio.log \
GH_AW_AWF_ATTEMPT_LOG_NAME=copilot \
bash "${RUNNER_TEMP}/gh-aw/actions/run_awf_with_startup_retries.sh" -- \
awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_AGENT_ID --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull \
-- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; GH_AW_TOOL_BINS="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')"; GH_AW_TOOL_BINS="${GH_AW_TOOL_BINS%:}"; export PATH="$PATH${GH_AW_TOOL_BINS:+:}$GH_AW_TOOL_BINS"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" "${RUNNER_TEMP}/gh-aw/bin/copilot" --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(github:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt'
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
GH_AW_PHASE: agent
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_TIMEOUT_MINUTES: ${{ fromJSON(vars.GH_AW_DEFAULT_TIMEOUT_MINUTES || '20') }}
GH_AW_VERSION: v0.88.7
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
GITHUB_HEAD_REF: ${{ github.head_ref }}
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
GITHUB_REF_NAME: ${{ github.ref_name }}
GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md
GITHUB_WORKSPACE: ${{ github.workspace }}
GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com
GIT_AUTHOR_NAME: github-actions[bot]
GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com
GIT_COMMITTER_NAME: github-actions[bot]
RUNNER_TEMP: ${{ runner.temp }}
S2STOKENS: true
TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }}
- name: Detect agent errors
if: always()
id: detect-agent-errors
continue-on-error: true
env:
GH_AW_AGENTIC_EXECUTION_OUTCOME: ${{ steps.agentic_execution.outcome }}
GH_AW_ENGINE_STEP_TIMEOUT_MINUTES: ${{ fromJSON(vars.GH_AW_DEFAULT_TIMEOUT_MINUTES || '20') }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'detect_agent_errors.cjs'));
await main();
- 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_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID"
- name: Redact secrets in logs
if: always()
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'redact_secrets.cjs'));
await main();
env:
GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Append agent step summary
if: always()
run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh"
- name: Copy Safe Outputs
if: always()
env:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
run: |
mkdir -p /tmp/gh-aw
cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true
- name: Ingest agent output
id: collect_output
if: always()
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
GH_AW_ALLOWED_DOMAINS: "api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,cdn.winget.microsoft.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,s.symcb.com,s.symcd.com,security.ubuntu.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 path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'collect_ndjson_output.cjs'));
await main();
- name: Parse agent logs for step summary
if: always()
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'parse_copilot_log.cjs'));
await main();
- name: Parse MCP Gateway logs for step summary
if: always()
id: parse-mcp-gateway
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'parse_mcp_gateway_log.cjs'));
await main();
- name: Print firewall logs
if: always()
continue-on-error: true
env:
AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs
run: bash "${RUNNER_TEMP}/gh-aw/actions/print_firewall_logs.sh" --rootless
- name: Parse token usage for step summary
if: always()
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
await main();
- name: Print AWF reflect summary
if: always()
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'awf_reflect_summary.cjs'));
await main();
- name: Generate observability summary
if: always()
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, '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
# Small dedicated copy of the agent output so safe-output processing
# survives a failed or timed-out upload of the larger agent artifact
- name: Upload agent output fallback artifact
if: always()
continue-on-error: true
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: agent-output-fallback
path: |
/tmp/gh-aw/agent_output.json
/tmp/gh-aw/safeoutputs.jsonl
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/proxy-logs/
!/tmp/gh-aw/proxy-logs/proxy-tls/
/tmp/gh-aw/agent_usage.json
/tmp/gh-aw/agent-stdio.log
/tmp/gh-aw/pre-agent-audit.txt
/tmp/gh-aw/github_rate_limits.jsonl
/tmp/gh-aw/otel.jsonl
/tmp/gh-aw/otlp-export-errors.jsonl
/tmp/gh-aw/safeoutputs.jsonl
/tmp/gh-aw/agent_output.json
/tmp/gh-aw/aw-*.patch
/tmp/gh-aw/aw-*.bundle
/tmp/gh-aw/awf-config.json
/tmp/gh-aw/sandbox/firewall/logs/
/tmp/gh-aw/sandbox/firewall/audit/
/tmp/gh-aw/sandbox/firewall/awf-reflect.json
if-no-files-found: ignore
conclusion:
needs:
- activation
- agent
- detection
- post_transient_security_comment
if: >
always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' ||
needs.activation.outputs.daily_ai_credits_exceeded == 'true')
runs-on: ubuntu-slim
permissions:
actions: write
concurrency:
group: "gh-aw-conclusion-transient-security-explanation"
cancel-in-progress: false
queue: max
env:
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
outputs:
incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }}
noop_message: ${{ steps.noop.outputs.noop_message }}
steps:
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
trace-id: ${{ needs.activation.outputs.setup-trace-id }}
parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }}
env:
GH_AW_SETUP_WORKFLOW_NAME: "Transient Security Explanation"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/transient-security-explanation.lock.yml@${{ github.ref }}
GH_AW_INFO_VERSION: "1.0.80"
GH_AW_INFO_AWF_VERSION: "v0.28.14"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: "{agent,agent-output-fallback}"
merge-multiple: true
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
if [ -f "/tmp/gh-aw/agent_output.json" ]; then
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
fi
- name: Download detection artifact
id: download-detection-artifact
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: detection
path: /tmp/gh-aw/threat-detection/
- name: Download Safe Outputs Items Manifest
id: download-safe-outputs-manifest
if: always()
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: safe-outputs-items
merge-multiple: true
path: /tmp/gh-aw/
- name: Collect usage artifact files
if: always()
continue-on-error: true
run: bash "${RUNNER_TEMP}/gh-aw/actions/collect_usage_artifact_files.sh"
- name: Upload usage artifact
if: always()
continue-on-error: true
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: usage
path: |
/tmp/gh-aw/usage/aw_info.json
/tmp/gh-aw/usage/aw-info.jsonl
/tmp/gh-aw/usage/agent_usage.json
/tmp/gh-aw/usage/agent_usage.jsonl
/tmp/gh-aw/usage/detection_usage.jsonl
/tmp/gh-aw/usage/evals.jsonl
/tmp/gh-aw/usage/graders/grader_manifest.json
/tmp/gh-aw/usage/graders/grader_results.json
/tmp/gh-aw/usage/github_rate_limits.jsonl
/tmp/gh-aw/usage/agent/token_usage.jsonl
/tmp/gh-aw/usage/detection/token_usage.jsonl
/tmp/gh-aw/usage/activity/summary.json
if-no-files-found: ignore
- name: Restore daily AIC usage cache
id: restore-daily-aic-cache-conclusion
if: always()
continue-on-error: true
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
key: agentic-workflow-usage-transientsecurityexplanation-${{ github.run_id }}
restore-keys: agentic-workflow-usage-transientsecurityexplanation-
path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl
- name: Write daily AIC usage cache entry
id: write-daily-aic-cache
if: always()
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ github.token }}
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context);
const { main } = require(path.join(actionsDir, 'write_daily_aic_usage_cache.cjs'));
await main();
- name: Save daily AIC usage cache
id: save-daily-aic-cache
if: always()
continue-on-error: true
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
key: agentic-workflow-usage-transientsecurityexplanation-${{ github.run_id }}
path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl
- name: Upload daily AIC usage cache artifact
id: upload-daily-aic-cache
if: always()
continue-on-error: true
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: aic-usage-cache
path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl
if-no-files-found: ignore
retention-days: 7
- name: Process no-op messages
id: noop
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_NOOP_MAX: "1"
GH_AW_WORKFLOW_NAME: "Transient Security Explanation"
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/transient-security-explanation.md"
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
GH_AW_NOOP_REPORT_AS_ISSUE: "false"
GH_AW_AIC: ${{ needs.agent.outputs.aic }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }}
GH_AW_WORKFLOW_ID: "transient-security-explanation"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'handle_noop_message.cjs'));
await main();
- name: Log detection run
id: detection_runs
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_WORKFLOW_NAME: "Transient Security Explanation"
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/transient-security-explanation.md"
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }}
GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }}
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'handle_detection_runs.cjs'));
await main();
- name: Record incomplete
id: report_incomplete
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "false"
GH_AW_REPORT_INCOMPLETE_TITLE_PREFIX: "[incomplete]"
GH_AW_WORKFLOW_NAME: "Transient Security Explanation"
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/transient-security-explanation.md"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'report_incomplete_handler.cjs'));
await main();
- name: Handle agent failure
id: handle_agent_failure
if: always()
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
GH_AW_WORKFLOW_NAME: "Transient Security Explanation"
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/transient-security-explanation.md"
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
GH_AW_WORKFLOW_ID: "transient-security-explanation"
GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "0"
GH_AW_ENGINE_ID: "copilot"
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }}
GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }}
GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }}
GH_AW_AIC: ${{ needs.agent.outputs.aic }}
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }}
GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }}
GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }}
GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }}
GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }}
GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.agent.outputs.max_cache_misses_exceeded }}
GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.agent.outputs.missing_model_pricing_error }}
GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.agent.outputs.missing_model_pricing_model_name }}
GH_AW_SHELL_EXPANSION_GUARD_REJECTED: ${{ needs.agent.outputs.shell_expansion_guard_rejected }}
GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com"
GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
GH_AW_OAUTH_TOKEN_CHECK_FAILED: ${{ needs.activation.outputs.oauth_token_check_failed }}
GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }}
GH_AW_DAILY_AI_CREDITS_EXCEEDED: ${{ needs.activation.outputs.daily_ai_credits_exceeded }}
GH_AW_DAILY_AI_CREDITS_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_ai_credits_total_effective_tokens }}
GH_AW_DAILY_AI_CREDITS_THRESHOLD: ${{ needs.activation.outputs.daily_ai_credits_threshold }}
GH_AW_GROUP_REPORTS: "false"
GH_AW_FAILURE_REPORT_AS_ISSUE: "false"
GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true"
GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true"
GH_AW_TIMEOUT_MINUTES: "${{ fromJSON(vars.GH_AW_DEFAULT_TIMEOUT_MINUTES || '20') }}"
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'handle_agent_failure.cjs'));
await main();
detection:
needs:
- activation
- agent
if: always() && needs.agent.result != 'skipped'
runs-on: ubuntu-latest
permissions:
contents: read
copilot-requests: write
timeout-minutes: 10
env:
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
outputs:
aic: ${{ steps.parse_detection_token_usage.outputs.aic }}
detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }}
detection_reason: ${{ steps.detection_conclusion.outputs.reason }}
detection_success: ${{ steps.detection_conclusion.outputs.success }}
steps:
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
trace-id: ${{ needs.activation.outputs.setup-trace-id }}
parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }}
env:
GH_AW_SETUP_WORKFLOW_NAME: "Transient Security Explanation"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/transient-security-explanation.lock.yml@${{ github.ref }}
GH_AW_INFO_VERSION: "1.0.80"
GH_AW_INFO_AWF_VERSION: "v0.28.14"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Download activation artifact
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: activation
path: /tmp/gh-aw
- name: Download agent output artifact
id: download-agent-output
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: "{agent,agent-output-fallback}"
merge-multiple: true
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
if [ -f "/tmp/gh-aw/agent_output.json" ]; then
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
fi
- name: Checkout repository for patch context
if: needs.agent.outputs.has_patch == 'true'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
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.28.14@sha256:f7df036c86575527b61f3f7df91c4412349a12b2a74988d929eafa2999230c98 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.14@sha256:6f95e2234dd9bd6333a8ff28ccea7ecf0204acd4a09108723844dbd2bf6268c5 ghcr.io/github/gh-aw-firewall/squid:0.28.14@sha256:2ce8df3abf3e9b76e9c0cf5863da41f1ab3f89b20ad14b988806ab89e7bf2cd5
- name: Check if detection needed
id: detection_guard
if: always()
env:
OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }}
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
run: |
if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then
echo "run_detection=true" >> "$GITHUB_OUTPUT"
echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH"
else
echo "run_detection=false" >> "$GITHUB_OUTPUT"
echo "Detection skipped: no agent outputs or patches to analyze"
fi
- name: Clear MCP Config for detection
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json"
rm -f "$HOME/.copilot/mcp-config.json"
rm -f "$GITHUB_WORKSPACE/.gemini/settings.json"
- name: Prepare threat detection files
if: always() && steps.detection_guard.outputs.run_detection == 'true'
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/prepare_threat_detection_files.sh"
- name: Setup threat detection
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
WORKFLOW_NAME: "Transient Security Explanation"
WORKFLOW_DESCRIPTION: "Closed-world author assist for one confirmed Defender scan contention."
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
GH_AW_DETECTION_SKIP_PROMPT_SUMMARY: "true"
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, '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: Install AWF binary
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.14 --rootless
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'
package-manager-cache: false
- name: Install GitHub Copilot CLI
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
GH_AW_COMPILED_VERSION: v0.88.7
- name: Install threat-detect binary
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/install_threat_detect_binary.sh" v0.5.1
- name: Execute threat detection with AWF
id: detection_agentic_execution
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
timeout-minutes: 10
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
COPILOT_GITHUB_TOKEN: ${{ github.token }}
COPILOT_MODEL: detection
GH_AW_HARNESS_MAX_RETRIES: 0
GH_AW_LLM_PROVIDER: github
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
GH_AW_PHASE: detection
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_TIMEOUT_MINUTES: 10
GH_AW_VERSION: v0.88.7
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
GITHUB_HEAD_REF: ${{ github.head_ref }}
GITHUB_REF_NAME: ${{ github.ref_name }}
GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md
GITHUB_WORKSPACE: ${{ github.workspace }}
GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com
GIT_AUTHOR_NAME: github-actions[bot]
GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com
GIT_COMMITTER_NAME: github-actions[bot]
RUNNER_TEMP: ${{ runner.temp }}
S2STOKENS: true
TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }}
WORKFLOW_NAME: "Transient Security Explanation"
WORKFLOW_DESCRIPTION: "Closed-world author assist for one confirmed Defender scan contention."
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
run: |
set -o pipefail
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
exit 127
fi
GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
fi
chmod 755 "$GH_AW_COPILOT_BIN"
(umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
GH_AW_MAX_AI_CREDITS="400"
fi
printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.14/awf-config.schema.json\",\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.14,squid=sha256:2ce8df3abf3e9b76e9c0cf5863da41f1ab3f89b20ad14b988806ab89e7bf2cd5,agent=sha256:f7df036c86575527b61f3f7df91c4412349a12b2a74988d929eafa2999230c98,api-proxy=sha256:6f95e2234dd9bd6333a8ff28ccea7ecf0204acd4a09108723844dbd2bf6268c5,cli-proxy=sha256:3a379c5e96e29499c815e9dd2a71334d01c326a9b73991c76544fda9cae35c34\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
GH_AW_DOCKER_HOST=""
if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
GH_AW_DOCKER_HOST="${DOCKER_HOST}"
fi
if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
_GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
fi
GH_AW_TOOL_CACHE_MOUNT=""
GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
if [ -d "$GH_AW_TOOL_CACHE" ]; then
if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
fi
fi
# shellcheck disable=SC1003,SC2016,SC2086
awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --mount /tmp/gh-aw:/tmp/gh-aw:rw --mount /tmp/gh-aw/threat-detection:/tmp/gh-aw/threat-detection:rw --log-level info --skip-pull \
-- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; GH_AW_TOOL_BINS="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')"; GH_AW_TOOL_BINS="${GH_AW_TOOL_BINS%:}"; export PATH="$PATH${GH_AW_TOOL_BINS:+:}$GH_AW_TOOL_BINS"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && threat-detect --engine copilot --output /tmp/gh-aw/threat-detection/detection_result.json /tmp/gh-aw/threat-detection' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
- name: Render detection log
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'render_detection_log.cjs'));
await main();
- name: Copy detection firewall logs
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
run: |
mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall
if [ -d /tmp/gh-aw/sandbox/firewall/logs ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/logs && cp -r /tmp/gh-aw/sandbox/firewall/logs/. /tmp/gh-aw/threat-detection/sandbox/firewall/logs/; fi
if [ -d /tmp/gh-aw/sandbox/firewall/audit ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/audit && cp -r /tmp/gh-aw/sandbox/firewall/audit/. /tmp/gh-aw/threat-detection/sandbox/firewall/audit/; fi
- name: Upload threat detection artifact
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_result.json
/tmp/gh-aw/threat-detection/sandbox/firewall/logs/
/tmp/gh-aw/threat-detection/sandbox/firewall/audit/
if-no-files-found: ignore
- name: Parse threat detection token usage for step summary
id: parse_detection_token_usage
if: always()
continue-on-error: true
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
with:
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
await main();
- name: Conclude threat detection
id: detection_conclusion
if: always()
continue-on-error: true
env:
RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }}
GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/conclude_threat_detection.sh" /tmp/gh-aw/threat-detection/detection_result.json
post_transient_security_comment:
needs:
- agent
- detection
if: >
(!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'post_transient_security_comment') &&
(needs.detection.result == 'success' && needs.detection.outputs.detection_success == 'true')
runs-on: ubuntu-slim
permissions:
checks: read
pull-requests: write
steps:
- name: Download agent output artifact
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: "{agent,agent-output-fallback}"
merge-multiple: true
path: ${{ runner.temp }}/gh-aw/safe-jobs/
- name: Download sealed transient-security evidence
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 (source v8)
env:
GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json
with:
name: transient-security-evidence-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/gh-aw/trusted-evidence
- name: Validate and post fixed-target comment
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9)
env:
EVIDENCE_ARTIFACT: transient-security-evidence-${{ github.run_id }}-${{ github.run_attempt }}
EVIDENCE_DIR: ${{ runner.temp }}/gh-aw/trusted-evidence
GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json
with:
github-token: ${{ github.token }}
script: |
const fs = require("fs");
const owner = "microsoft", repo = "winget-pkgs";
const label = "Validation-Defender-Error", footer = "###### Template: msftbot/authorAssist/transientSecurity";
const appId = 1451866, appSlug = "wingetvalidator-prod";
const outcomes = new Map([
["01. Pull Request Validation", "success"],
["02. Manifest Validation", "success"],
["03. URLs Validation", "success"],
["04. URL Domain Validation", "success"],
["05. Manifest Policy Validation", "success"],
["06. Catalog Content Verification", "success"],
["07. Installers Scan", "success"],
["08. Installation Validation", "failure|action_required"],
["09. Installer Metadata Validation", "skipped"],
["10. Validation Completed", "success"],
]);
const conflicts = new Set([
"Binary-Validation-Error", "Blocking-Issue",
"Error-Analysis-Timeout", "Error-Hash-Mismatch",
"Internal-Error", "Internal-Error-AppsAndFeaturesVersion",
"Internal-Error-Dependencies", "Internal-Error-Domain",
"Internal-Error-Dynamic-Scan", "Internal-Error-Keyword-Policy",
"Internal-Error-Manifest", "Internal-Error-Manifest-Installer",
"Internal-Error-NoArchitectures",
"Internal-Error-NoSupportedArchitectures", "Internal-Error-PR",
"Internal-Error-Static-Scan", "Internal-Error-URL",
"Internal-Error-Webhook", "Needs-SmartScreen-Investigation",
"Network-Blocker", "Package-Flagged", "PUA-Detection",
"PullRequest-Error", "Scripted-Application",
"URL-Validation-Error", "Validation-Certificate-Root",
"Validation-Defender-Error", "Validation-Executable-Error",
"Validation-Hash-Flagged", "Validation-Hash-Verification-Failed",
"Validation-HTTP-Error", "Validation-No-Executables",
"Validation-Shell-Execute", "Validation-SmartScreen",
"Validation-SmartScreen-Error", "Validation-Submission-Expired",
"Validation-Submission-Failed",
"Validation-Submission-Mismatch",
"Validation-Submission-Missing",
"Validation-Submission-Unsupported",
"Validation-Virus-Scan-Error",
]);
const trusted = (check, head) =>
check?.app?.id === appId &&
check?.app?.slug === appSlug &&
check?.head_sha === head;
const boundedOutput = (check) => {
const output = check?.output;
const skippedText =
check?.name === "09. Installer Metadata Validation" &&
(output?.text === null || output?.text === "");
const values = [
output?.title,
output?.summary,
skippedText ? "" : output?.text,
];
return values.every((value) => typeof value === "string") &&
output.title.length > 0 && output.summary.length > 0 &&
output.title.length <= 1000 &&
output.summary.length <= 10000 &&
values[2].length <= 30000 &&
(skippedText || values[2].length > 0) &&
!/\b(?:output (?:was )?truncated|truncated due to)\b|\[\s*(?:output\s+)?truncated\s*\]/i
.test(values.join("\n"));
};
const finding = (text) => [
/\b(?:detected|found|blocked|quarantined)\b.{0,100}\b(?:threat|malware|virus|trojan|pua)\b/is,
/\b(?:threat|malware|virus|trojan|pua)\b.{0,100}\b(?:detected|found|blocked|quarantined)\b/is,
/\bsmartscreen\b.{0,100}\b(?:blocked|malicious|unsafe|warning|failed)\b/is,
/\b(?:hash|signature|integrity)\b.{0,100}\b(?:mismatch|invalid|tampered|compromised)\b/is,
].some((pattern) => pattern.test(text));
const parseCompletion = (check) => {
if (!boundedOutput(check)) return null;
const blocks = [
...check.output.text.matchAll(
/```json\s*([\s\S]*?)```/gi,
),
];
try {
return blocks.length === 1
? JSON.parse(blocks[0][1]) : null;
} catch {
return null;
}
};
const eventPr = context.payload.pull_request;
const prNumber = Number(eventPr?.number), eventHead = String(eventPr?.head?.sha ?? "");
if (context.eventName !== "pull_request_target" || context.payload.action !== "labeled" ||
context.payload.label?.name !== label || context.repo.owner !== owner ||
context.actor !== "wingetvalidator-prod[bot]" || context.repo.repo !== repo ||
!Number.isSafeInteger(prNumber) || prNumber <= 0 ||
!/^[0-9a-f]{40}$/.test(eventHead)) return;
const evidenceArtifact = process.env.EVIDENCE_ARTIFACT;
const expectedArtifact =
`transient-security-evidence-${process.env.GITHUB_RUN_ID}-${process.env.GITHUB_RUN_ATTEMPT}`;
const evidenceDir = process.env.EVIDENCE_DIR;
if (evidenceArtifact !== expectedArtifact || !evidenceDir ||
!fs.existsSync(evidenceDir)) return;
const entries = fs.readdirSync(evidenceDir, { withFileTypes: true });
if (entries.length !== 1 || !entries[0].isFile() ||
entries[0].name !== "transient-security-evidence.json") return;
const evidenceFile = `${evidenceDir}/transient-security-evidence.json`;
const evidenceStat = fs.lstatSync(evidenceFile);
if (!evidenceStat.isFile() || evidenceStat.size < 2 || evidenceStat.size > 4096) return;
const evidence = JSON.parse(fs.readFileSync(evidenceFile, "utf8"));
const evidenceKeys = [
"schemaVersion", "verdict", "reasonCode", "pullRequestNumber",
"headSha", "operationId", "completionCheckId", "checkRunIds",
"facts",
];
const requiredFacts = [
"completeTrustedOperation", "allInstallationRecordsBound",
"allSecurityScanFailuresExactContention", "transientScanContentionConfirmed",
"actualSecurityFindingExcluded",
];
if (evidence?.schemaVersion !== 1 ||
Object.keys(evidence).length !== evidenceKeys.length ||
!evidenceKeys.every((key) => Object.hasOwn(evidence, key)) ||
evidence.verdict !== "transient_defender_scan_contention" ||
evidence.reasonCode !== "supported" || evidence.pullRequestNumber !== prNumber ||
evidence.headSha !== eventHead ||
!new RegExp(
`^WinGetSvc-Validation-${prNumber}-[0-9]+$`,
).test(evidence.operationId) ||
!Number.isSafeInteger(evidence.completionCheckId) ||
!Array.isArray(evidence.checkRunIds) ||
evidence.checkRunIds.length !== 10 ||
evidence.checkRunIds.some(
(id) => !Number.isSafeInteger(id),
) ||
new Set(evidence.checkRunIds).size !==
evidence.checkRunIds.length ||
!evidence.checkRunIds.includes(
evidence.completionCheckId,
) ||
!evidence.facts ||
Object.keys(evidence.facts).length !== requiredFacts.length ||
!requiredFacts.every((fact) => evidence.facts[fact] === true)) return;
const outputFile = process.env.GH_AW_AGENT_OUTPUT;
if (!outputFile || fs.statSync(outputFile).size > 100000) return;
const output = JSON.parse(fs.readFileSync(outputFile, "utf8"));
const items = Array.isArray(output.items) ?
output.items.filter((item) => item?.type === "post_transient_security_comment") : [];
if (items.length !== 1 || Object.keys(items[0]).some(
(key) => !["type", "body"].includes(key))) return;
const expectedBody = [
"> [!NOTE]",
"> **Temporary validation-infrastructure condition**",
">",
"> The validator reported a transient Microsoft Defender scan-contention condition. This is not a",
"> malware finding, but it is also not a successful security scan. No manifest change is indicated",
"> by this validation result. A maintainer may revalidate the pull request.",
">",
`> Head SHA: \`${eventHead}\``,
].join("\n");
const body = items[0].body;
if (typeof body !== "string" || body.length < 200 || body.length > 1200 ||
body.includes("@") || body.trim() !== expectedBody) return;
const { data: pull } = await github.rest.pulls.get({
owner, repo, pull_number: prNumber,
});
const labels = new Set((pull.labels ?? []).map((item) => item.name));
if (pull.number !== prNumber || pull.state !== "open" || pull.head?.sha !== eventHead ||
pull.user?.login === "wingetbot" || !labels.has(label) ||
[...conflicts].some((item) => item !== label && labels.has(item))) return;
const comments = []; let complete = false;
for (let page = 1; page <= 10; page++) {
const result = await github.rest.issues.listComments(
{ owner, repo, issue_number: prNumber, per_page: 100, page });
comments.push(...result.data);
if (result.data.length < 100) { complete = true; break; }
}
if (!complete || comments.some((comment) => String(comment.body ?? "").includes(footer) &&
String(comment.body ?? "").includes(`Head SHA: \`${eventHead}\``))) return;
const { data: finalPull } = await github.rest.pulls.get({
owner, repo, pull_number: prNumber,
});
const finalLabels = new Set(
(finalPull.labels ?? []).map((item) => item.name),
);
if (
finalPull.state !== "open" ||
finalPull.head?.sha !== eventHead ||
!finalLabels.has(label) ||
[...conflicts].some(
(item) => item !== label && finalLabels.has(item),
)
) return;
// Keep trusted Checks as the final evidence read before posting.
const response = await github.rest.checks.listForRef({
owner, repo, ref: eventHead, app_id: appId,
filter: "all", per_page: 100, page: 1,
});
const runs = response.data?.check_runs ?? [];
if (
response.data?.total_count !== runs.length ||
runs.length > 100
) return;
const trustedRuns = runs.filter(
(check) => trusted(check, eventHead),
);
const operationPattern = new RegExp(
`^WinGetSvc-Validation-${prNumber}-([0-9]+)$`,
);
const selectedSequence = operationPattern.exec(
evidence.operationId,
);
const operationSequences = trustedRuns.map((check) => {
const match = operationPattern.exec(
String(check.external_id ?? "").trim(),
);
return match ? BigInt(match[1]) : null;
});
if (
!selectedSequence ||
operationSequences.some(
(sequence) =>
sequence === null ||
sequence > BigInt(selectedSequence[1]),
)
) return;
const completions = trustedRuns.filter(
(check) =>
check.name === "10. Validation Completed" &&
check.status === "completed" &&
Number.isSafeInteger(check.id) &&
Number.isFinite(Date.parse(check.completed_at ?? "")),
).sort(
(left, right) =>
Date.parse(right.completed_at) -
Date.parse(left.completed_at) ||
right.id - left.id,
);
const completion = completions[0];
const payload = parseCompletion(completion);
const completionTime = Date.parse(
completion?.completed_at ?? "",
);
const newerTrustedRun = trustedRuns.some(
(check) =>
check.id !== completion?.id &&
(
(
["queued", "in_progress"].includes(check.status) &&
String(check.external_id ?? "").trim() !==
evidence.operationId
) ||
!Number.isSafeInteger(check.id) ||
check.id > completion?.id ||
Date.parse(check.started_at ?? "") > completionTime
),
);
const operation = trustedRuns.filter(
(check) =>
String(check.external_id ?? "").trim() ===
evidence.operationId,
);
const freshIds = operation.map((check) => check.id).sort(
(left, right) => left - right,
);
const byName = new Map();
for (const check of operation) {
if (
!outcomes.has(check.name) ||
byName.has(check.name) ||
!boundedOutput(check)
) return;
byName.set(check.name, check);
}
if (
!completion ||
completion.id !== evidence.completionCheckId ||
completion.conclusion !== "success" ||
String(completion.external_id ?? "").trim() !==
evidence.operationId ||
payload?.PullRequestNumber !== prNumber ||
payload?.OperationId !== evidence.operationId ||
!Array.isArray(payload?.Labels) ||
payload.Labels.length !== 1 ||
payload.Labels[0]?.Name !== label ||
payload.Labels[0]?.Result !== "TestPlan" ||
newerTrustedRun ||
operation.length !== outcomes.size ||
freshIds.join(",") !== evidence.checkRunIds.join(",")
) return;
for (const [name, allowed] of outcomes) {
const check = byName.get(name);
if (
check?.status !== "completed" ||
!new RegExp(`^(?:${allowed})$`).test(
String(check.conclusion).toLowerCase(),
)
) return;
}
if (finding(operation.map((check) =>
`${check.output.title}\n${check.output.summary}\n` +
check.output.text).join("\n"))) return;
if (process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true") return;
await github.rest.issues.createComment({
owner, repo, issue_number: prNumber, body: `${expectedBody}\n\n${footer}`,
});
pre_activation:
if: >
github.event_name == 'pull_request_target' && github.event.action == 'labeled' && github.event.label.name == 'Validation-Defender-Error' &&
github.actor == 'wingetvalidator-prod[bot]' && github.event.pull_request.user.login != 'wingetbot'
runs-on: ubuntu-slim
env:
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
outputs:
activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }}
matched_command: ''
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
setup-span-id: ${{ steps.setup.outputs.span-id }}
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
steps:
- name: Setup Scripts
id: setup
uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
env:
GH_AW_SETUP_WORKFLOW_NAME: "Transient Security Explanation"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/transient-security-explanation.lock.yml@${{ github.ref }}
GH_AW_INFO_VERSION: "1.0.80"
GH_AW_INFO_AWF_VERSION: "v0.28.14"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Check team membership for workflow
id: check_membership
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_REQUIRED_ROLES: "admin,maintainer,write"
GH_AW_ALLOWED_BOTS: "wingetvalidator-prod[bot]"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const path = require('path');
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require(path.join(actionsDir, 'check_membership.cjs'));
await main();