Skip to content

fix: reject cross-repo linked issues in mirror-path issue multiplier#1246

Open
kinjitakabe wants to merge 1 commit into
entrius:testfrom
kinjitakabe:fix/mirror-linked-issues-cross-repo
Open

fix: reject cross-repo linked issues in mirror-path issue multiplier#1246
kinjitakabe wants to merge 1 commit into
entrius:testfrom
kinjitakabe:fix/mirror-linked-issues-cross-repo

Conversation

@kinjitakabe
Copy link
Copy Markdown

@kinjitakabe kinjitakabe commented May 13, 2026

Summary

  • Add repository_full_name: Optional[str] to MirrorLinkedIssue; lowercase at parse time to match MirrorPullRequest.repo_full_name.
  • Gate _is_valid_linked_issue on the same-repo invariant when the field is populated; fail open on None (older mirror snapshots, current production payload).
  • 6 new test cases covering cross-repo / same-repo / missing / case-mismatch / end-to-end multiplier.

Closes the mirror-path half of #1019 / PR #1038. After #1202 the mirror path is the only PR scoring path; the cross-repo leak that was tolerable when legacy was authoritative is now the entire surface.

Failure mode this closes

_is_valid_linked_issue walks every anti-gaming gate from the legacy path (transferred, self-issue, created-after-PR, state_reason=COMPLETED, edited_after_merge, CLOSED, close-window) except the repository-identity gate — because MirrorLinkedIssue had no repository field to gate on.

A miner can earn 1.33×–1.66× on every PR by:

  1. Coordinating a second GitHub account B (any access, no privileges needed in the target repo).
  2. Filing trivial issue #X in B/throwaway-repo. State: OPEN.
  3. Submitting PR P_M to a registered repo with Closes B/throwaway-repo#X in the body. The cross-repo Closes does not auto-close #X because A has no write on B/throwaway-repo#X stays OPEN at merge.
  4. Within 24 h of P_M merging, B manually closes #X as COMPLETED.
  5. das-github-mirror surfaces P_M.linked_issues = [MirrorLinkedIssue(number=X, state='CLOSED', state_reason='COMPLETED', author_github_id=B, ...)] — no repository identity on the payload.
  6. All other gates pass. Multiplier applied: 1.33× (STANDARD), or 1.66× (MAINTAINER) if B has OWNER/MEMBER/COLLABORATOR on B/throwaway-repo (trivial when B owns it).

Per-PR uplift: 1.33×–1.66× on earned_score. Orchestration cost per fake link: one alt GitHub account + one trivial issue. Detection: indistinguishable from a legitimate same-repo Closes #N in any log line.

Layer 1 / Layer 2

This is intentionally a two-layer change:

Layer 1 — validator-side (this PR)

  • MirrorLinkedIssue.repository_full_name: Optional[str] = None, populated from data.get('repository_full_name') and lowercased to match the PR side.
  • New gate in _is_valid_linked_issue, placed after the existing transferred / author / created-at gates and before state_reason / close-window:
    if li.repository_full_name is not None and li.repository_full_name != pr.repo_full_name:
        bt.logging.warning(
            f'Skipping linked issue #{li.number} - cross-repo reference '
            f'(issue in {li.repository_full_name}, PR in {pr.repo_full_name})'
        )
        return False
  • Fails open when repository_full_name is None — no behavior change for the current production mirror snapshot. No-op until Layer 2 lands.

Layer 2 — das-github-mirror (separate / coordinated)

das-github-mirror populates repository_full_name on each linked_issues entry. This validator-side guard arms automatically the moment the field is served. No further validator-side change required.

A follow-up issue can later tighten the guard to fail-closed-on-unknown once the mirror is fully repopulated, but that would over-correct today and zero every issue multiplier in the system.

Tests

tests/validator/oss_contributions/mirror/test_scoring.py::TestLinkedIssueCrossRepo:

  • test_cross_repo_linked_issue_rejected — PR in entrius/gittensor-ui, issue payload says outsider/throwaway; _is_valid_linked_issue returns False.
  • test_same_repo_linked_issue_passes — same-repo populated → guard does not fire.
  • test_repo_identity_missing_falls_openNone (current production shape) → guard does not fire; regression guard for the no-op-today invariant.
  • test_cross_repo_case_insensitive — mixed-case OUTSIDER/Throwaway is lowercased at parse time; != comparison stays case-correct.
  • test_cross_repo_multiplier_stays_neutral — end-to-end: _calculate_issue_multiplier returns 1.0.
  • test_same_repo_multiplier_still_applies — end-to-end regression guard: known same-repo still earns STANDARD_ISSUE_MULTIPLIER.

Existing fixture _linked_issue(...) gained one optional kwarg (repository_full_name=None); 61 prior tests unchanged.

Test plan

  • pytest tests/validator/oss_contributions/mirror/test_scoring.py — 67/67 pass (6 new).
  • pytest tests/ — 731/731 pass, no regressions.
  • pyright — 0 errors / 0 warnings.
  • ruff check gittensor/ tests/ — clean.

Related

Out of scope

  • das-github-mirror schema change (Layer 2). Filed/tracked separately; this PR is the validator-side preparation.
  • Tightening to fail-closed-on-unknown. Premature until Layer 2 lands and the mirror snapshot is fully repopulated.

Fixes #1243

PR entrius#1038 fixed cross-repo `Closes other/repo#N` leakage on the legacy OSS
scoring path (entrius#1019), explicitly scoping out the mirror path because the
live mirror payload did not carry repository identity on linked issues.
After entrius#1202 stripped legacy and made mirror the sole PR-scoring path,
that scoping-out is now load-bearing: a miner can earn 1.33×–1.66× by
referencing a coordinated-account issue in any other repo.

Layer 1 of a 2-layer fix:

- Add `repository_full_name: Optional[str]` to `MirrorLinkedIssue`,
  lowercased at parse time to match `MirrorPullRequest.repo_full_name`.
- Gate `_is_valid_linked_issue` on the same-repo invariant when the
  field is populated; fail open when `None` so existing mirror snapshots
  behave as before.

Layer 2 (separate / coordinated): das-github-mirror populates
`repository_full_name` on each `linked_issues` entry. The guard arms
automatically once the field is served — no further validator-side
change required.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: mirror-path _is_valid_linked_issue accepts cross-repo Closes references

1 participant