Skip to content

Add PR Event Logger workflow and authentication methods; remove old r… #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
2 changes: 2 additions & 0 deletions .bot
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ID=Iv23liGyTZJYhySo4cEM
SECRET=a0d16e1977f5dbd754649d9daa7d19d8ef32f38b
Copy link

Choose a reason for hiding this comment

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

Critical Security Issue: Hardcoded credentials should never be committed to version control. This file contains what appears to be actual GitHub App ID and secret values. Remove this file immediately, add .bot to .gitignore, and rotate these credentials.

26 changes: 26 additions & 0 deletions .github/workflows/pr-reviewer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: PR Event Logger

on:
pull_request:
types: [opened, reopened, ready_for_review, review_requested]
issue_comment:
types: [created, edited]

jobs:
log-event:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'

- name: log existing secrets
env:
API_TOKEN: ${{ secrets.API_TOKEN }}
WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }}
ORG_TOKEN: ${{ secrets.ORG_TOKEN }}
run: echo $API_TOKEN $WEBHOOK_SECRET $ORG_TOKEN
Comment on lines +21 to +26
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical Security Issue: Secrets exposed in workflow logs.

The workflow logs secrets to stdout, which exposes them in GitHub Actions logs where they can be viewed by anyone with repository access.

Remove the secret logging immediately:

-      - name: log existing secrets
-        env: 
-          API_TOKEN: ${{ secrets.API_TOKEN }}
-          WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }}
-          ORG_TOKEN: ${{ secrets.ORG_TOKEN }}
-        run: echo $API_TOKEN $WEBHOOK_SECRET $ORG_TOKEN
+      - name: Verify secrets exist
+        env: 
+          API_TOKEN: ${{ secrets.API_TOKEN }}
+          WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }}
+          ORG_TOKEN: ${{ secrets.ORG_TOKEN }}
+        run: |
+          if [ -z "$API_TOKEN" ]; then echo "API_TOKEN not set"; exit 1; fi
+          if [ -z "$WEBHOOK_SECRET" ]; then echo "WEBHOOK_SECRET not set"; exit 1; fi  
+          if [ -z "$ORG_TOKEN" ]; then echo "ORG_TOKEN not set"; exit 1; fi
+          echo "All required secrets are configured"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: log existing secrets
env:
API_TOKEN: ${{ secrets.API_TOKEN }}
WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }}
ORG_TOKEN: ${{ secrets.ORG_TOKEN }}
run: echo $API_TOKEN $WEBHOOK_SECRET $ORG_TOKEN
- name: Verify secrets exist
env:
API_TOKEN: ${{ secrets.API_TOKEN }}
WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }}
ORG_TOKEN: ${{ secrets.ORG_TOKEN }}
run: |
if [ -z "$API_TOKEN" ]; then echo "API_TOKEN not set"; exit 1; fi
if [ -z "$WEBHOOK_SECRET" ]; then echo "WEBHOOK_SECRET not set"; exit 1; fi
if [ -z "$ORG_TOKEN" ]; then echo "ORG_TOKEN not set"; exit 1; fi
echo "All required secrets are configured"
🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 22-22: trailing spaces

(trailing-spaces)


[error] 26-26: no new line character at the end of file

(new-line-at-end-of-file)

🤖 Prompt for AI Agents
In .github/workflows/pr-reviewer.yml around lines 21 to 26, the workflow logs
sensitive secrets to stdout, exposing them in GitHub Actions logs. Remove the
entire step or at least the run command that echoes the secrets to prevent them
from being printed in the logs and ensure secrets remain confidential.

Comment on lines +17 to +26
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix workflow formatting and version issues.

Static analysis identified several issues that should be addressed.

Apply these fixes:

       - name: Set up Python
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: '3.9'

-      - name: log existing secrets
-        env: 
+      - name: log existing secrets  
+        env:
           API_TOKEN: ${{ secrets.API_TOKEN }}
           WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }}
           ORG_TOKEN: ${{ secrets.ORG_TOKEN }}
-        run: echo $API_TOKEN $WEBHOOK_SECRET $ORG_TOKEN
+        run: echo $API_TOKEN $WEBHOOK_SECRET $ORG_TOKEN

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 actionlint (1.7.7)

17-17: the runner of "actions/setup-python@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🪛 YAMLlint (1.37.1)

[error] 22-22: trailing spaces

(trailing-spaces)


[error] 26-26: no new line character at the end of file

(new-line-at-end-of-file)

🤖 Prompt for AI Agents
In .github/workflows/pr-reviewer.yml around lines 17 to 26, the workflow
formatting is incorrect and the Python setup action version should be updated.
Fix the indentation and structure of the YAML to comply with GitHub Actions
syntax, and update the actions/setup-python version to the latest stable release
(e.g., v4). Also, ensure environment variables are properly referenced and
echoed in the run step without syntax errors.

Copy link

Choose a reason for hiding this comment

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

Critical Security Issue: This workflow step exposes sensitive secrets in GitHub Actions logs. The echo $API_TOKEN $WEBHOOK_SECRET $ORG_TOKEN command will print all secrets to the build logs, making them accessible to anyone with repository access. Remove this step immediately and rotate the exposed credentials.

43 changes: 0 additions & 43 deletions .github/workflows/reviewer.yml

This file was deleted.

19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# PR Reviewer bot
A bot that helps you to review the PRs in your repository.

## Get started

### Install the dependencies
```bash
pip3 install -r requirements.txt
```

### To run the bot
```bash
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
```

### Forward the port using ngrok
```bash
ngrok http 8000
```
Comment on lines +1 to +19
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance documentation with more comprehensive information.

The README provides basic setup instructions but could be more helpful with additional details.

Suggested improvements:

 # PR Reviewer bot
-A bot that helps you to review the PRs in your repository.
+A bot that helps you to review the PRs in your repository by providing automated analysis and feedback.

 ## Get started

+### Prerequisites
+- Python 3.9 or higher
+- A GitHub repository with webhook access
+- ngrok (optional, for local development)
+
 ### Install the dependencies
 ```bash
 pip3 install -r requirements.txt

+### Configuration
+Set up the required environment variables or update the configuration as needed.
+

To run the bot

uvicorn main:app --host 0.0.0.0 --port 8000 --reload

Forward the port using ngrok

+For local development and testing:

ngrok http 8000

+## Features
+- Automated PR review and analysis
+- GitHub webhook integration
+- Secure authentication and token handling


<details>
<summary>🤖 Prompt for AI Agents</summary>

In README.md lines 1 to 19, the documentation is minimal and lacks important
details. Enhance it by adding a "Configuration" section that explains setting up
environment variables or configuration files needed for the bot. Also, add a
"Features" section listing key functionalities like automated PR review, GitHub
webhook integration, and secure authentication. This will provide users with
clearer guidance and a better understanding of the bot's capabilities.


</details>

<!-- This is an auto-generated comment by CodeRabbit -->

38 changes: 38 additions & 0 deletions auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import base64
import hashlib
import hmac
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

def decrypt_token(encrypted_token, iv, WEBHOOK_SECRET):
"""Decrypt API token using WEBHOOK_SECRET as the key."""
# Generate the key from WEBHOOK_SECRET in the same way as the GitHub Action
key = hashlib.sha256(WEBHOOK_SECRET.encode()).hexdigest()
key_bytes = bytes.fromhex(key)
iv_bytes = bytes.fromhex(iv)

# Base64 decode the encrypted token
encrypted_data = base64.b64decode(encrypted_token)

# Create cipher and decrypt
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
decrypted_bytes = cipher.decrypt(encrypted_data)

# Handle padding properly
try:
unpadded = unpad(decrypted_bytes, AES.block_size)
except ValueError:
# If unpadding fails, try to find the null termination
if b'\x00' in decrypted_bytes:
unpadded = decrypted_bytes[:decrypted_bytes.index(b'\x00')]
else:
unpadded = decrypted_bytes

return unpadded.decode('utf-8')


def verify_signature(secret, body, signature):
"""Verify GitHub webhook signature using HMAC-SHA256."""
mac = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
expected_signature = f"sha256={mac}"
return hmac.compare_digest(expected_signature, signature)
31 changes: 31 additions & 0 deletions github.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from fastapi import HTTPException
import requests
import time
import logging

logger = logging.getLogger(__name__)

def get_pr_commits(repo_full_name: str, pr_number: int, github_token: str, retries: int = 3, backoff_factor: float = 0.5):
"""Fetch the list of commits for a PR from GitHub API with retries and exception handling."""
url = f"https://api.github.com/repos/{repo_full_name}/pulls/{pr_number}/commits"
headers = {
"Authorization": f"{github_token}",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix the Authorization header format.

The Authorization header should include the authentication scheme. GitHub API expects either Bearer or token prefix.

Apply this diff to fix the header format:

-        "Authorization": f"{github_token}",
+        "Authorization": f"Bearer {github_token}",

If the token is a personal access token (classic), you can also use:

-        "Authorization": f"{github_token}",
+        "Authorization": f"token {github_token}",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"Authorization": f"{github_token}",
"Authorization": f"Bearer {github_token}",
🤖 Prompt for AI Agents
In github.py at line 12, the Authorization header is missing the required
authentication scheme prefix. Update the header value to include the prefix
"Bearer " or "token " before the github_token variable to comply with GitHub API
requirements.

"Accept": "application/vnd.github.v3+json"
}

for attempt in range(retries):
try:
response = requests.get(url, headers=headers, timeout=10)
if response.status_code == 200:
return response.json()
else:
logger.warning(f"Attempt {attempt + 1}: GitHub API returned {response.status_code} - {response.text}")
except requests.exceptions.RequestException as e:
logger.warning(f"Attempt {attempt + 1}: Request failed with exception: {e}")

sleep_time = backoff_factor * (2 ** attempt)
time.sleep(sleep_time)

# If all retries fail
logger.error(f"Failed to fetch commits for PR #{pr_number} in {repo_full_name} after {retries} attempts.")
raise HTTPException(status_code=502, detail="Failed to fetch PR commits from GitHub")
123 changes: 1 addition & 122 deletions listener.py
Original file line number Diff line number Diff line change
@@ -1,122 +1 @@
import hmac
import hashlib
import json
import logging
import os
import base64
import requests
from fastapi import APIRouter, Request, Header, HTTPException
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

from dotenv import load_dotenv

load_dotenv()

router = APIRouter()
WEBHOOK_SECRET = os.getenv("WEBHOOK_SECRET")

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)


def verify_signature(secret, body, signature):
"""Verify GitHub webhook signature using HMAC-SHA256."""
mac = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
expected_signature = f"sha256={mac}"
return hmac.compare_digest(expected_signature, signature)


def decrypt_token(encrypted_token, iv):
"""Decrypt API token using WEBHOOK_SECRET as the key."""
try:
# Generate the key from WEBHOOK_SECRET in the same way as the GitHub Action
key = hashlib.sha256(WEBHOOK_SECRET.encode()).hexdigest()
key_bytes = bytes.fromhex(key)
iv_bytes = bytes.fromhex(iv)

# Base64 decode the encrypted token
encrypted_data = base64.b64decode(encrypted_token)

# Create cipher and decrypt
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
decrypted_bytes = cipher.decrypt(encrypted_data)

# Handle padding properly
try:
unpadded = unpad(decrypted_bytes, AES.block_size)
except ValueError:
# If unpadding fails, try to find the null termination
if b'\x00' in decrypted_bytes:
unpadded = decrypted_bytes[:decrypted_bytes.index(b'\x00')]
else:
unpadded = decrypted_bytes

return unpadded.decode('utf-8')
except Exception as e:
logger.error(f"Token decryption error: {str(e)}")
raise HTTPException(status_code=500, detail="Failed to decrypt token")


def get_pr_commits(repo_full_name, pr_number, github_token):
"""Fetch the list of commits for a PR from GitHub API."""
url = f"https://api.github.com/repos/{repo_full_name}/pulls/{pr_number}/commits"
print(url)
headers = {"Authorization": f"{github_token}", "Accept": "application/vnd.github.v3+json"}

response = requests.get(url, headers=headers)

if response.status_code != 200:
logger.error(f"Failed to fetch commits: {response.text}")
raise HTTPException(status_code=500, detail="Error fetching PR commits")

return response.json()


@router.post("/github-webhook")
async def github_webhook(
request: Request,
x_hub_signature_256: str = Header(None),
x_encrypted_token: str = Header(None, alias="X-Encrypted-Token"),
x_token_iv: str = Header(None, alias="X-Token-IV")
):
"""Receives GitHub webhook payload and fetches PR commits if applicable."""
body = await request.body()

# Verify webhook signature
if WEBHOOK_SECRET and x_hub_signature_256:
if not verify_signature(WEBHOOK_SECRET, body, x_hub_signature_256):
logger.error("Signature verification failed")
raise HTTPException(status_code=403, detail="Invalid signature")

# Validate encrypted token headers
if not x_encrypted_token or not x_token_iv:
logger.error("Missing encryption headers")
raise HTTPException(status_code=403, detail="Missing token encryption headers")

# Decrypt the token
try:
github_token = decrypt_token(x_encrypted_token, x_token_iv)
except Exception as e:
logger.error(f"Token decryption failed: {str(e)}")
raise HTTPException(status_code=403, detail="Token decryption failed")

payload = await request.json()
# save this locally
with open("samples/payload.json", "w") as f:
json.dump(payload, f)
event_type = payload.get("action", "")

logger.info(f"Received GitHub event: {event_type}")

if event_type == "synchronize":
action = payload.get("action", "")
if action in ["opened", "synchronize", "reopened"]:
repo_full_name = payload["repository"]["full_name"]
pr_number = payload["pull_request"]["number"]
commits = get_pr_commits(repo_full_name, pr_number, github_token)

logger.info(f"Fetched {len(commits)} commits for PR #{pr_number}")
return {"message": "PR processed", "pr_number": pr_number, "commits_count": len(commits)}

return {"message": "Webhook received", "event": event_type}
##