Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/review-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ jobs:
add-prompt-files: ${{ inputs.add-prompt-files }}
model: ${{ inputs.model }}
github-token: ${{ steps.app-token.outputs.token || github.token }}
trusted-bot-app-id: ${{ secrets.CAGENT_REVIEWER_APP_ID }}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ MEDIUM: Same app ID used for both reviewer identity and authorization bypass

The workflow passes secrets.CAGENT_REVIEWER_APP_ID as the trusted-bot-app-id input. This creates a circular trust model where:

  • The app that posts reviews (reviewer identity)
  • Is also trusted to bypass authorization checks when it comments /review

While this may be intentional for legitimate automation (e.g., a trusted triage bot triggering reviews), if the app's credentials are compromised or if someone exploits the app's webhook, this bypass would allow unauthorized reviews.

Recommendation: Consider using separate apps for triggering (automation) and posting (identity), or add additional verification that the app is being used in the expected context.

anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
google-api-key: ${{ secrets.GOOGLE_API_KEY }}
Expand Down
24 changes: 22 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ inputs:
description: "Additional arguments to pass to cagent run"
required: false
default: ""
trusted-bot-app-id:
description: "GitHub App ID of a trusted bot that can bypass comment-based auth checks (e.g., for self-review triggers)"
required: false
default: ""
add-prompt-files:
description: "Comma-separated list of files to append to the prompt (e.g., 'AGENTS.md,CLAUDE.md')"
required: false
Expand Down Expand Up @@ -190,10 +194,12 @@ runs:
shell: bash
env:
ACTION_PATH: ${{ github.action_path }}
# Get author_association from comment events (the main risk)
COMMENT_ASSOCIATION: ${{ github.event.comment.author_association }}
TRUSTED_BOT_APP_ID: ${{ inputs.trusted-bot-app-id }}
DEBUG: ${{ inputs.debug }}
run: |
# Read comment fields directly from the event payload (cannot be overridden by workflow env vars)
COMMENT_ASSOCIATION=$(jq -r '.comment.author_association // empty' "$GITHUB_EVENT_PATH")

# Only enforce auth for comment-triggered events
# This prevents abuse via /commands while allowing PR-triggered workflows to run
if [ -z "$COMMENT_ASSOCIATION" ]; then
Expand All @@ -202,6 +208,20 @@ runs:
exit 0
fi

# Allow a trusted GitHub App bot to bypass auth (e.g., auto-triage posts /review).
# Verified via user type + app ID from the event payload to prevent spoofing.
if [ -n "$TRUSTED_BOT_APP_ID" ]; then
COMMENT_USER_TYPE=$(jq -r '.comment.user.type // empty' "$GITHUB_EVENT_PATH")
COMMENT_APP_ID=$(jq -r '.comment.performed_via_github_app.id // empty' "$GITHUB_EVENT_PATH")

if [ "$COMMENT_USER_TYPE" = "Bot" ] && [ -n "$COMMENT_APP_ID" ] && [ "$COMMENT_APP_ID" = "$TRUSTED_BOT_APP_ID" ]; then
COMMENT_USER_LOGIN=$(jq -r '.comment.user.login // empty' "$GITHUB_EVENT_PATH")
echo "ℹ️ Skipping auth check (trusted bot: $COMMENT_USER_LOGIN, app_id: $COMMENT_APP_ID)"
echo "authorized=bot" >> $GITHUB_OUTPUT
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ MEDIUM: Bot bypass skips all authorization checks without fallback verification

When the bot bypass succeeds, the script immediately exits with authorized=bot, skipping the hardcoded ALLOWED_ROLES check that follows.

While this is by design, the lack of fallback verification creates risk if the app ID check is weak (e.g., Finding #1 shows it can succeed with empty IDs). If the app is compromised or misconfigured, this removes all authorization safeguards.

Recommendation: Add defense-in-depth:

  • Log the bypass event more prominently for audit purposes
  • Consider adding a secondary check that the bot has appropriate repository permissions
  • Or at minimum, add explicit non-empty validation to prevent the empty ID bypass bug

exit 0
fi
fi

echo "Using comment author_association: $COMMENT_ASSOCIATION"

# Allowed roles (hardcoded for security - cannot be overridden)
Expand Down
6 changes: 6 additions & 0 deletions review-pr/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ inputs:
description: "Comma-separated list of files to append to the prompt (e.g., 'AGENTS.md,CLAUDE.md')"
required: false
default: ""
trusted-bot-app-id:
description: "GitHub App ID of a trusted bot that can bypass comment-based auth checks"
required: false
default: ""

outputs:
exit-code:
Expand Down Expand Up @@ -464,6 +468,7 @@ runs:
nebius-api-key: ${{ inputs.nebius-api-key }}
mistral-api-key: ${{ inputs.mistral-api-key }}
github-token: ${{ steps.resolve-token.outputs.token }}
trusted-bot-app-id: ${{ inputs.trusted-bot-app-id }}
extra-args: ${{ inputs.model && format('--model={0}', inputs.model) || '' }}

# ========================================
Expand Down Expand Up @@ -551,6 +556,7 @@ runs:
nebius-api-key: ${{ inputs.nebius-api-key }}
mistral-api-key: ${{ inputs.mistral-api-key }}
github-token: ${{ steps.resolve-token.outputs.token }}
trusted-bot-app-id: ${{ inputs.trusted-bot-app-id }}
extra-args: ${{ inputs.model && format('--model={0}', inputs.model) || '' }}
add-prompt-files: ${{ inputs.add-prompt-files }}

Expand Down
Loading