diff --git a/.changeset/patch-escape-shell-env-vars.md b/.changeset/patch-escape-shell-env-vars.md new file mode 100644 index 00000000000..c40fa04bc4d --- /dev/null +++ b/.changeset/patch-escape-shell-env-vars.md @@ -0,0 +1,5 @@ +--- +"gh-aw": patch +--- + +Quote environment variables with `${VAR@Q}` in the shell scripts under `actions/setup/sh/` so echo statements cannot be abused by special characters or injection vectors. diff --git a/.github/workflows/ai-moderator.lock.yml b/.github/workflows/ai-moderator.lock.yml index 08cc0136f31..1f9298904cb 100644 --- a/.github/workflows/ai-moderator.lock.yml +++ b/.github/workflows/ai-moderator.lock.yml @@ -1025,7 +1025,7 @@ jobs: env: GH_AW_RATE_LIMIT_MAX: "5" GH_AW_RATE_LIMIT_WINDOW: "60" - GH_AW_RATE_LIMIT_EVENTS: "workflow_dispatch,issues,issue_comment" + GH_AW_RATE_LIMIT_EVENTS: "issues,issue_comment,workflow_dispatch" with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/auto-triage-issues.lock.yml b/.github/workflows/auto-triage-issues.lock.yml index 12a64ec04d4..cca519e0369 100644 --- a/.github/workflows/auto-triage-issues.lock.yml +++ b/.github/workflows/auto-triage-issues.lock.yml @@ -1082,7 +1082,7 @@ jobs: env: GH_AW_RATE_LIMIT_MAX: "5" GH_AW_RATE_LIMIT_WINDOW: "60" - GH_AW_RATE_LIMIT_EVENTS: "issues,workflow_dispatch" + GH_AW_RATE_LIMIT_EVENTS: "workflow_dispatch,issues" with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/actions/setup/sh/check_mcp_servers.sh b/actions/setup/sh/check_mcp_servers.sh index 960f6825849..8d72f14c5f0 100755 --- a/actions/setup/sh/check_mcp_servers.sh +++ b/actions/setup/sh/check_mcp_servers.sh @@ -49,7 +49,7 @@ echo "" # Validate configuration file exists CONFIG_VALIDATION_START=$(date +%s%3N) if [ ! -f "$GATEWAY_CONFIG_PATH" ]; then - echo "ERROR: Gateway configuration file not found: $GATEWAY_CONFIG_PATH" >&2 + echo "ERROR: Gateway configuration file not found: ${GATEWAY_CONFIG_PATH@Q}" >&2 exit 1 fi @@ -184,8 +184,8 @@ while IFS= read -r SERVER_NAME; do SERVERS_SUCCEEDED=$((SERVERS_SUCCEEDED + 1)) else echo "✗ $SERVER_NAME: failed to connect" - echo " URL: $SERVER_URL" - echo " Last error: $LAST_ERROR" + echo " URL: ${SERVER_URL@Q}" + echo " Last error: ${LAST_ERROR@Q}" echo " Retries attempted: $MAX_RETRIES" SERVERS_FAILED=$((SERVERS_FAILED + 1)) fi diff --git a/actions/setup/sh/clean_git_credentials.sh b/actions/setup/sh/clean_git_credentials.sh index 801f8d71f3c..2627ac1f96e 100755 --- a/actions/setup/sh/clean_git_credentials.sh +++ b/actions/setup/sh/clean_git_credentials.sh @@ -20,11 +20,11 @@ set -euo pipefail WORKSPACE="${GITHUB_WORKSPACE:-.}" GIT_CONFIG_PATH="${WORKSPACE}/.git/config" -echo "Cleaning git credentials from ${GIT_CONFIG_PATH}" +echo "Cleaning git credentials from ${GIT_CONFIG_PATH@Q}" # Check if .git/config exists if [ ! -f "${GIT_CONFIG_PATH}" ]; then - echo "No .git/config found at ${GIT_CONFIG_PATH}, nothing to clean" + echo "No .git/config found at ${GIT_CONFIG_PATH@Q}, nothing to clean" exit 0 fi diff --git a/actions/setup/sh/generate_git_patch.sh b/actions/setup/sh/generate_git_patch.sh index ac064297778..02edcb04e22 100755 --- a/actions/setup/sh/generate_git_patch.sh +++ b/actions/setup/sh/generate_git_patch.sh @@ -1,7 +1,7 @@ # Diagnostic logging: Show environment information echo "=== Diagnostic: Environment Information ===" -echo "GITHUB_SHA: ${GITHUB_SHA}" -echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH}" +echo "GITHUB_SHA: ${GITHUB_SHA@Q}" +echo "DEFAULT_BRANCH: ${DEFAULT_BRANCH@Q}" echo "Current HEAD: $(git rev-parse HEAD 2>/dev/null || echo 'unknown')" echo "Current branch: $(git branch --show-current 2>/dev/null || echo 'detached HEAD')" @@ -30,7 +30,7 @@ if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then # Extract branch value using sed BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from create_pull_request: $BRANCH_NAME" + echo "Extracted branch name from create_pull_request: ${BRANCH_NAME@Q}" break fi # Extract branch from push_to_pull_request_branch line using simple grep and sed @@ -40,7 +40,7 @@ if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then # Extract branch value using sed BRANCH_NAME="$(echo "$line" | sed -n 's/.*"branch"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" if [ -n "$BRANCH_NAME" ]; then - echo "Extracted branch name from push_to_pull_request_branch: $BRANCH_NAME" + echo "Extracted branch name from push_to_pull_request_branch: ${BRANCH_NAME@Q}" break fi fi @@ -48,7 +48,7 @@ if [ -f "$GH_AW_SAFE_OUTPUTS" ]; then done < "$GH_AW_SAFE_OUTPUTS" else echo "" - echo "GH_AW_SAFE_OUTPUTS file not found at: $GH_AW_SAFE_OUTPUTS" + echo "GH_AW_SAFE_OUTPUTS file not found at: ${GH_AW_SAFE_OUTPUTS@Q}" fi # If no branch found in JSONL, log it but don't give up yet @@ -63,30 +63,30 @@ PATCH_GENERATED=false if [ -n "$BRANCH_NAME" ]; then echo "" echo "=== Strategy 1: Using named branch from JSONL ===" - echo "Looking for branch: $BRANCH_NAME" + echo "Looking for branch: ${BRANCH_NAME@Q}" # Check if the branch exists if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then - echo "Branch $BRANCH_NAME exists, generating patch from branch changes" + echo "Branch ${BRANCH_NAME@Q} exists, generating patch from branch changes" # Check if origin/$BRANCH_NAME exists to use as base if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then - echo "Using origin/$BRANCH_NAME as base for patch generation" + echo "Using origin/${BRANCH_NAME@Q} as base for patch generation" BASE_REF="origin/$BRANCH_NAME" else - echo "origin/$BRANCH_NAME does not exist, using merge-base with default branch" + echo "origin/${BRANCH_NAME@Q} does not exist, using merge-base with default branch" # Use the default branch name from environment variable - echo "Default branch: $DEFAULT_BRANCH" + echo "Default branch: ${DEFAULT_BRANCH@Q}" # Fetch the default branch to ensure it's available locally git fetch origin "$DEFAULT_BRANCH" # Find merge base between default branch and current branch BASE_REF="$(git merge-base "origin/$DEFAULT_BRANCH" "$BRANCH_NAME")" - echo "Using merge-base as base: $BASE_REF" + echo "Using merge-base as base: ${BASE_REF@Q}" fi # Diagnostic logging: Show diff stats before generating patch echo "" echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat $BASE_REF..$BRANCH_NAME" + echo "Command: git diff --stat ${BASE_REF@Q}..${BRANCH_NAME@Q}" git diff --stat "$BASE_REF".."$BRANCH_NAME" || echo "Failed to show diff stats" # Diagnostic logging: Count commits to be included @@ -102,14 +102,14 @@ if [ -n "$BRANCH_NAME" ]; then # Diagnostic logging: Show the exact command being used echo "" echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch $BASE_REF..$BRANCH_NAME --stdout > /tmp/gh-aw/aw.patch" + echo "Command: git format-patch ${BASE_REF@Q}..${BRANCH_NAME@Q} --stdout > /tmp/gh-aw/aw.patch" # Generate patch from the determined base to the branch git format-patch "$BASE_REF".."$BRANCH_NAME" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from branch" > /tmp/gh-aw/aw.patch - echo "Patch file created from branch: $BRANCH_NAME (base: $BASE_REF)" + echo "Patch file created from branch: ${BRANCH_NAME@Q} (base: ${BASE_REF@Q})" PATCH_GENERATED=true else - echo "Branch $BRANCH_NAME does not exist locally" + echo "Branch ${BRANCH_NAME@Q} does not exist locally" fi fi @@ -120,8 +120,8 @@ if [ "$PATCH_GENERATED" = false ]; then # Get current HEAD SHA CURRENT_HEAD="$(git rev-parse HEAD 2>/dev/null || echo '')" - echo "Current HEAD: $CURRENT_HEAD" - echo "Checkout SHA (GITHUB_SHA): $GITHUB_SHA" + echo "Current HEAD: ${CURRENT_HEAD@Q}" + echo "Checkout SHA (GITHUB_SHA): ${GITHUB_SHA@Q}" if [ -z "$CURRENT_HEAD" ]; then echo "ERROR: Could not determine current HEAD SHA" @@ -150,15 +150,15 @@ if [ "$PATCH_GENERATED" = false ]; then # Show diff stats echo "" echo "=== Diagnostic: Diff stats for patch generation ===" - echo "Command: git diff --stat ${GITHUB_SHA}..HEAD" + echo "Command: git diff --stat ${GITHUB_SHA@Q}..HEAD" git diff --stat "${GITHUB_SHA}..HEAD" || echo "Failed to show diff stats" # Generate patch from GITHUB_SHA to HEAD echo "" echo "=== Diagnostic: Generating patch ===" - echo "Command: git format-patch ${GITHUB_SHA}..HEAD --stdout > /tmp/gh-aw/aw.patch" + echo "Command: git format-patch ${GITHUB_SHA@Q}..HEAD --stdout > /tmp/gh-aw/aw.patch" git format-patch "${GITHUB_SHA}..HEAD" --stdout > /tmp/gh-aw/aw.patch || echo "Failed to generate patch from HEAD" > /tmp/gh-aw/aw.patch - echo "Patch file created from commits on HEAD (base: $GITHUB_SHA)" + echo "Patch file created from commits on HEAD (base: ${GITHUB_SHA@Q})" PATCH_GENERATED=true else echo "No commits found between GITHUB_SHA and HEAD" diff --git a/actions/setup/sh/install_awf_binary.sh b/actions/setup/sh/install_awf_binary.sh index e5695d5cb2f..e81eb4bba86 100755 --- a/actions/setup/sh/install_awf_binary.sh +++ b/actions/setup/sh/install_awf_binary.sh @@ -41,10 +41,10 @@ TEMP_DIR=$(mktemp -d) trap 'rm -rf "$TEMP_DIR"' EXIT # Download binary and checksums -echo "Downloading binary from ${BINARY_URL}..." +echo "Downloading binary from ${BINARY_URL@Q}..." curl -fsSL -o "${TEMP_DIR}/${AWF_BINARY}" "${BINARY_URL}" -echo "Downloading checksums from ${CHECKSUMS_URL}..." +echo "Downloading checksums from ${CHECKSUMS_URL@Q}..." curl -fsSL -o "${TEMP_DIR}/checksums.txt" "${CHECKSUMS_URL}" # Verify checksum diff --git a/actions/setup/sh/start_mcp_gateway.sh b/actions/setup/sh/start_mcp_gateway.sh index accd64aa221..168c420af4d 100755 --- a/actions/setup/sh/start_mcp_gateway.sh +++ b/actions/setup/sh/start_mcp_gateway.sh @@ -426,5 +426,5 @@ echo "" { echo "gateway-pid=$GATEWAY_PID" echo "gateway-port=${MCP_GATEWAY_PORT}" - echo "gateway-api-key=${MCP_GATEWAY_API_KEY}" + echo "gateway-api-key=${MCP_GATEWAY_API_KEY@Q}" } >> $GITHUB_OUTPUT diff --git a/actions/setup/sh/start_safe_inputs_server.sh b/actions/setup/sh/start_safe_inputs_server.sh index ecc1667dbe6..852a2a9b551 100755 --- a/actions/setup/sh/start_safe_inputs_server.sh +++ b/actions/setup/sh/start_safe_inputs_server.sh @@ -128,5 +128,5 @@ done # Output the configuration for the MCP client { echo "port=$GH_AW_SAFE_INPUTS_PORT" - echo "api_key=$GH_AW_SAFE_INPUTS_API_KEY" + echo "api_key=${GH_AW_SAFE_INPUTS_API_KEY@Q}" } >> "$GITHUB_OUTPUT" diff --git a/actions/setup/sh/start_safe_outputs_server.sh b/actions/setup/sh/start_safe_outputs_server.sh index a7449058e05..fe763edd86c 100755 --- a/actions/setup/sh/start_safe_outputs_server.sh +++ b/actions/setup/sh/start_safe_outputs_server.sh @@ -128,5 +128,5 @@ done # Output the configuration for the MCP client { echo "port=$GH_AW_SAFE_OUTPUTS_PORT" - echo "api_key=$GH_AW_SAFE_OUTPUTS_API_KEY" + echo "api_key=${GH_AW_SAFE_OUTPUTS_API_KEY@Q}" } >> "$GITHUB_OUTPUT" diff --git a/actions/setup/sh/validate_gatewayed_server.sh b/actions/setup/sh/validate_gatewayed_server.sh index 39687263fbd..cd68a520034 100755 --- a/actions/setup/sh/validate_gatewayed_server.sh +++ b/actions/setup/sh/validate_gatewayed_server.sh @@ -33,7 +33,7 @@ GATEWAY_URL="$3" # Validate that MCP config file exists validate_config_file_exists() { if [ ! -f "$MCP_CONFIG_PATH" ]; then - echo "ERROR: MCP config file not found: $MCP_CONFIG_PATH" >&2 + echo "ERROR: MCP config file not found: ${MCP_CONFIG_PATH@Q}" >&2 return 1 fi return 0 @@ -104,8 +104,8 @@ validate_gateway_url() { if ! echo "$server_url" | grep -q "$GATEWAY_URL"; then echo "ERROR: ${SERVER_NAME} server URL does not point to gateway" >&2 - echo "Expected gateway URL: $GATEWAY_URL" >&2 - echo "Actual URL: $server_url" >&2 + echo "Expected gateway URL: ${GATEWAY_URL@Q}" >&2 + echo "Actual URL: ${server_url@Q}" >&2 return 1 fi diff --git a/actions/setup/sh/validate_multi_secret.sh b/actions/setup/sh/validate_multi_secret.sh index 55339dd8c6a..1229e4394b9 100755 --- a/actions/setup/sh/validate_multi_secret.sh +++ b/actions/setup/sh/validate_multi_secret.sh @@ -70,14 +70,14 @@ if [ "$all_empty" = true ]; then echo "❌ Error: $error_msg" echo "$requirement_msg" echo "Please configure one of these secrets in your repository settings." - echo "Documentation: $DOCS_URL" + echo "Documentation: ${DOCS_URL@Q}" } >> "$GITHUB_STEP_SUMMARY" # Print to stderr echo "Error: $error_msg" >&2 echo "$requirement_msg" >&2 echo "Please configure one of these secrets in your repository settings." >&2 - echo "Documentation: $DOCS_URL" >&2 + echo "Documentation: ${DOCS_URL@Q}" >&2 # Set step output to indicate verification failed if [ -n "$GITHUB_OUTPUT" ]; then diff --git a/docs/src/content/docs/agent-factory-status.mdx b/docs/src/content/docs/agent-factory-status.mdx index da81e579323..398f7ce7020 100644 --- a/docs/src/content/docs/agent-factory-status.mdx +++ b/docs/src/content/docs/agent-factory-status.mdx @@ -22,6 +22,7 @@ These are experimental agentic workflows used by the GitHub Next team to learn, | [Automated Portfolio Analyst](https://github.com/github/gh-aw/blob/main/.github/workflows/portfolio-analyst.md) | copilot | [![Automated Portfolio Analyst](https://github.com/github/gh-aw/actions/workflows/portfolio-analyst.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/portfolio-analyst.lock.yml) | - | - | | [Basic Research Agent](https://github.com/github/gh-aw/blob/main/.github/workflows/research.md) | copilot | [![Basic Research Agent](https://github.com/github/gh-aw/actions/workflows/research.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/research.lock.yml) | - | - | | [Blog Auditor](https://github.com/github/gh-aw/blob/main/.github/workflows/blog-auditor.md) | claude | [![Blog Auditor](https://github.com/github/gh-aw/actions/workflows/blog-auditor.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/blog-auditor.lock.yml) | - | - | +| [Bot Detection Agent 🔍🤖](https://github.com/github/gh-aw/blob/main/.github/workflows/bot-detection.md) | copilot | [![Bot Detection Agent 🔍🤖](https://github.com/github/gh-aw/actions/workflows/bot-detection.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/bot-detection.lock.yml) | - | - | | [Brave Web Search Agent](https://github.com/github/gh-aw/blob/main/.github/workflows/brave.md) | copilot | [![Brave Web Search Agent](https://github.com/github/gh-aw/actions/workflows/brave.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/brave.lock.yml) | - | `/brave` | | [Breaking Change Checker](https://github.com/github/gh-aw/blob/main/.github/workflows/breaking-change-checker.md) | copilot | [![Breaking Change Checker](https://github.com/github/gh-aw/actions/workflows/breaking-change-checker.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/breaking-change-checker.lock.yml) | - | - | | [Changeset Generator](https://github.com/github/gh-aw/blob/main/.github/workflows/changeset.md) | codex | [![Changeset Generator](https://github.com/github/gh-aw/actions/workflows/changeset.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/changeset.lock.yml) | - | - | @@ -143,7 +144,6 @@ These are experimental agentic workflows used by the GitHub Next team to learn, | [Test Create PR Error Handling](https://github.com/github/gh-aw/blob/main/.github/workflows/test-create-pr-error-handling.md) | claude | [![Test Create PR Error Handling](https://github.com/github/gh-aw/actions/workflows/test-create-pr-error-handling.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/test-create-pr-error-handling.lock.yml) | - | - | | [Test Dispatcher Workflow](https://github.com/github/gh-aw/blob/main/.github/workflows/test-dispatcher.md) | copilot | [![Test Dispatcher Workflow](https://github.com/github/gh-aw/actions/workflows/test-dispatcher.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/test-dispatcher.lock.yml) | - | - | | [Test Project URL Explicit Requirement](https://github.com/github/gh-aw/blob/main/.github/workflows/test-project-url-default.md) | copilot | [![Test Project URL Explicit Requirement](https://github.com/github/gh-aw/actions/workflows/test-project-url-default.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/test-project-url-default.lock.yml) | - | - | -| [Test Rate Limiting](https://github.com/github/gh-aw/blob/main/.github/workflows/test-rate-limit.md) | copilot | [![Test Rate Limiting](https://github.com/github/gh-aw/actions/workflows/test-rate-limit.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/test-rate-limit.lock.yml) | - | - | | [Test Workflow](https://github.com/github/gh-aw/blob/main/.github/workflows/test-workflow.md) | copilot | [![Test Workflow](https://github.com/github/gh-aw/actions/workflows/test-workflow.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/test-workflow.lock.yml) | - | - | | [The Daily Repository Chronicle](https://github.com/github/gh-aw/blob/main/.github/workflows/daily-repo-chronicle.md) | copilot | [![The Daily Repository Chronicle](https://github.com/github/gh-aw/actions/workflows/daily-repo-chronicle.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/daily-repo-chronicle.lock.yml) | `0 16 * * 1-5` | - | | [The Great Escapi](https://github.com/github/gh-aw/blob/main/.github/workflows/firewall-escape.md) | copilot | [![The Great Escapi](https://github.com/github/gh-aw/actions/workflows/firewall-escape.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/firewall-escape.lock.yml) | - | - | diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index 1bce7bc39bb..9d0d3c5851b 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -3513,11 +3513,11 @@ bots: [] # (optional) rate-limit: # Maximum number of workflow runs allowed per user within the time window. - # Defaults to 5. - # (optional) + # Required field. max: 1 - # Time window in minutes for rate limiting. Defaults to 60 (1 hour). + # Time window in minutes for rate limiting. Defaults to 60 (1 hour). Maximum: 180 + # (3 hours). # (optional) window: 1