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("![Track me](//evil.com/pixel.gif)"); + 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