CoreMesh

AI Agent for Bug Bounty Hunting: 6 Merged PRs and $0 Paid

I let an AI agent hunt open-source bounties for weeks: 375 candidates a day, 6 merged PRs, $0 paid. Here's the funnel and the checks I'd add first.

AI Agent for Bug Bounty Hunting: 6 Merged PRs and $0 Paid

An AI agent for bug bounty hunting can do the engineering part. Mine found open issues with bounty labels, wrote patches, and got 6 pull requests merged. What it couldn't do was get paid. After weeks of running the pipeline, the verified payout was $0, and I paused it.

The money problem wasn't a model problem. It was the funnel. Most of what the radar surfaced was noise or already taken, and every dollar that showed up as "pending" came from repos that were never going to pay. If you're deciding whether to build one of these, the numbers below are the part I wish I'd seen first.

What an AI agent for bug bounty hunting actually does#

Strip the hype away and the pipeline has five stages. Each stage is where a different kind of failure lives.

  1. Discover. Search GitHub for open issues carrying a bounty label or a reward in the title. The GitHub issue search API does most of this work.
  2. Filter. Throw away aggregator reposts, speculative "would pay for this" comments, issues in languages the agent doesn't handle well, and issues someone else has already claimed.
  3. Verify the reward. Check who is offering the money and whether that account looks like it can pay.
  4. Attempt. Clone, reproduce, patch, run the tests, open a PR.
  5. Track. Watch the PR for review, merge, and, separately, payment.

The discovery query is the easy part. With a token in $GITHUB_TOKEN, this returns open, unassigned issues labeled bounty:

bash
curl -s \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer $GITHUB_TOKEN" \
  "https://api.github.com/search/issues?q=label:bounty+state:open+no:assignee+is:issue&per_page=100"

The query syntax is documented on GitHub's page for searching issues and pull requests, and the endpoint itself is in the REST search reference. Everything after this call is where the real work, and the real loss, happens.

One day of candidates, and how few survive#

Here is what a single day looked like once the filters were in place.

Stage Issues
Unique candidates seen by the radar 375
Aggregator reposts or speculative noise 103
Failed the language filter 58
Already had a claim comment 31
Looked eligible before the reward check 18

A few notes on each cut.

Aggregator noise is the biggest bucket. Bounty aggregators mirror the same issue into their own trackers, and some issues are just a comment saying "I'd pay for this" with no reward attached. Both look like bounties to a naive search and neither is one.

The language filter is boring but necessary. If the agent's test harness only knows how to run certain toolchains, an issue in anything else is an expensive way to produce a PR that doesn't pass CI.

Claim comments are the quiet killer. By the time a bounty is visible in search, someone has often already posted "working on this." Skipping those is cheap and saves you from racing a human who started first.

What's left after the remaining checks, 18 issues, is a small number. And that's before anyone asks whether the money is real.

The triage logic itself is not complicated. This is the shape of it, on made-up sample data so you can see the funnel without an API token:

python
SUPPORTED = {"python", "javascript", "typescript"}

issues = [
    {"id": 1, "title": "Fix race in scheduler", "lang": "python", "source": "repo", "comments": ["I'll take this"]},
    {"id": 2, "title": "Bounty: CSV export", "lang": "rust", "source": "repo", "comments": []},
    {"id": 3, "title": "[mirror] Fix race in scheduler", "lang": "python", "source": "aggregator", "comments": []},
    {"id": 4, "title": "Would pay for dark mode", "lang": "typescript", "source": "speculative", "comments": []},
    {"id": 5, "title": "Bounty: null deref on empty config", "lang": "python", "source": "repo", "comments": []},
]

CLAIM_WORDS = ("working on this", "i'll take", "claimed", "assigned to me")

def is_claimed(comments):
    return any(w in c.lower() for c in comments for w in CLAIM_WORDS)

counts = {"noise": 0, "language": 0, "claimed": 0, "eligible": 0}
eligible = []
for issue in issues:
    if issue["source"] != "repo":
        counts["noise"] += 1
    elif issue["lang"] not in SUPPORTED:
        counts["language"] += 1
    elif is_claimed(issue["comments"]):
        counts["claimed"] += 1
    else:
        counts["eligible"] += 1
        eligible.append(issue["id"])

print(counts)
print("eligible issue ids:", eligible)

The order of the checks matters for cost. Source and language are free string checks. Reading comments means another API call per issue, so it goes last.

What went wrong: the $415 that never existed#

This is the section I'd want someone to read before they build anything.

At one point the tracker showed $415 in pending payouts. Six PRs merged, money on the way. Every dollar of it came from bounty repos with the same profile. The owner account was three days old. It had zero followers. And the PRs were merged through that same account. Nobody independent had looked at the code, nobody independent had posted the reward, and nobody ever paid. The number went from "pending" to nothing, and the correct description of my earnings went back to $0.

In hindsight the pattern is obvious. A brand-new account posts a plausible bounty, lets agents and eager contributors do free work, merges whatever lands, and never pays. Whether the motive is free labor, farming activity on a fresh account, or something else, the result for you is the same.

A merge is not a payment. My tracker counted it as one, which is why "pending" ever reached $415.

The fix is cheap: check the account before the agent spends anything. The GitHub users endpoint returns the fields you need, and it's documented in the REST users reference:

bash
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
  https://api.github.com/users/OWNER | jq '{login, created_at, followers, public_repos}'

Then the gate is a few lines. Pick your own thresholds; the point is that the check exists and runs before any model call:

python
from datetime import datetime, timedelta, timezone

MIN_ACCOUNT_AGE_DAYS = 90
MIN_FOLLOWERS = 5

def account_risk(created_at, followers, merged_by_owner):
    created = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
    age_days = (datetime.now(timezone.utc) - created).days
    reasons = []
    if age_days < MIN_ACCOUNT_AGE_DAYS:
        reasons.append(f"account is {age_days} days old")
    if followers < MIN_FOLLOWERS:
        reasons.append(f"only {followers} followers")
    if merged_by_owner:
        reasons.append("owner merges their own bounty PRs")
    return ("SKIP", reasons) if reasons else ("OK", reasons)

three_days_ago = (datetime.now(timezone.utc) - timedelta(days=3)).strftime("%Y-%m-%dT%H:%M:%SZ")
print(account_risk(three_days_ago, 0, True))
print(account_risk("2019-06-01T12:00:00Z", 240, False))

That gate would have rejected every repo behind the $415 before a single model call, and the pipeline would have reported the honest number weeks sooner.

How bounty hunters get paid, and why merge is not the milestone#

If you searched "how do bounty hunters get paid," here's the practical version for open-source issue bounties, as opposed to vendor security programs where a company pays for vulnerability reports.

The reward is posted by someone: a maintainer, a sponsor, or a platform acting on their behalf. In the best case, a platform holds the money up front and pays out when the maintainer marks the issue solved. In the worst case, the reward is a sentence in an issue body and payment depends entirely on the goodwill of whoever wrote it. Most bounties sit somewhere between, and the issue page rarely tells you which.

So the sequence has three separate events: the PR is merged, the issue is marked solved by the person who owns the bounty, and the money moves. An agent can observe the first. It can sometimes observe the second. It can only observe the third if your tracker checks a real balance, not a label.

Treat the payout as unverified until it reaches an account you control. "Pending" turned out to mean nothing.

Handling tasks the agent can't do#

Not every eligible issue is solvable from a laptop. One bounty required real car hardware to reproduce and test. The model did the right thing: it refused to fabricate code it couldn't verify and returned an empty diff.

My pipeline then did the wrong thing. It took the empty diff and sent it to the review stage anyway, which spent an expensive model call reviewing nothing. The agent was honest; the orchestration around it wasn't listening.

Now a no-op result is a first-class status. If the attempt stage produces an empty diff or reports that the task needs something the environment doesn't have, the task is marked NOT_FEASIBLE and review never runs:

python
def route(attempt):
    if not attempt.diff.strip() or attempt.needs_hardware:
        return "NOT_FEASIBLE"
    return "REVIEW"

Two things make this work. First, tell the model in the attempt prompt that "I can't do this, and here's why" is a valid answer, so it doesn't feel pushed into producing something. The guidance in prompt engineering techniques that work in any AI chat covers how to give a model an explicit way out. Second, treat review as the expensive call it is and guard it. Anything that keeps review from running on garbage pays for itself, and for the calls that do run, Claude prompt caching is the next lever on cost.

How much do bug bounty hunters make with an agent#

I can't give you an industry figure, and I'd distrust anyone who does without showing their funnel. What I can give you is mine: weeks of work, 6 merged PRs, $0 verified.

The cost side is the part people skip. Every candidate that reaches the attempt stage costs model calls to read the code, write the patch, and run the review loop. The 375-to-18 funnel above means the filter stages do most of the work for almost nothing, and the expensive stages run on a handful of issues. If your filters are weaker than that, your costs scale with the noise, not the opportunity.

Measure it. If your pipeline runs on Claude Code, the guide to Claude Code OpenTelemetry gen_ai attributes shows how to read token counts per call so you can put a cost next to every stage. A pipeline that can't tell you what each merged PR cost can't tell you whether a bounty was worth taking, even if it had paid.

And the honest answer to "how to get into bounty hunting" with an agent is: get good at filtering before you get good at fixing. Fixing is what the model is already good at. Filtering is what decides whether any of it was worth doing.

Checklist before you let an agent hunt bounties#

  • Confirm the bounty is attached to the issue by the repo owner or a platform, not a comment from a stranger.
  • Check the owner account's age and followers before any model call. Skip fresh accounts with no history.
  • Skip repos where the owner reviews and merges their own bounty PRs.
  • Skip issues with a claim comment. Someone started first.
  • Filter by language before reading any code.
  • Give the model an explicit way to say a task isn't feasible, and route that status around review.
  • Track payment as a separate event from merge. "Pending" is not a number.
  • Put a cost next to every stage so you know what each PR cost, paid or not.
  • Decide in advance what result makes you stop. Mine was weeks of work and $0.

Quick answers

Can an AI agent for bug bounty hunting actually make money?

Mine didn't. After weeks it had 6 merged PRs and $0 in verified payouts, so I paused it. The code side works; the problem is that most open-source bounties have no verified money behind them.

How do bug bounty hunters get paid?

For open-source issue bounties, the reward is posted by a maintainer, sponsor, or platform, and payment is a separate step after the PR is merged and the issue is marked solved. A merge is not a payment, so track the money as its own event.

How much do bug bounty hunters make with an AI agent?

I can't give you an industry figure, and the only number I can stand behind is my own: $0 verified after 6 merged PRs. Costs matter as much as rewards, so measure what each stage of your pipeline spends.

How do I get into bounty hunting with an agent without getting burned?

Start with filtering, not fixing. Drop aggregator reposts, already-claimed issues, and repos owned by brand-new accounts with no followers before the agent does any work, and give the model a way to mark a task as not feasible.

Did this solve it?

One tap. No account.

Saved you an hour? Send a tip.

USDC or ETH on Base, Ethereum, Arbitrum and more. Keeps the notes free and the examples running.

Base

Network

0xc783bc5c3fa14cEC3f09A22284bC91C5AF0d59e1
Open in wallet · 5 USDC

On a computer? Scan the code with your phone wallet, or copy the address.

Pick the network your wallet or exchange uses. USDC works on all six; ETH on Base, Ethereum, Arbitrum and Optimism. Don't send on Tron or Solana: this address doesn't exist there. Tips are optional and unlock nothing; every guide stays free.

Discussion

Start the discussion

Indra Wijaya

Builds and runs AI agents, bots and content pipelines, and writes down what works and what broke.

About · Email · RSS

Keep reading

New notes, no spam.

One post at a time, only when there's something worth reading. Follow in any feed reader.

Subscribe via RSS