-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
ID=Iv23liGyTZJYhySo4cEM | ||
SECRET=a0d16e1977f5dbd754649d9daa7d19d8ef32f38b | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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
Suggested change
🧰 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
Comment on lines
+17
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
🧰 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
This file was deleted.
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 To run the botuvicorn main:app --host 0.0.0.0 --port 8000 --reload Forward the port using ngrok+For local development and testing: ngrok http 8000 +## Features
In README.md lines 1 to 19, the documentation is minimal and lacks important
|
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): | ||
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""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) |
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}", | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix the Authorization header format. The Authorization header should include the authentication scheme. GitHub API expects either 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
Suggested change
🤖 Prompt for AI Agents
|
||||||
"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") |
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} | ||
## | ||
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
|
Uh oh!
There was an error while loading. Please reload this page.