How I Built an Automated GitHub Security Bot in a Weekend

Amey PacharkarAmey Pacharkar
4 min read

We've all felt that small pang of fear after a git push. Did I accidentally commit an API key? Is one of my dependencies secretly vulnerable? In a fast-paced development cycle, these small but critical issues can easily slip through, creating massive security holes and technical debt.

Manually reviewing every commit is impossible. We need a tireless, automated team member who never sleeps. So, I decided to build one.

The Solution: "Sentry", a Proactive Guardian for Your Code

I built Sentry, a simple but powerful, event-driven bot that hooks directly into the GitHub workflow. It proactively scans every new commit for two critical issues:

  1. Accidentally committed secrets.

  2. Outdated and vulnerable dependencies.

When it finds something, it doesn't just send a silent notification; it opens a detailed, actionable issue right in your repository, providing immediate feedback where developers live.

Seeing is believing. Here it is in action, detecting an outdated dependency in a requirements.txt file seconds after the push:

The Tech Stack & The "Why"

I wanted to build this with modern, high-performance tools that were right for the job.

  • Backend: Python & FastAPI

    • Why? FastAPI's asynchronous nature is perfect for an API-driven service that spends most of its time waiting for network responses (from the GitHub API, the PyPI API, etc.). Its speed and built-in data validation with Pydantic make it a robust choice for building reliable webhooks.
  • Deployment: Render

    • Why? I needed a platform with a seamless Git-to-deployment workflow. Render's free tier for web services, automatic SSL, and simple environment variable management meant I could go from a local script to a live public endpoint in minutes.
  • GitHub Integration: PyGithub Library

    • Why? Instead of making raw HTTP requests, this library provides a clean, object-oriented way to interact with the GitHub API. This made tasks like fetching file content and creating issues much cleaner and more readable.

My Biggest Challenge: Debugging a Serverless "Cold Start"

The most interesting challenge wasn't a code bug; it was a ghost in the machine. After deploying, my bot stopped working. GitHub reported that every webhook delivery was failing with a timeout error.

This was incredibly frustrating. My local tests with ngrok had worked perfectly. The Render logs showed the application was "live," but my own log messages never appeared. The requests were vanishing into a void between the internet and my application code.

After ruling out code errors, I had an "Aha!" moment. I remembered that Render's free tier puts services to "sleep" after 15 minutes of inactivity. GitHub's webhooks, however, give up after just 10 seconds.

My app was taking 30-40 seconds to "wake up" from a cold start, so GitHub always timed out. The problem wasn't that the service was broken, but that it was too slow on its first request. This taught me a valuable lesson about the realities of serverless and free-tier hosting environments—sometimes the problem isn't in the code, but in the infrastructure's behavior.

Under the Hood: Checking for Outdated Dependencies

The core logic of the bot is straightforward. Here’s a look at the function that checks a requirements.txt file for outdated packages. It's a great example of the bot's workflow: parse a file, call an external API, and compare the results.

# Part of app.py

import requests
from packaging.version import parse as parse_version

def check_outdated_dependencies(file_content):
    """
    Parses requirements.txt content, checks each package against the PyPI API,
    and returns a list of outdated packages.
    """
    outdated_packages = []
    for line in file_content.splitlines():
        # First, strip away any inline comments
        line = line.split('#')[0].strip()

        # Ignore empty lines or lines that aren't version-pinned
        if not line or '==' not in line:
            continue

        try:
            package_name, specified_version = line.split('==', 1)

            # Call the official PyPI API
            response = requests.get(f"https://pypi.org/pypi/{package_name}/json")
            if response.status_code == 200:
                latest_version = response.json()['info']['version']

                # Use the 'packaging' library for robust version comparison
                if parse_version(latest_version) > parse_version(specified_version):
                    outdated_packages.append({
                        "name": package_name,
                        "current": specified_version,
                        "latest": latest_version
                    })
        except Exception as e:
            print(f"Could not parse or check dependency '{line}'. Error: {e}")

    return outdated_packages

A Tool is Born

This project was an incredible learning experience in building event-driven systems. It's a small step, but it's a real, working tool that solves a problem I care about. The next steps to make it even more robust would be to add a full test suite with pytest and refactor the authentication to use a proper GitHub App, allowing for easy, one-click installation by other users.

You can check out the complete source code and a detailed setup guide on GitHub.

Join the Conversation!

This was my approach to automated code quality. What other common security checks or best practices would you want to see a bot like this perform? I'd love to hear your ideas in the comments

0
Subscribe to my newsletter

Read articles from Amey Pacharkar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Amey Pacharkar
Amey Pacharkar