Parent: #1936
Problem
The contributor blacklist (src/settings/contributor-blacklist.ts) matches by exact login string; the contributor cap (contributorOpenPrCap/contributorOpenIssueCap) counts open items per repo. Neither has any signal for how OLD a GitHub account is or how fast it's opening PRs/issues. This is the classic ban-evasion gap: a banned login gets a fresh GitHub account created the same day, and because the blacklist matches by exact string and the cap is purely a per-repo open-item count, the new account gets FULL trust until it independently accumulates enough open items in ONE repo to trip the cap on its own. There is currently no cheaper, earlier signal available.
This is deliberately scoped as a much lower-risk first step than behavioral cross-account identity matching (a separate, much harder, higher-false-positive-risk problem not in scope here) — this is a simple, deterministic, GitHub-API-native signal (account creation date), not inference.
Requirements
- Add an optional
accountAgeDays threshold setting: PRs/issues from an account younger than the configured threshold get an advisory new-account label and/or a tighter effective open-item cap (e.g. half the configured contributorOpenPrCap), never an automatic close on account-age alone — this is a friction/visibility signal, not a ban.
- Fetch account creation date via GitHub's existing users API (one additional, cacheable lookup — GitHub
user.created_at on the GET /users/{username} endpoint).
- Config-driven per repo, off by default.
- Never fires for the repo owner, admin logins, or automation bots — same standing exemption as every other anti-abuse mechanism.
Deliverables
- The
accountAgeDays setting wired through the full config-as-code chain.
- A cached account-creation-date lookup (reusing existing GitHub-API caching infra where possible).
- The label/tighter-cap short-circuit logic in
src/settings/agent-actions.ts, following the same zero-hallucination short-circuit shape as blacklistMatch/contributorCapMatch.
- Tests covering: an account below the age threshold gets labeled/tighter-capped; an account above it is unaffected; the owner/admin/bot exemption holds.
Acceptance criteria
- A PR/issue from an account younger than the configured
accountAgeDays gets the new-account label and/or a tighter cap, per config.
- Off by default — zero behavior change for repos that haven't opted in.
- Never auto-closes on account age alone.
Expected outcome
A cheap, deterministic, false-positive-safe first line of defense against the "banned login gets a fresh account" evasion pattern — visibility and friction now, without the risk profile of behavioral cross-account matching.
Parent: #1936
Problem
The contributor blacklist (
src/settings/contributor-blacklist.ts) matches by exact login string; the contributor cap (contributorOpenPrCap/contributorOpenIssueCap) counts open items per repo. Neither has any signal for how OLD a GitHub account is or how fast it's opening PRs/issues. This is the classic ban-evasion gap: a banned login gets a fresh GitHub account created the same day, and because the blacklist matches by exact string and the cap is purely a per-repo open-item count, the new account gets FULL trust until it independently accumulates enough open items in ONE repo to trip the cap on its own. There is currently no cheaper, earlier signal available.This is deliberately scoped as a much lower-risk first step than behavioral cross-account identity matching (a separate, much harder, higher-false-positive-risk problem not in scope here) — this is a simple, deterministic, GitHub-API-native signal (account creation date), not inference.
Requirements
accountAgeDaysthreshold setting: PRs/issues from an account younger than the configured threshold get an advisorynew-accountlabel and/or a tighter effective open-item cap (e.g. half the configuredcontributorOpenPrCap), never an automatic close on account-age alone — this is a friction/visibility signal, not a ban.user.created_aton theGET /users/{username}endpoint).Deliverables
accountAgeDayssetting wired through the full config-as-code chain.src/settings/agent-actions.ts, following the same zero-hallucination short-circuit shape asblacklistMatch/contributorCapMatch.Acceptance criteria
accountAgeDaysgets thenew-accountlabel and/or a tighter cap, per config.Expected outcome
A cheap, deterministic, false-positive-safe first line of defense against the "banned login gets a fresh account" evasion pattern — visibility and friction now, without the risk profile of behavioral cross-account matching.