Skip to content

orb(anti-abuse): the blacklist and every contributor cap key on the renameable login — a banned contributor clears all of them, and re-enters the auto-merge path, by renaming #9125

Description

@JSONbored

Problem

Every contributor-scoped anti-abuse control keys on the mutable GitHub login. The immutable numeric
user id is present in every webhook payload and is discarded at content ingest, so a contributor clears
all of these controls at once by renaming their account — a free, instantaneous, self-service action.

findBlacklistEntry (packages/loopover-engine/src/settings/contributor-blacklist.ts:67-71):

const key = login.toLowerCase();
return (entries ?? []).find((entry) => entry.login.toLowerCase() === key) ?? null;

ContributorBlacklistEntry has no id field at all — the module validates entries against a GitHub
login regex (:12) and nothing else.

The ingest side matches: PR/issue upsert writes only authorLogin: pr.user?.login, and src/db/schema.ts
has no author-id column on pull_requests, issues, recent_merged_pull_requests, or the moderation
tally. Note the id is not universally unavailable — src/auth/security.ts:316 and
src/orb/oauth.ts:100,112 both capture user.id on the OAuth/enrollment paths. It is specifically the
webhook content-ingest path that drops it.

Controls that consequently reset on rename:

control site
blacklist deterministic close (fires ahead of all merit/CI/AI analysis) contributor-blacklist.ts:67
open-PR / open-item caps src/db/repositories.ts:4602,4616
moderation ban tally src/db/repositories.ts:2977 ("persist by login")
review-nag ping counter countRecentAuditEventsForActorInRepo(env, commenter, …)
autoCloseExemptLogins settings resolution

Trigger

A contributor blacklisted as spammer99 renames to spammer99x. GitHub carries the account, its PRs and
its history across the rename. The next PR arrives with user.login = "spammer99x"; findBlacklistEntry
returns null; the deterministic blacklist close never fires; the PR re-enters full merit review and is
eligible for auto-merge. In the same move their open-PR count resets to 0 (cap bypassed), their
moderation ban tally resets to 0, and their review-nag budget resets.

Impact

Wrong-merge path. The one production anti-abuse control documented as zero-hallucination and immune to the
close-precision breaker is bypassable by the attacker's own action, and the bypass simultaneously clears
every rate limit that would otherwise slow a repeat offender. On a repo whose merges drive upstream
rewards, this is the highest-value control to defeat and the cheapest to defeat.

Dedup

Distinct from #9079 (miner detection keyed on the renameable login). There, githubId is already
fetched and merely discarded at the comparison — the fix is a one-line comparison change. Here ORB has
no immutable contributor identity anywhere in the content schema: user.id is dropped at the webhook
boundary, so the fix is an ingest + schema change plus an optional id on the blacklist entry. Different
subsystem, different remediation. They should land together, since both stem from the same missing column.

Requirements

  • Persist author_github_id on pull_requests and issues at upsert (contiguous migrations/NNNN_*.sql),
    populated from the webhook payload's user.id.
  • Add an optional githubId to ContributorBlacklistEntry and match on id-when-present ∪ login, so
    existing login-only entries keep working and new entries can be rename-proof.
  • Move the open-item caps, the moderation tally, and the nag counter to id-when-present.
  • Backfill is best-effort — document which historical rows cannot be attributed rather than guessing.
  • A rename should be observable: when a known id appears under a new login, record an audit event.

Test Coverage Requirements

99%+ patch coverage, branch-counted. Both arms of id-present / id-absent matching, plus a regression test
that a blacklisted id under a new login is still matched.

Links & Resources

Boundaries

Identity keying for contributor-scoped controls. No change to blacklist semantics, cap thresholds, or the
close copy.

maintainer-only — anti-abuse and schema authority.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions