fix(action): address security review - prevent token exfiltration and add input validation
PR Ready Gate / clear-labels (pull_request) Successful in 2s
CI / test (pull_request) Successful in 22s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 33s
CI / review (gpt-5, security, ., rodin/security-patterns, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m14s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m58s
PR Ready Gate / clear-labels (pull_request) Successful in 2s
CI / test (pull_request) Successful in 22s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 33s
CI / review (gpt-5, security, ., rodin/security-patterns, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m14s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m58s
Security fixes: - On GitHub/GHES (VCS_TYPE=github), inputs.gitea-url is now completely ignored. API calls use github.api_url; downloads use github.server_url. Tokens are never sent to user-supplied URLs. - Replace action_token step output with masked GITHUB_ENV variable to prevent token leakage in debug logs. - Validate action-repo against owner/repo pattern to prevent path traversal. - Validate SERVER_URL in Gitea path: require https:// scheme, reject whitespace and newlines. - Strengthen VERSION validation: block slashes and whitespace in addition to newlines. - Add integrity check in Install step: verify SERVER_URL matches github.server_url on GitHub runners. Addresses findings from security-review-bot on PR #121. Deferred: full IP-level SSRF defense (see #123).
This commit is contained in:
@@ -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://<host>/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://<host>/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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user