diff --git a/.gitea/actions/review/action.yml b/.gitea/actions/review/action.yml index c7a3828..f249410 100644 --- a/.gitea/actions/review/action.yml +++ b/.gitea/actions/review/action.yml @@ -2,13 +2,23 @@ # It detects the VCS host type using the github.api_url context (set only on # GitHub/GHES runners) and uses the appropriate releases API for version # resolution and binary download. +# +# Security notes: +# - On GitHub/GHES (VCS_TYPE=github), inputs.gitea-url is IGNORED to prevent +# token exfiltration. API calls use github.api_url; downloads use +# github.server_url. Tokens are never sent to user-supplied URLs. +# - On Gitea (VCS_TYPE=gitea), inputs.gitea-url is validated (https scheme, +# no whitespace/newlines) before use. +# - action-repo is validated against owner/repo pattern. +# - Tokens are passed via masked environment variables, not step outputs. +# # Requirements: python3, sha256sum, curl (all present on ubuntu-* runners). name: 'AI Code Review' description: 'Run AI-powered code review on a pull request using review-bot' inputs: gitea-url: - description: 'VCS instance URL (defaults to server_url). Works for both Gitea and GitHub.' + description: 'Gitea instance URL (only used on Gitea runners; ignored on GitHub/GHES). Defaults to server_url.' required: false default: '' repo: @@ -121,9 +131,7 @@ runs: id: version shell: bash run: | - SERVER_URL="${{ inputs.gitea-url || github.server_url }}" - # Strip trailing slash if present - SERVER_URL="${SERVER_URL%/}" + # --- Input Validation --- # Determine the repo hosting review-bot releases (not the repo being reviewed) ACTION_REPO="${{ inputs.action-repo }}" @@ -136,6 +144,12 @@ runs: ACTION_REPO="rodin/review-bot" fi + # Validate ACTION_REPO matches owner/repo pattern (防止 path traversal) + if ! printf '%s' "$ACTION_REPO" | grep -qE '^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$'; then + echo "Error: action-repo '${ACTION_REPO}' does not match expected owner/repo format" >&2 + exit 1 + fi + # Detect VCS host type using github.api_url context. # github.api_url is set on GitHub.com (https://api.github.com) and GHES # (https:///api/v3). It is empty/unset on Gitea Actions runners. @@ -146,6 +160,32 @@ runs: VCS_TYPE="gitea" fi + # Determine SERVER_URL based on VCS type. + # SECURITY: On GitHub/GHES, ALWAYS use github.server_url — never trust + # inputs.gitea-url to prevent token exfiltration to attacker-controlled hosts. + if [ "$VCS_TYPE" = "github" ]; then + SERVER_URL="${{ github.server_url }}" + if [ -n "${{ inputs.gitea-url }}" ]; then + echo "::warning::inputs.gitea-url is ignored on GitHub/GHES runners (VCS_TYPE=github). Using github.server_url instead." + fi + else + SERVER_URL="${{ inputs.gitea-url || github.server_url }}" + fi + # Strip trailing slash if present + SERVER_URL="${SERVER_URL%/}" + + # Validate SERVER_URL for Gitea path: must be https, no whitespace/newlines + if [ "$VCS_TYPE" = "gitea" ]; then + if ! printf '%s' "$SERVER_URL" | grep -qE '^https://[^[:space:]]+$'; then + echo "Error: SERVER_URL '${SERVER_URL}' must be an https:// URL with no whitespace" >&2 + exit 1 + fi + if printf '%s' "$SERVER_URL" | grep -q $'\n'; then + echo "Error: SERVER_URL contains unexpected newline" >&2 + exit 1 + fi + fi + # Determine auth token for release API requests ACTION_TOKEN="${{ inputs.action-repo-token }}" if [ -z "$ACTION_TOKEN" ]; then @@ -158,11 +198,11 @@ runs: if [ "${{ inputs.version }}" = "latest" ]; then if [ "$VCS_TYPE" = "github" ]; then - # Use github.api_url which resolves correctly for both github.com - # (https://api.github.com) and GHES (https:///api/v3) + # SECURITY: Use github.api_url which is a trusted platform-provided value. + # Never construct API URLs from user-supplied inputs on GitHub. API_URL="${GITHUB_API_URL}/repos/${ACTION_REPO}/releases?per_page=1" else - # Gitea API + # Gitea API — SERVER_URL was validated above API_URL="${SERVER_URL}/api/v1/repos/${ACTION_REPO}/releases?limit=1" fi @@ -193,9 +233,9 @@ runs: VERSION="${{ inputs.version }}" fi - # Validate outputs don't contain newlines (defensive against injection) - if printf '%s' "$VERSION" | grep -q $'\n'; then - echo "Error: VERSION contains unexpected newline" >&2 + # Validate VERSION: no newlines, no slashes, no whitespace (防止 path traversal) + if printf '%s' "$VERSION" | grep -qE '[\n\r/[:space:]]'; then + echo "Error: VERSION '${VERSION}' contains invalid characters (newline, slash, or whitespace)" >&2 exit 1 fi @@ -203,7 +243,13 @@ runs: echo "action_repo=${ACTION_REPO}" >> "$GITHUB_OUTPUT" echo "server_url=${SERVER_URL}" >> "$GITHUB_OUTPUT" echo "vcs_type=${VCS_TYPE}" >> "$GITHUB_OUTPUT" - echo "action_token=${ACTION_TOKEN}" >> "$GITHUB_OUTPUT" + + # SECURITY: Pass token via masked environment variable instead of step output. + # Step outputs can leak in debug logs; GITHUB_ENV with masking is safer. + if [ -n "$ACTION_TOKEN" ]; then + echo "::add-mask::${ACTION_TOKEN}" + echo "ACTION_TOKEN=${ACTION_TOKEN}" >> "$GITHUB_ENV" + fi - name: Cache review-bot binary id: cache @@ -220,16 +266,21 @@ runs: ACTION_REPO="${{ steps.version.outputs.action_repo }}" VERSION="${{ steps.version.outputs.version }}" VCS_TYPE="${{ steps.version.outputs.vcs_type }}" - ACTION_TOKEN="${{ steps.version.outputs.action_token }}" + # Read token from masked environment variable (set in Determine version step) + # Falls back to empty if not set (public repos don't need auth) + ACTION_TOKEN="${ACTION_TOKEN:-}" BINARY="review-bot-linux-amd64" - # Build auth args if token is available (supports private repos) - AUTH_ARGS="" - if [ -n "$ACTION_TOKEN" ]; then - if [ "$VCS_TYPE" = "github" ]; then - AUTH_ARGS="-H \"Authorization: Bearer ${ACTION_TOKEN}\"" - else - AUTH_ARGS="-H \"Authorization: token ${ACTION_TOKEN}\"" + # SECURITY: On GitHub/GHES, use github.server_url for downloads. + # SERVER_URL is already set correctly per VCS_TYPE in Determine version step, + # but verify the download destination matches expectations. + if [ "$VCS_TYPE" = "github" ]; then + # Double-check: SERVER_URL must be github.server_url (platform-provided) + EXPECTED_SERVER="${{ github.server_url }}" + EXPECTED_SERVER="${EXPECTED_SERVER%/}" + if [ "$SERVER_URL" != "$EXPECTED_SERVER" ]; then + echo "Error: SERVER_URL mismatch on GitHub runner (possible tampering)" >&2 + exit 1 fi fi