0xBugLetter

Bot

How the archive fills itself

The bot runs as a GitHub Action once a day. It checks the feeds, writes a new entry to the archive for anything genuinely new, and posts it to Discord. No server, no hosting, no review queue — it commits directly.

This is fully automatic — read this before trusting a classification

RSS gives a title and a summary, nothing more. There’s no reliable way to know the real bug type or severity from that alone, so the bot guesses from keywords in the title and summary. It gets things wrong sometimes — a research post about CSS-driven data exfiltration could easily get misfiled near XSS by a careless pattern, for instance. Every auto-archived entry is marked with a comment at the top of its file, and fixing one is just editing the YAML and committing the correction. See the classification notes for the exact rules.

How it decides what to archive

Three rules do the work, and each exists because a simpler version behaved badly.

Only sources marked verified: true

This is the one guardrail standing in for the human judgment a manual curator used to apply per article. Medium and other low-signal aggregators were removed from sources.yaml for exactly this reason.

One article per source, per pass

The bot cycles through the sources instead of walking the file top to bottom. Without this, whichever feed sits first in sources.yaml takes every slot — in practice PortSwigger consumed all three every day and the sources further down were never reached.

Anything older than 45 days is backlog

Several of these feeds still expose posts from 2017–2023. A feed being new to the bot doesn’t make its archive news, so old entries are skipped rather than added as a “new read”. This also stops a freshly added source from dumping years of history into the archive at once.

How it avoids duplicates

There’s no separate history file. Before writing anything, the bot scans every file already in data/writeups/ and builds a set of URLs already archived — whether the bot wrote that entry or a human did. If a URL is already there, it’s skipped. The archive is the state.

One consequence: a feed has to expose a real, distinct URL per entry for this to work. A couple of podcast hosts don’t — every episode shares the same homepage link — and for those the bot skips the entire source rather than silently losing episodes to a false duplicate match.

Run it on your own server

  1. 1

    Create a webhook in your server

    In Discord: Channel settings → Integrations → Webhooks → New webhook. Name it, pick the channel you want the posts in, and copy the URL.

    That URL is a credential. Anyone who has it can post to your channel — don’t paste it into an issue and don’t commit it.

  2. 2

    Fork the repository

    Fork G3kSec/0xBugLetter. The workflow is already configured; all it needs is the credential. Note that running your own fork means your fork archives independently — it’s a separate copy of the data, not a mirror.

  3. 3

    Store the webhook as a secret

    In your fork: Settings → Secrets and variables → Actions → New repository secret.

    Secret
    Name:   DISCORD_WEBHOOK
    Value:  https://discord.com/api/webhooks/...
  4. 4

    Try it locally first

    Before wiring up the real webhook, see exactly what the bot would archive and how it classifies each entry — no webhook required, nothing written:

    Terminal
    pip install -r bot/requirements.txt
    python bot/index.py --dry-run
  5. 5

    Run it for real

    Open the Actions tab in your fork, pick the Daily Post workflow and hit Run workflow. If the webhook is correct, the new entries land in data/writeups/ and the posts show up in your channel within seconds.

Want it posted somewhere other than Discord?

Right now the bot only supports Discord. The sending logic is isolated in bot/index.py, so adding another destination is a contained change — and the PR is welcome.