Skip to content

Automating Secret Detection with GitHub Secret Scanning API

This is a follow-up to the morning article

Morning article: Complete Guide for Handling Accidental Secret Exposure on GitHub

Goals

  • Implement periodic monitoring scripts using Secret Scanning API
  • Build automatic notification system (Slack/Discord) for detection events
  • Complete automation with GitHub Actions and retry strategies for failures

Architecture Overview

The monitoring system consists of three components:

  1. API Poller: Periodically fetches alerts via GitHub API
  2. Notification Handler: Sends new detections to Slack/Discord
  3. State Manager: Manages state to prevent duplicate notifications

Implementation Steps

Step 1: Python Monitoring Script Implementation

scripts/secret_scanner.py:

import os
import json
import requests
from datetime import datetime
from typing import List, Dict

class SecretScanner:
    def __init__(self, token: str, repo: str):
        self.token = token
        self.repo = repo
        self.api_base = f"https://api.github.com/repos/{repo}"
        self.headers = {
            "Authorization": f"token {token}",
            "Accept": "application/vnd.github.v3+json"
        }
        self.state_file = ".scanner_state.json"

    def get_alerts(self) -> List[Dict]:
        """Fetch unresolved secret alerts"""
        url = f"{self.api_base}/secret-scanning/alerts"
        params = {"state": "open", "per_page": 100}

        response = requests.get(url, headers=self.headers, params=params)
        if response.status_code == 404:
            return []  # Secret scanning not enabled
        response.raise_for_status()
        return response.json()

    def filter_new_alerts(self, alerts: List[Dict]) -> List[Dict]:
        """Filter only new alerts"""
        known_ids = self.load_state()
        new_alerts = [a for a in alerts if a["number"] not in known_ids]

        # Update state
        if new_alerts:
            for alert in new_alerts:
                known_ids.add(alert["number"])
            self.save_state(known_ids)

        return new_alerts

Step 2: Notification Handler Implementation

class NotificationHandler:
    def __init__(self, webhook_url: str, platform: str = "slack"):
        self.webhook_url = webhook_url
        self.platform = platform

    def send_alert(self, alert: Dict) -> bool:
        """Send alert to Slack/Discord"""
        if self.platform == "slack":
            payload = self._format_slack_message(alert)
        else:
            payload = self._format_discord_message(alert)

        response = requests.post(self.webhook_url, json=payload)
        return response.status_code == 200

    def _format_slack_message(self, alert: Dict) -> Dict:
        return {
            "text": "🚨 Secret detected in repository",
            "attachments": [{
                "color": "danger",
                "fields": [
                    {"title": "Secret Type", "value": alert["secret_type"], "short": True},
                    {"title": "File", "value": alert["locations"][0]["path"], "short": True},
                    {"title": "Detected", "value": alert["created_at"], "short": False},
                    {"title": "URL", "value": alert["html_url"], "short": False}
                ]
            }]
        }

Step 3: GitHub Actions Workflow Setup

.github/workflows/secret-monitoring.yml:

name: Secret Scanning Monitor
on:
  schedule:
    - cron: '*/30 * * * *'  # Every 30 minutes
  workflow_dispatch:  # Manual execution enabled

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: pip install requests

      - name: Run scanner with retry
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
        run: |
          for i in 1 2 3; do
            python scripts/secret_scanner.py && break
            echo "Attempt $i failed, retrying..."
            sleep 10
          done

Benchmark / Comparison

Monitoring MethodDetection SpeedFalse Positive RateOperational Cost
Manual Check24+ hoursHigh (many misses)High (human effort)
Git hooks onlyImmediateMedium (local only)Low
API Periodic MonitoringWithin 30 minLow (GitHub verified)Very Low (automated)
API + WebhooksReal-timeLowestLow (setup only)

Failure Patterns and Mitigations

SymptomCauseMitigation
404 ErrorSecret scanning not enabledEnable in repository settings or graceful handling
Rate limit exceededToo many API callsAdjust interval to 30+ min, conditional polling
Duplicate notificationsPoor state managementPersist .scanner_state.json with Actions artifacts
Webhook failureURL expired/permissionsConfigure fallback notification destination

Extension Ideas for Automation

  • Priority Assessment: Auto-classify urgency based on secret_type
  • Auto-fix PR: Create PR to update .gitignore upon detection
  • Issue Creation: Auto-create GitHub Issues for each alert
  • Metrics Collection: Send detection frequency to Datadog/CloudWatch
  • Compliance Reports: Auto-generate monthly summaries

Next Steps

Build upon this guide to implement further security enhancements and production optimization.