> your AI agent picks dependencies from memory; give it dated facts — try starlog.dev ↗ vet your agent's deps ↗ vibe-coding is fine. vibe-importing isn’t. — try starlog.dev ↗ vibe-importing isn’t fine ↗ your agent has never seen your private packages — try starlog.dev ↗ facts for private packages ↗ a linter for the dependencies your AI agent picks — try starlog.dev ↗ a linter for agent deps ↗

Back to Articles

CVE Half-Day Watcher: Catching the Exploit Window Between Disclosure and Release

[ View on GitHub ]

CVE Half-Day Watcher: Catching the Exploit Window Between Disclosure and Release

Hook

When a security fix hits GitHub but hasn't shipped in a release yet, you're not protecting users—you're publishing an exploitation roadmap. This window, called a 'half-day vulnerability,' is where most real-world breaches start.

Context

Traditional vulnerability tracking operates on a comforting fiction: that coordinated disclosure works. A researcher finds a bug, notifies the vendor privately, waits 90 days, then publishes a CVE alongside a patched release. Users update, attackers arrive too late, everyone stays safe.

Reality is messier. Developers commit fixes to public GitHub repos immediately—sometimes because they don't recognize the security implications, sometimes because their workflow doesn't accommodate secret branches, sometimes because CI/CD won't run on private commits. The CVE gets assigned weeks later. The official release? Maybe another month after that, once QA completes and release managers schedule it. During this gap, the vulnerability is public (anyone can read the diff), actively discussed (in PRs and issues), and completely unpatched for users who install from package managers. Attackers call these 'N-day' or '1-day' exploits, but they're functionally zero-days for the 99% of users who don't compile from HEAD. CVE Half-Day Watcher exists to systematically identify this window—either by scanning the NVD for CVEs whose fixes haven't shipped, or by monitoring your own repositories for suspicious issues that might become CVEs before you've released a fix.

Technical Insight

Repo Scan Mode

NVD Mode

Recent CVEs

Extract GitHub URLs

Fetch releases

Compare commit SHA

No

Yes

Pull PRs/Issues

Suspicious terms

Validated

NVD API

CVE Parser

GitHub API Layer

Release Checker

Fix in release?

Half-Day Vuln Detected

Already Released

Keyword Matcher

OpenAI Validator

stdout

System architecture — auto-generated

The tool's architecture splits into two operational modes that share a common GitHub API interaction layer. The first mode—NVD feed scanning—is where the "half-day" concept becomes tangible. It polls the National Vulnerability Database API for recent CVEs, extracts GitHub URLs from the references section (commits, pull requests, issues), then performs the critical check: has this commit made it into a tagged release?

Here's the core logic that determines exposure:

def check_commit_in_releases(repo_owner, repo_name, commit_sha, github_token):
    headers = {'Authorization': f'token {github_token}'}
    releases_url = f'https://api.github.com/repos/{repo_owner}/{repo_name}/releases'
    
    response = requests.get(releases_url, headers=headers)
    releases = response.json()
    
    for release in releases:
        tag_name = release['tag_name']
        tag_url = f'https://api.github.com/repos/{repo_owner}/{repo_name}/git/refs/tags/{tag_name}'
        tag_response = requests.get(tag_url, headers=headers)
        tag_data = tag_response.json()
        
        # Compare commit SHA from CVE reference against release tag commit
        if tag_data.get('object', {}).get('sha') == commit_sha:
            return True, release['tag_name']
    
    return False, None

This deceptively simple check exposes the vulnerability window. When it returns False, you've found a CVE whose fix exists in the codebase but hasn't shipped to users. The time between the commit timestamp and now is your exploitation window—often measured in weeks or months.

The second mode inverts this workflow for proactive scanning. Instead of starting with CVEs, it pulls open issues and PRs from repositories you specify, then runs keyword matching against terms like 'vulnerability', 'exploit', 'CVE', 'security', 'bypass', and 'injection'. The suspicious word list is hardcoded:

SUSPICIOUS_KEYWORDS = [
    'vulnerability', 'exploit', 'cve', 'security', 
    'bypass', 'injection', 'xss', 'csrf', 'rce',
    'privilege escalation', 'buffer overflow'
]

def scan_repo_for_suspicious_activity(repo_owner, repo_name, github_token):
    issues_url = f'https://api.github.com/repos/{repo_owner}/{repo_name}/issues'
    headers = {'Authorization': f'token {github_token}'}
    
    response = requests.get(issues_url, headers=headers, params={'state': 'open'})
    issues = response.json()
    
    suspicious_items = []
    for issue in issues:
        text = f"{issue['title']} {issue.get('body', '')}".lower()
        if any(keyword in text for keyword in SUSPICIOUS_KEYWORDS):
            suspicious_items.append(issue)
    
    return suspicious_items

The keyword approach generates massive false positives—any security-focused project will trigger on nearly every issue. To filter noise, the tool includes optional OpenAI integration that sends issue text to GPT-4 with a prompt asking "Is this a real security vulnerability or just discussion about security topics?" It's pragmatic but expensive; scanning an active repository could burn through API credits fast.

The GitHub API interactions are synchronous and lack sophisticated rate limiting. The tool relies entirely on GitHub's response headers for throttling, and crashes ungracefully when limits hit:

response = requests.get(api_url, headers=headers)
if response.status_code == 403:
    print("Rate limit exceeded")
    sys.exit(1)

No exponential backoff, no queuing, no persistent state to resume from. For ad-hoc queries this is acceptable; for continuous monitoring it's fatal.

The release-checking logic reveals a key architectural insight: the tool doesn't just verify that code exists—it verifies distribution. A commit sitting in main doesn't help users who pip install or npm install from the last tagged release. By specifically checking release tags, CVE Half-Day Watcher identifies the availability gap that matters for exploitation. However, it stops one layer too early: it doesn't verify whether those GitHub releases actually propagate to package managers, where the real distribution happens. A tagged release on GitHub might never get published to PyPI or npm, extending the half-day window indefinitely.

Output is printed directly to stdout as JSON-like structures, with no database persistence or historical tracking. Each execution is stateless—you can't trend how long vulnerabilities typically remain in the half-day window for specific projects, or track when fixes eventually ship. For incident response this is fine; for research or metrics it's limiting.

Gotcha

The API rate limiting will ruin your day immediately. GitHub's authenticated API provides 5,000 requests per hour, which sounds generous until you realize that checking a single CVE against a repository with 50 releases consumes 51 requests (one for releases list, one per tag). Scan 100 CVEs and you've exhausted your quota, forcing a one-hour wait. The synchronous implementation means you're not even parallelizing these calls—it's sequential blocking requests that crash the entire script when limits hit. No retry logic, no intelligent queueing, no fallback to conditional requests using ETags. For production use you'd need to add Redis-backed rate limiting, request caching, and resumable scan state.

The keyword matching for suspicious issues is nearly unusable without the OpenAI filter, and the OpenAI filter is too expensive for broad scanning. Running it against Kubernetes, which discusses security constantly and legitimately, would flag hundreds of issues and cost dollars per scan in API fees. The prompt engineering is minimal—just "analyze this text for security content"—with no few-shot examples or domain-specific tuning. You'll get better results with a locally-run sentence transformer model to detect semantic similarity to known CVE descriptions, but that requires ML infrastructure the tool doesn't provide. The 150-star minimum threshold for repository scanning is arbitrary and excludes critical infrastructure projects with small communities.

Verdict

Use if: You're doing incident response or red team reconnaissance and need to quickly identify which recent CVEs have public fixes but no available releases—this automates the tedious GitHub archaeology that would otherwise take hours per CVE. Also valuable if you maintain high-value projects and want to audit whether your own PRs are leaking security context before you've shipped fixes. The proactive scanning mode is useful for security-conscious teams doing pre-release audits of their issue trackers.

Skip if: You need continuous monitoring, historical trending, or production-grade reliability. The stateless design, API rate limit crashes, and stdout-only output make this a one-shot investigative tool, not infrastructure. Also skip if you can't manually review results—the false positive rate from keyword matching is too high for automated alerting, even with OpenAI filtering. For systematic vulnerability tracking across many projects, you want OSV Scanner or a commercial solution with proper persistence and deduplication. This is a sharp knife for specific surgical tasks, not a platform for ongoing threat intelligence.