From c92ed147592e5779f7b43eeaee17940460a997b6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 1 Apr 2026 16:41:43 +0000
Subject: [PATCH 1/3] Initial plan
From 0093fdc988d0568f4678dee7db01d76c896abcaf Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 1 Apr 2026 17:17:20 +0000
Subject: [PATCH 2/3] fix: address 4 security findings from security triage
(#22914, #23737, #23739, #22908)
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/091b08a2-ecaf-4440-ac26-63ff74183d19
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
.github/workflows/contribution-check.lock.yml | 38 +++++++++----------
.github/workflows/contribution-check.md | 10 ++---
.../workflows/stale-repo-identifier.lock.yml | 38 +++++++++----------
.github/workflows/stale-repo-identifier.md | 2 +-
actions/setup/js/sanitize_content.test.cjs | 22 +++++++++++
actions/setup/js/sanitize_content_core.cjs | 18 +++++++--
.../setup/sh/convert_gateway_config_claude.sh | 3 ++
.../setup/sh/convert_gateway_config_codex.sh | 3 ++
.../sh/convert_gateway_config_copilot.sh | 3 ++
.../setup/sh/convert_gateway_config_gemini.sh | 3 ++
actions/setup/sh/setup_cache_memory_git.sh | 13 +++++++
pkg/workflow/compile_outputs_label_test.go | 4 +-
pkg/workflow/expression_safety_test.go | 25 +++++++++---
pkg/workflow/expression_safety_validation.go | 17 +++++----
pkg/workflow/expressions_benchmark_test.go | 4 --
15 files changed, 137 insertions(+), 66 deletions(-)
diff --git a/.github/workflows/contribution-check.lock.yml b/.github/workflows/contribution-check.lock.yml
index 24aa7f2ced6..268a5d202f2 100644
--- a/.github/workflows/contribution-check.lock.yml
+++ b/.github/workflows/contribution-check.lock.yml
@@ -21,7 +21,7 @@
# For more information: https://github.github.com/gh-aw/introduction/overview/
#
#
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c6a3f4c1233714c024d91fc9b1c524d86770132bae2044a11ce34b31aeaaa870","strict":true,"agent_id":"copilot"}
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"f13b66a0ab9cd27b3d07073ecf00b98766de5da3621a7e14213d1d8a0543538a","strict":true,"agent_id":"copilot"}
name: "Contribution Check"
"on":
@@ -123,7 +123,7 @@ jobs:
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl
- GH_AW_ENV_TARGET_REPOSITORY: ${{ env.TARGET_REPOSITORY }}
+ GH_AW_EXPR_CBC1CEA2: ${{ vars.TARGET_REPOSITORY || github.repository }}
GH_AW_GITHUB_ACTOR: ${{ github.actor }}
GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
@@ -136,14 +136,14 @@ jobs:
run: |
bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh
{
- cat << 'GH_AW_PROMPT_c5fafbe77bac02c3_EOF'
+ cat << 'GH_AW_PROMPT_4d53a8b50763a9ab_EOF'
- GH_AW_PROMPT_c5fafbe77bac02c3_EOF
+ GH_AW_PROMPT_4d53a8b50763a9ab_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_c5fafbe77bac02c3_EOF'
+ cat << 'GH_AW_PROMPT_4d53a8b50763a9ab_EOF'
Tools: add_comment(max:10), create_issue, add_labels(max:4), missing_tool, missing_data, noop
@@ -175,19 +175,19 @@ jobs:
{{/if}}
- GH_AW_PROMPT_c5fafbe77bac02c3_EOF
+ GH_AW_PROMPT_4d53a8b50763a9ab_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_c5fafbe77bac02c3_EOF'
+ cat << 'GH_AW_PROMPT_4d53a8b50763a9ab_EOF'
{{#runtime-import .github/workflows/contribution-check.md}}
- GH_AW_PROMPT_c5fafbe77bac02c3_EOF
+ GH_AW_PROMPT_4d53a8b50763a9ab_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_ENV_TARGET_REPOSITORY: ${{ env.TARGET_REPOSITORY }}
GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_EXPR_CBC1CEA2: ${{ vars.TARGET_REPOSITORY || github.repository }}
with:
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
@@ -198,7 +198,7 @@ jobs:
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_ENV_TARGET_REPOSITORY: ${{ env.TARGET_REPOSITORY }}
+ GH_AW_EXPR_CBC1CEA2: ${{ vars.TARGET_REPOSITORY || github.repository }}
GH_AW_GITHUB_ACTOR: ${{ github.actor }}
GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
@@ -218,7 +218,7 @@ jobs:
return await substitutePlaceholders({
file: process.env.GH_AW_PROMPT,
substitutions: {
- GH_AW_ENV_TARGET_REPOSITORY: process.env.GH_AW_ENV_TARGET_REPOSITORY,
+ GH_AW_EXPR_CBC1CEA2: process.env.GH_AW_EXPR_CBC1CEA2,
GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
@@ -344,12 +344,12 @@ jobs:
mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_6bec1d2b7f53adff_EOF'
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_3dcf271d1ce79a6a_EOF'
{"add_comment":{"hide_older_comments":true,"max":10,"target":"*","target-repo":"${{ vars.TARGET_REPOSITORY }}"},"add_labels":{"allowed":["spam","needs-work","outdated","lgtm"],"max":4,"target":"*","target-repo":"${{ vars.TARGET_REPOSITORY }}"},"create_issue":{"close_older_issues":true,"expires":24,"group_by_day":true,"labels":["contribution-report"],"max":1,"title_prefix":"[Contribution Check Report]"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"}}
- GH_AW_SAFE_OUTPUTS_CONFIG_6bec1d2b7f53adff_EOF
+ GH_AW_SAFE_OUTPUTS_CONFIG_3dcf271d1ce79a6a_EOF
- name: Write Safe Outputs Tools
run: |
- cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_d7a7258648455683_EOF'
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_449ad1767e3facb1_EOF'
{
"description_suffixes": {
"add_comment": " CONSTRAINTS: Maximum 10 comment(s) can be added. Target: *. Comments will be added in repository \"${{ vars.TARGET_REPOSITORY }}\".",
@@ -359,8 +359,8 @@ jobs:
"repo_params": {},
"dynamic_tools": []
}
- GH_AW_SAFE_OUTPUTS_TOOLS_META_d7a7258648455683_EOF
- cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_13a38a731c2dd3c1_EOF'
+ GH_AW_SAFE_OUTPUTS_TOOLS_META_449ad1767e3facb1_EOF
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_1974050a561a6269_EOF'
{
"add_comment": {
"defaultMax": 1,
@@ -490,7 +490,7 @@ jobs:
}
}
}
- GH_AW_SAFE_OUTPUTS_VALIDATION_13a38a731c2dd3c1_EOF
+ GH_AW_SAFE_OUTPUTS_VALIDATION_1974050a561a6269_EOF
node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs
- name: Generate Safe Outputs MCP Server Config
id: safe-outputs-config
@@ -556,7 +556,7 @@ jobs:
export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.11'
mkdir -p /home/runner/.copilot
- cat << GH_AW_MCP_CONFIG_2eaf8996402089a2_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
+ cat << GH_AW_MCP_CONFIG_f6ea4ab0ba43d611_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
{
"mcpServers": {
"github": {
@@ -600,7 +600,7 @@ jobs:
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
}
}
- GH_AW_MCP_CONFIG_2eaf8996402089a2_EOF
+ GH_AW_MCP_CONFIG_f6ea4ab0ba43d611_EOF
- name: Download activation artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
diff --git a/.github/workflows/contribution-check.md b/.github/workflows/contribution-check.md
index 708bee21c68..6229ed8abb4 100644
--- a/.github/workflows/contribution-check.md
+++ b/.github/workflows/contribution-check.md
@@ -39,7 +39,7 @@ safe-outputs:
## Target Repository
-The target repository is `${{ env.TARGET_REPOSITORY }}`. All PR fetching and subagent dispatch use this value.
+The target repository is `${{ vars.TARGET_REPOSITORY || github.repository }}`. All PR fetching and subagent dispatch use this value.
## Overview
@@ -49,7 +49,7 @@ You do NOT evaluate PRs yourself. You delegate each evaluation to `.github/agent
## Pre-filtered PR List
-A `pre-agent` step has already queried and filtered PRs from `${{ env.TARGET_REPOSITORY }}`. The results are in `pr-filter-results.json` at the workspace root. Read this file first. It contains:
+A `pre-agent` step has already queried and filtered PRs from `${{ vars.TARGET_REPOSITORY || github.repository }}`. The results are in `pr-filter-results.json` at the workspace root. Read this file first. It contains:
```json
{
@@ -70,7 +70,7 @@ For each PR number in the comma-separated list, delegate evaluation to the **con
Call the contribution-checker subagent for each PR with this prompt:
```
-Evaluate PR ${{ env.TARGET_REPOSITORY }}# against the contribution guidelines.
+Evaluate PR ${{ vars.TARGET_REPOSITORY || github.repository }}# against the contribution guidelines.
```
The subagent accepts any `owner/repo#number` reference — the target repo is not hardcoded.
@@ -176,9 +176,9 @@ If any subagent call failed (❓), also apply `outdated`.
- **You are the orchestrator** — you dispatch and compile. You do NOT run the checklist yourself.
- **PR fetching and filtering is pre-computed** — a `pre-agent` step writes `pr-filter-results.json`. Read it at the start.
- **Subagent does the analysis** — `.github/agents/contribution-checker.agent.md` handles all per-PR evaluation logic.
-- **Read from `${{ env.TARGET_REPOSITORY }}`** — read-only access via GitHub MCP tools.
+- **Read from `${{ vars.TARGET_REPOSITORY || github.repository }}`** — read-only access via GitHub MCP tools.
- **Write to `${{ github.repository }}`** — reports go here as issues.
-- **Use safe output tools for target repository interactions** — use `add-comment` and `add-labels` safe output tools to post comments and labels to PRs in the target repository `${{ env.TARGET_REPOSITORY }}`. Never use `gh` CLI or direct API calls for writes.
+- **Use safe output tools for target repository interactions** — use `add-comment` and `add-labels` safe output tools to post comments and labels to PRs in the target repository `${{ vars.TARGET_REPOSITORY || github.repository }}`. Never use `gh` CLI or direct API calls for writes.
- Close the previous report issue when creating a new one (`close-older-issues: true`).
- Be constructive in assessments — these reports help maintainers prioritize, not gatekeep.
diff --git a/.github/workflows/stale-repo-identifier.lock.yml b/.github/workflows/stale-repo-identifier.lock.yml
index 63a5d854229..a21bb4efbea 100644
--- a/.github/workflows/stale-repo-identifier.lock.yml
+++ b/.github/workflows/stale-repo-identifier.lock.yml
@@ -29,7 +29,7 @@
# - shared/reporting.md
# - shared/trending-charts-simple.md
#
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"32da181169b45c3331196c90b357916a58548257641ff3b48b5bd98db44144fc","strict":true,"agent_id":"copilot"}
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"8657105a364ca871e2a61c98b3366f368db26b09738a35bea2d0e3c8179d5bac","strict":true,"agent_id":"copilot"}
name: "Stale Repository Identifier"
"on":
@@ -135,7 +135,7 @@ jobs:
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl
- GH_AW_ENV_ORGANIZATION: ${{ env.ORGANIZATION }}
+ GH_AW_EXPR_BD84B27F: ${{ github.event.inputs.organization || 'github' }}
GH_AW_GITHUB_ACTOR: ${{ github.actor }}
GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
@@ -148,15 +148,15 @@ jobs:
run: |
bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh
{
- cat << 'GH_AW_PROMPT_04cc7f07fb47b59f_EOF'
+ cat << 'GH_AW_PROMPT_71feae30af7b6081_EOF'
- GH_AW_PROMPT_04cc7f07fb47b59f_EOF
+ GH_AW_PROMPT_71feae30af7b6081_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_04cc7f07fb47b59f_EOF'
+ cat << 'GH_AW_PROMPT_71feae30af7b6081_EOF'
Tools: create_issue(max:10), upload_asset, missing_tool, missing_data, noop
@@ -190,22 +190,22 @@ jobs:
{{/if}}
- GH_AW_PROMPT_04cc7f07fb47b59f_EOF
+ GH_AW_PROMPT_71feae30af7b6081_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_04cc7f07fb47b59f_EOF'
+ cat << 'GH_AW_PROMPT_71feae30af7b6081_EOF'
{{#runtime-import .github/workflows/shared/python-dataviz.md}}
{{#runtime-import .github/workflows/shared/jqschema.md}}
{{#runtime-import .github/workflows/shared/trending-charts-simple.md}}
{{#runtime-import .github/workflows/shared/reporting.md}}
{{#runtime-import .github/workflows/stale-repo-identifier.md}}
- GH_AW_PROMPT_04cc7f07fb47b59f_EOF
+ GH_AW_PROMPT_71feae30af7b6081_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
- GH_AW_ENV_ORGANIZATION: ${{ env.ORGANIZATION }}
+ GH_AW_EXPR_BD84B27F: ${{ github.event.inputs.organization || 'github' }}
GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
with:
@@ -221,7 +221,7 @@ jobs:
GH_AW_ALLOWED_EXTENSIONS: ''
GH_AW_CACHE_DESCRIPTION: ''
GH_AW_CACHE_DIR: '/tmp/gh-aw/cache-memory/'
- GH_AW_ENV_ORGANIZATION: ${{ env.ORGANIZATION }}
+ GH_AW_EXPR_BD84B27F: ${{ github.event.inputs.organization || 'github' }}
GH_AW_GITHUB_ACTOR: ${{ github.actor }}
GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
@@ -244,7 +244,7 @@ jobs:
GH_AW_ALLOWED_EXTENSIONS: process.env.GH_AW_ALLOWED_EXTENSIONS,
GH_AW_CACHE_DESCRIPTION: process.env.GH_AW_CACHE_DESCRIPTION,
GH_AW_CACHE_DIR: process.env.GH_AW_CACHE_DIR,
- GH_AW_ENV_ORGANIZATION: process.env.GH_AW_ENV_ORGANIZATION,
+ GH_AW_EXPR_BD84B27F: process.env.GH_AW_EXPR_BD84B27F,
GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
@@ -447,12 +447,12 @@ jobs:
mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_31a537358bb21b4d_EOF'
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_e9b68add426057cf_EOF'
{"create_issue":{"expires":48,"group":true,"labels":["stale-repository","automated-analysis","cookie"],"max":10,"title_prefix":"[Stale Repository] "},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"upload_asset":{"allowed-exts":[".png",".jpg",".jpeg"],"branch":"assets/${{ github.workflow }}","max-size":10240}}
- GH_AW_SAFE_OUTPUTS_CONFIG_31a537358bb21b4d_EOF
+ GH_AW_SAFE_OUTPUTS_CONFIG_e9b68add426057cf_EOF
- name: Write Safe Outputs Tools
run: |
- cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_0c8110d127c8bd31_EOF'
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_ebcd95f9c9be27ef_EOF'
{
"description_suffixes": {
"create_issue": " CONSTRAINTS: Maximum 10 issue(s) can be created. Title will be prefixed with \"[Stale Repository] \". Labels [\"stale-repository\" \"automated-analysis\" \"cookie\"] will be automatically added.",
@@ -461,8 +461,8 @@ jobs:
"repo_params": {},
"dynamic_tools": []
}
- GH_AW_SAFE_OUTPUTS_TOOLS_META_0c8110d127c8bd31_EOF
- cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_b481a899eceb925d_EOF'
+ GH_AW_SAFE_OUTPUTS_TOOLS_META_ebcd95f9c9be27ef_EOF
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_04772e10e86be072_EOF'
{
"create_issue": {
"defaultMax": 1,
@@ -564,7 +564,7 @@ jobs:
}
}
}
- GH_AW_SAFE_OUTPUTS_VALIDATION_b481a899eceb925d_EOF
+ GH_AW_SAFE_OUTPUTS_VALIDATION_04772e10e86be072_EOF
node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs
- name: Generate Safe Outputs MCP Server Config
id: safe-outputs-config
@@ -633,7 +633,7 @@ jobs:
export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.11'
mkdir -p /home/runner/.copilot
- cat << GH_AW_MCP_CONFIG_8bf3865cd00153cd_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
+ cat << GH_AW_MCP_CONFIG_2d6f126c636c1d87_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
{
"mcpServers": {
"github": {
@@ -677,7 +677,7 @@ jobs:
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
}
}
- GH_AW_MCP_CONFIG_8bf3865cd00153cd_EOF
+ GH_AW_MCP_CONFIG_2d6f126c636c1d87_EOF
- name: Download activation artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
diff --git a/.github/workflows/stale-repo-identifier.md b/.github/workflows/stale-repo-identifier.md
index 634373c479d..fb60ee5e513 100644
--- a/.github/workflows/stale-repo-identifier.md
+++ b/.github/workflows/stale-repo-identifier.md
@@ -104,7 +104,7 @@ Analyze repositories identified as potentially stale by the stale-repos tool and
## Context
-- **Organization**: ${{ env.ORGANIZATION }}
+- **Organization**: ${{ github.event.inputs.organization || 'github' }}
- **Inactive Threshold**: 365 days
- **Exempt Topics**: keep, template
- **Repository**: ${{ github.repository }}
diff --git a/actions/setup/js/sanitize_content.test.cjs b/actions/setup/js/sanitize_content.test.cjs
index 7c1a3bd0a31..7714611d4b5 100644
--- a/actions/setup/js/sanitize_content.test.cjs
+++ b/actions/setup/js/sanitize_content.test.cjs
@@ -777,6 +777,28 @@ describe("sanitize_content.cjs", () => {
const result = sanitizeContent("Visit https://deep.nested.example.com/page");
expect(result).toBe("Visit https://deep.nested.example.com/page");
});
+
+ it("should redact protocol-relative URLs with disallowed domains (security: #23737)", () => {
+ const result = sanitizeContent("Click [here](//evil.com/steal)");
+ expect(result).toContain("(evil.com/redacted)");
+ expect(result).not.toContain("//evil.com");
+ });
+
+ it("should redact protocol-relative image embeds with disallowed domains", () => {
+ const result = sanitizeContent("");
+ expect(result).toContain("(evil.com/redacted)");
+ expect(result).not.toContain("//evil.com");
+ });
+
+ it("should allow protocol-relative URLs with allowed domains", () => {
+ const result = sanitizeContent("See //github.com/repo");
+ expect(result).toBe("See //github.com/repo");
+ });
+
+ it("should not match // inside existing https:// URLs (no false positives)", () => {
+ const result = sanitizeContent("Visit https://github.com/path//extra");
+ expect(result).toBe("Visit https://github.com/path//extra");
+ });
});
describe("domain sanitization", () => {
diff --git a/actions/setup/js/sanitize_content_core.cjs b/actions/setup/js/sanitize_content_core.cjs
index cb8c53e78b6..7bde0961cb5 100644
--- a/actions/setup/js/sanitize_content_core.cjs
+++ b/actions/setup/js/sanitize_content_core.cjs
@@ -232,8 +232,14 @@ function sanitizeUrlDomains(s, allowed) {
// 5. Stop before another https:// URL in query params (using negative lookahead)
const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi;
- return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => {
- // Extract just the hostname (remove port if present)
+ // Match protocol-relative URLs (//hostname/path) and treat them equivalently to
+ // https://hostname/path for domain allowlist checks. Browsers on HTTPS pages resolve
+ // //hostname as https://hostname, bypassing the scheme-based sanitizeUrlProtocols check.
+ // The negative lookbehind (? checkDomain(_match, hostnameWithPort, pathPart));
+ return s;
}
/**
diff --git a/actions/setup/sh/convert_gateway_config_claude.sh b/actions/setup/sh/convert_gateway_config_claude.sh
index 639147cafe1..9efd6174f1f 100755
--- a/actions/setup/sh/convert_gateway_config_claude.sh
+++ b/actions/setup/sh/convert_gateway_config_claude.sh
@@ -80,6 +80,9 @@ jq --arg urlPrefix "$URL_PREFIX" '
)
' "$MCP_GATEWAY_OUTPUT" > /tmp/gh-aw/mcp-config/mcp-servers.json
+# Restrict permissions so only the current user can read the bearer token (security: #22908)
+chmod 0600 /tmp/gh-aw/mcp-config/mcp-servers.json
+
echo "Claude configuration written to /tmp/gh-aw/mcp-config/mcp-servers.json"
echo ""
echo "Converted configuration:"
diff --git a/actions/setup/sh/convert_gateway_config_codex.sh b/actions/setup/sh/convert_gateway_config_codex.sh
index b89ac2fa2a2..f2cbb7c9a61 100755
--- a/actions/setup/sh/convert_gateway_config_codex.sh
+++ b/actions/setup/sh/convert_gateway_config_codex.sh
@@ -85,6 +85,9 @@ jq -r --arg urlPrefix "$URL_PREFIX" '
"http_headers = { Authorization = \"\(.value.headers.Authorization)\" }\n"
' "$MCP_GATEWAY_OUTPUT" >> /tmp/gh-aw/mcp-config/config.toml
+# Restrict permissions so only the current user can read the bearer token (security: #22908)
+chmod 0600 /tmp/gh-aw/mcp-config/config.toml
+
echo "Codex configuration written to /tmp/gh-aw/mcp-config/config.toml"
echo ""
echo "Converted configuration:"
diff --git a/actions/setup/sh/convert_gateway_config_copilot.sh b/actions/setup/sh/convert_gateway_config_copilot.sh
index 14f471bbd69..f8fd957a747 100755
--- a/actions/setup/sh/convert_gateway_config_copilot.sh
+++ b/actions/setup/sh/convert_gateway_config_copilot.sh
@@ -82,6 +82,9 @@ jq --arg urlPrefix "$URL_PREFIX" '
)
' "$MCP_GATEWAY_OUTPUT" > /home/runner/.copilot/mcp-config.json
+# Restrict permissions so only the current user can read the bearer token (security: #22908)
+chmod 0600 /home/runner/.copilot/mcp-config.json
+
echo "Copilot configuration written to /home/runner/.copilot/mcp-config.json"
echo ""
echo "Converted configuration:"
diff --git a/actions/setup/sh/convert_gateway_config_gemini.sh b/actions/setup/sh/convert_gateway_config_gemini.sh
index 4b2b14d5711..b7b69d31fce 100644
--- a/actions/setup/sh/convert_gateway_config_gemini.sh
+++ b/actions/setup/sh/convert_gateway_config_gemini.sh
@@ -99,6 +99,9 @@ jq --arg urlPrefix "$URL_PREFIX" '
.context.includeDirectories = ["/tmp/"]
' "$MCP_GATEWAY_OUTPUT" > "$GEMINI_SETTINGS_FILE"
+# Restrict permissions so only the current user can read the bearer token (security: #22908)
+chmod 0600 "$GEMINI_SETTINGS_FILE"
+
echo "Gemini configuration written to $GEMINI_SETTINGS_FILE"
echo ""
echo "Converted configuration:"
diff --git a/actions/setup/sh/setup_cache_memory_git.sh b/actions/setup/sh/setup_cache_memory_git.sh
index 34d94ffadba..d3e89c11ee1 100644
--- a/actions/setup/sh/setup_cache_memory_git.sh
+++ b/actions/setup/sh/setup_cache_memory_git.sh
@@ -21,6 +21,19 @@ LEVELS=("merged" "approved" "unapproved" "none")
mkdir -p "$CACHE_DIR"
cd "$CACHE_DIR"
+# --- Sanitize git hooks after cache restore (security: #23739) ---
+# Any executable hook files placed under .git/hooks/ by a previous agent run are preserved
+# in the cache archive and would fire on the host runner before the AWF sandbox starts.
+# Remove all non-sample hook files immediately after the cache is restored and before any
+# git operation that could trigger them (checkout, merge, etc.).
+if [ -d ".git/hooks" ]; then
+ find ".git/hooks" -maxdepth 1 -type f ! -name '*.sample' -delete 2>/dev/null || true
+fi
+# Redirect git's hook path to /dev/null so even newly-created hooks cannot fire.
+if [ -d ".git" ]; then
+ git config core.hooksPath /dev/null
+fi
+
# --- Format detection & migration ---
if [ ! -d .git ]; then
# No git repo yet — either a fresh cache or a legacy flat-file cache.
diff --git a/pkg/workflow/compile_outputs_label_test.go b/pkg/workflow/compile_outputs_label_test.go
index 608778231cf..269cf2583e1 100644
--- a/pkg/workflow/compile_outputs_label_test.go
+++ b/pkg/workflow/compile_outputs_label_test.go
@@ -204,7 +204,7 @@ safe-outputs:
# Test Output Label No Allowed Labels
This workflow tests label addition with no allowed labels restriction.
-Write your labels to ${{ env.GH_AW_SAFE_OUTPUTS }}, one per line.
+Write your labels to the safe-outputs file, one per line.
`
testFile := filepath.Join(tmpDir, "test-label-no-allowed.md")
@@ -281,7 +281,7 @@ safe-outputs:
# Test Output Label Null Config
This workflow tests label addition with null configuration (any labels allowed).
-Write your labels to ${{ env.GH_AW_SAFE_OUTPUTS }}, one per line.
+Write your labels to the safe-outputs file, one per line.
`
testFile := filepath.Join(tmpDir, "test-label-null-config.md")
diff --git a/pkg/workflow/expression_safety_test.go b/pkg/workflow/expression_safety_test.go
index cc4410ce324..7382e06e308 100644
--- a/pkg/workflow/expression_safety_test.go
+++ b/pkg/workflow/expression_safety_test.go
@@ -122,8 +122,14 @@ func TestValidateExpressionSafety(t *testing.T) {
expectError: false,
},
{
- name: "authorized_env_variable",
- content: "Environment: ${{ env.MY_VAR }}",
+ name: "authorized_env_variable",
+ content: "Environment: ${{ env.MY_VAR }}",
+ expectError: true,
+ expectedErrors: []string{"env.MY_VAR"},
+ },
+ {
+ name: "authorized_vars_variable",
+ content: "Config: ${{ vars.MY_CONFIG }}",
expectError: false,
},
{
@@ -143,7 +149,7 @@ func TestValidateExpressionSafety(t *testing.T) {
name: "multiple_unauthorized_expressions",
content: "Token: ${{ secrets.GITHUB_TOKEN }}, Valid: ${{ github.actor }}, Env: ${{ env.TEST }}",
expectError: true,
- expectedErrors: []string{"secrets.GITHUB_TOKEN"},
+ expectedErrors: []string{"secrets.GITHUB_TOKEN", "env.TEST"},
},
{
name: "expressions_with_whitespace",
@@ -408,10 +414,17 @@ func TestValidateExpressionSafetyWithParser(t *testing.T) {
- **Working directory**: ${{ github.workspace }}`,
wantErr: false,
},
- // env.* with defaults
+ // env.* expressions are blocked at compile time (security: #22914)
+ {
+ name: "env variable with string default",
+ content: `${{ env.LOG_LEVEL || 'info' }}`,
+ wantErr: true,
+ errContains: "env.LOG_LEVEL",
+ },
+ // vars.* expressions are allowed as non-secret configuration values
{
- name: "env variable with string default",
- content: `${{ env.LOG_LEVEL || 'info' }}`,
+ name: "vars variable with string default",
+ content: `${{ vars.TARGET_REPO || github.repository }}`,
wantErr: false,
},
}
diff --git a/pkg/workflow/expression_safety_validation.go b/pkg/workflow/expression_safety_validation.go
index ba6771196f8..510bcebf028 100644
--- a/pkg/workflow/expression_safety_validation.go
+++ b/pkg/workflow/expression_safety_validation.go
@@ -29,7 +29,9 @@ var (
workflowCallInputsRegex = regexp.MustCompile(`^inputs\.[a-zA-Z0-9_-]+$`)
awInputsRegex = regexp.MustCompile(`^github\.aw\.inputs\.[a-zA-Z0-9_-]+$`)
awImportInputsRegex = regexp.MustCompile(`^github\.aw\.import-inputs\.[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)?$`)
- envRegex = regexp.MustCompile(`^env\.[a-zA-Z0-9_-]+$`)
+ // varsRegex allows vars.* (non-secret GitHub repository/org variables)
+ // env.* is intentionally excluded because it can reference encrypted secrets.
+ varsRegex = regexp.MustCompile(`^vars\.[a-zA-Z0-9_-]+$`)
// comparisonExtractionRegex extracts property accesses from comparison expressions
// Matches patterns like "github.workflow == 'value'" and extracts "github.workflow"
comparisonExtractionRegex = regexp.MustCompile(`([a-zA-Z_][a-zA-Z0-9_.]*)\s*(?:==|!=|<|>|<=|>=)\s*`)
@@ -72,7 +74,7 @@ func validateExpressionSafety(markdownContent string) error {
WorkflowCallInputsRe: workflowCallInputsRegex,
AwInputsRe: awInputsRegex,
AwImportInputsRe: awImportInputsRegex,
- EnvRe: envRegex,
+ VarsRe: varsRegex,
UnauthorizedExpressions: &unauthorizedExpressions,
})
})
@@ -87,7 +89,7 @@ func validateExpressionSafety(markdownContent string) error {
WorkflowCallInputsRe: workflowCallInputsRegex,
AwInputsRe: awInputsRegex,
AwImportInputsRe: awImportInputsRegex,
- EnvRe: envRegex,
+ VarsRe: varsRegex,
UnauthorizedExpressions: &unauthorizedExpressions,
})
if err != nil {
@@ -128,7 +130,8 @@ func validateExpressionSafety(markdownContent string) error {
allowedList.WriteString(" - github.aw.inputs.* (shared workflow inputs)\n")
allowedList.WriteString(" - github.aw.import-inputs.* (import-schema inputs)\n")
allowedList.WriteString(" - inputs.* (workflow_call)\n")
- allowedList.WriteString(" - env.*\n")
+ allowedList.WriteString(" - vars.* (non-secret GitHub variables)\n")
+ allowedList.WriteString(" NOTE: env.* is not allowed (may expose secrets); use vars.* for configuration values\n")
return NewValidationError(
"expressions",
@@ -149,7 +152,7 @@ type ExpressionValidationOptions struct {
WorkflowCallInputsRe *regexp.Regexp
AwInputsRe *regexp.Regexp
AwImportInputsRe *regexp.Regexp
- EnvRe *regexp.Regexp
+ VarsRe *regexp.Regexp
UnauthorizedExpressions *[]string
}
@@ -214,7 +217,7 @@ func validateSingleExpression(expression string, opts ExpressionValidationOption
allowed = true
} else if opts.AwImportInputsRe != nil && opts.AwImportInputsRe.MatchString(expression) {
allowed = true
- } else if opts.EnvRe.MatchString(expression) {
+ } else if opts.VarsRe != nil && opts.VarsRe.MatchString(expression) {
allowed = true
} else if slices.Contains(constants.AllowedExpressions, expression) {
allowed = true
@@ -272,7 +275,7 @@ func validateSingleExpression(expression string, opts ExpressionValidationOption
propertyAllowed = true
} else if opts.AwImportInputsRe != nil && opts.AwImportInputsRe.MatchString(property) {
propertyAllowed = true
- } else if opts.EnvRe.MatchString(property) {
+ } else if opts.VarsRe != nil && opts.VarsRe.MatchString(property) {
propertyAllowed = true
} else if slices.Contains(constants.AllowedExpressions, property) {
propertyAllowed = true
diff --git a/pkg/workflow/expressions_benchmark_test.go b/pkg/workflow/expressions_benchmark_test.go
index a1ec1d6582b..c0a6b7a9db5 100644
--- a/pkg/workflow/expressions_benchmark_test.go
+++ b/pkg/workflow/expressions_benchmark_test.go
@@ -17,7 +17,6 @@ func BenchmarkValidateExpression(b *testing.B) {
InputsRe: inputsRegex,
WorkflowCallInputsRe: workflowCallInputsRegex,
AwInputsRe: awInputsRegex,
- EnvRe: envRegex,
UnauthorizedExpressions: &unauthorizedExprs,
})
}
@@ -34,7 +33,6 @@ func BenchmarkValidateExpression_Complex(b *testing.B) {
InputsRe: inputsRegex,
WorkflowCallInputsRe: workflowCallInputsRegex,
AwInputsRe: awInputsRegex,
- EnvRe: envRegex,
UnauthorizedExpressions: &unauthorizedExprs,
})
}
@@ -51,7 +49,6 @@ func BenchmarkValidateExpression_NeedsOutputs(b *testing.B) {
InputsRe: inputsRegex,
WorkflowCallInputsRe: workflowCallInputsRegex,
AwInputsRe: awInputsRegex,
- EnvRe: envRegex,
UnauthorizedExpressions: &unauthorizedExprs,
})
}
@@ -68,7 +65,6 @@ func BenchmarkValidateExpression_StepsOutputs(b *testing.B) {
InputsRe: inputsRegex,
WorkflowCallInputsRe: workflowCallInputsRegex,
AwInputsRe: awInputsRegex,
- EnvRe: envRegex,
UnauthorizedExpressions: &unauthorizedExprs,
})
}
From 249cbcdf2540429eb7937a741d9690fcf2ef8424 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 1 Apr 2026 17:22:52 +0000
Subject: [PATCH 3/3] fix: strengthen git hook cleanup and document regex
correctness
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/091b08a2-ecaf-4440-ac26-63ff74183d19
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
actions/setup/sh/setup_cache_memory_git.sh | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/actions/setup/sh/setup_cache_memory_git.sh b/actions/setup/sh/setup_cache_memory_git.sh
index d3e89c11ee1..6dcb951fab1 100644
--- a/actions/setup/sh/setup_cache_memory_git.sh
+++ b/actions/setup/sh/setup_cache_memory_git.sh
@@ -24,10 +24,11 @@ cd "$CACHE_DIR"
# --- Sanitize git hooks after cache restore (security: #23739) ---
# Any executable hook files placed under .git/hooks/ by a previous agent run are preserved
# in the cache archive and would fire on the host runner before the AWF sandbox starts.
-# Remove all non-sample hook files immediately after the cache is restored and before any
-# git operation that could trigger them (checkout, merge, etc.).
+# Remove ALL hook files immediately after the cache is restored and before any git operation
+# that could trigger them (checkout, merge, etc.). Sample files are excluded from git
+# execution so this is safe; legitimate hooks are never cached anyway.
if [ -d ".git/hooks" ]; then
- find ".git/hooks" -maxdepth 1 -type f ! -name '*.sample' -delete 2>/dev/null || true
+ find ".git/hooks" -maxdepth 1 -type f -delete 2>/dev/null || true
fi
# Redirect git's hook path to /dev/null so even newly-created hooks cannot fire.
if [ -d ".git" ]; then