-
Notifications
You must be signed in to change notification settings - Fork 4
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
Changes from 1 commit
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 | ||
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| SECRET=a0d16e1977f5dbd754649d9daa7d19d8ef32f38b | ||
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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: | ||||||||||||||||||||||||||||||||||
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||
| API_TOKEN: ${{ secrets.API_TOKEN }} | ||||||||||||||||||||||||||||||||||
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||
| WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }} | ||||||||||||||||||||||||||||||||||
| ORG_TOKEN: ${{ secrets.ORG_TOKEN }} | ||||||||||||||||||||||||||||||||||
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||
| 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
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||
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 --reloadForward 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 | ||
|
|
||
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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.""" | ||
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| # 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) | ||
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # 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) | ||
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| except ValueError: | ||
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| # 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,17 @@ | ||
| from fastapi import HTTPException, logger | ||
| import requests | ||
|
|
||
|
|
||
arvi18 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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) | ||
|
|
||
arvi18 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,122 +0,0 @@ | ||
| 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} | ||
Uh oh!
There was an error while loading. Please reload this page.