Skip to content

[bug] dev-lead reviewer-trust gate reads the PR author's association, not the reviewer's — untrusted-reviewer skip is dead code #1417

Closed

Description

@don-petry

Story

As a maintainer relying on dev-lead's reviewer-trust gate,
I want the gate to evaluate the reviewer's repo association rather than the PR author's,
so that "only a trusted human's review drives dev-lead's fix/push path" is actually enforced instead of silently always-true on dev-lead-authored PRs.

Problem

In the pull_request_review branch of scripts/dev-lead-intent.sh, the trust gate reads the wrong field:

# scripts/dev-lead-intent.sh:261
author_assoc=$(jq -r '.pull_request.author_association // empty' "$EVENT_PATH" ...)
...
# scripts/dev-lead-intent.sh:~300
elif is_human_trusted "$author_assoc"; then
    emit_intent "review-changes" "human-review-${review_state}" "$context"
else
    emit_skip "untrusted-reviewer"
fi

.pull_request.author_association is the PR author's association with the repo. The reviewer's association is .review.author_association. So is_human_trusted — whose evident intent is "is this reviewer a trusted human" — is actually asking "is the PR author trusted."

Consequence. The branch is only reached after the is_dev_lead_authored gate (#1311), so the PR author is always dev-lead's own identity (don-petry, OWNER). is_human_trusted is therefore always true on that path:

  • Every non-trusted-bot reviewer routes to review-changes, whatever their actual association.
  • The untrusted-reviewer skip is dead code on the very path it exists to protect.

This is inconsistent with the sibling branches, which get it right — issue_comment (:316) and pull_request_review_comment (:367) both read .comment.author_association, i.e. the commenter's association. Only the review branch reads from .pull_request.

Practical exposure is currently bounded (the repo is private, so only users with access can review, and fork PRs are skipped earlier), which is why this is a correctness/defence-in-depth fix rather than an active incident. But a trust gate that never rejects is not a trust gate — the same class of defect as #1415 (a gate whose control is not actually load-bearing) and #860 normative rule 2.

Acceptance Criteria

  1. The pull_request_review branch evaluates the reviewer's association, sourced from .review.author_association, not .pull_request.author_association.
  2. A review from an author_association outside OWNER|MEMBER|COLLABORATOR on a dev-lead-authored PR emits skip with reason untrusted-reviewer — i.e. the skip branch becomes reachable and is proven reachable by a test.
  3. A review from a trusted human (OWNER/MEMBER/COLLABORATOR) still routes to review-changes, and a trusted-bot review still routes to fix-reviews / enable-auto-merge — no regression to the existing paths.
  4. Fails closed: a missing or unparseable reviewer association is treated as untrusted (skip), never as trusted.
  5. Bats coverage with a fixture per case: trusted-human reviewer, untrusted reviewer (the currently-impossible case — the regression guard), trusted bot, missing association. Fixtures assert the reviewer's association is what moves the decision, e.g. by holding the PR author constant at OWNER while varying .review.author_association.
  6. The three branches are checked for consistency: pull_request_review, issue_comment, and pull_request_review_comment all gate on the association of the actor who produced the event.

Tasks / Subtasks

Dev Notes

Project Structure Notes

scripts/dev-lead-intent.sh (the pull_request_review branch) plus its bats suite under tests/dev-lead/unit/; fixtures alongside the existing intent fixtures.

References

Likely target surface

  • scripts/dev-lead-intent.sh

Found while verifying whether a review posted from a non-don-petry identity would be honoured by dev-lead (the #1415 workaround). Filed under epic #1402.

Activity

  1. github-actions commented on Aug 2, 2026

    @github-actions
    Contributor

    Thank you for the thorough write-up — the field-level diff between .pull_request.author_association and .review.author_association, the always-true analysis, and the dead-code consequence are all clearly laid out and make triage straightforward. Before this lands, the dev notes flag one open question worth confirming up front: does the secondary identity you tested as a #1415 workaround resolve to an association within OWNER|MEMBER|COLLABORATOR, or would the corrected gate reject its reviews and leave dev-lead in a block-but-not-act state on that path?

  2. don-petry commented on Aug 2, 2026

    @don-petry
    CollaboratorAuthor

    Dev-Lead Implementation Plan

    Issue: #1417 — [bug] dev-lead reviewer-trust gate reads the PR author's association, not the reviewer's — untrusted-reviewer skip is dead code

    Scope

    Fix the pull_request_review branch of scripts/dev-lead-intent.sh so the reviewer-trust gate evaluates the reviewer's association (.review.author_association) instead of the PR author's (.pull_request.author_association), making the untrusted-reviewer skip reachable and fail-closed.

    Implementation Checklist

    Tests to Write

    • Trusted-human reviewer (PR author held at OWNER, reviewer assoc COLLABORATOR) → review-changes
    • Untrusted reviewer (PR author held at OWNER, reviewer assoc CONTRIBUTOR) → skip untrusted-reviewer — the regression guard that fails against today's code
    • Trusted bot reviewer (PR author held at OWNER, reviewer assoc NONE) → fix-reviews — proves the bot path ignores assoc, no regression
    • Missing reviewer association (PR author held at OWNER, .review.author_association absent) → skip untrusted-reviewer — fail-closed
    • Fixtures hold .pull_request.author_association at OWNER and vary only .review.author_association, so each test would fail against the pre-fix code

    Test Command

    bats tests/dev-lead/unit/ (CI: .github/workflows/test-dev-lead.yml), plus bash .dev-lead/scripts/dev-lead-lint.sh

  3. don-petry commented on Aug 2, 2026

    @don-petry
    CollaboratorAuthor

    Dev-Lead: Implementation Complete

    Plan Execution

    Test Results

    # New reviewer-trust cases (tests 10–13 in test_intent_reviews.bats):
    ok 10 reviews: reviewer-trust — trusted-human reviewer (COLLABORATOR) → review-changes
    ok 11 reviews: reviewer-trust — untrusted reviewer (CONTRIBUTOR) on OWNER-authored PR → skip untrusted-reviewer
    ok 12 reviews: reviewer-trust — trusted bot (assoc NONE) → fix-reviews (bot path ignores assoc)
    ok 13 reviews: reviewer-trust — missing reviewer association fails closed → skip untrusted-reviewer
    
    # Red-phase confirmation (against pre-fix code): tests 11 and 13 FAILED (routed review-changes
    # because the old gate read the PR author's OWNER association) — proving the guards are load-bearing.
    
    # Full dev-lead unit suite:
    761 passing, 0 failing
    
    # Required lint:
    bash .dev-lead/scripts/dev-lead-lint.sh → all checks passed
    (shellcheck on 102 files, 4 agent profiles, CODEOWNERS)
    

    Files Changed

    • scripts/dev-lead-intent.sh: reviewer-trust gate reads .review.author_association (reviewer), local renamed to reviewer_assoc, fail-closed comment added
    • tests/dev-lead/unit/test_intent_reviews.bats: new "reviewer-trust gate" section with the four cases
    • tests/dev-lead/fixtures/events/pr_review_human_owner.json: added .review.author_association: OWNER so the trusted-human case stays valid under the fixed gate
    • tests/dev-lead/fixtures/events/pr_review_reviewer_trusted_human.json (new): PR author OWNER, reviewer COLLABORATOR → review-changes
    • tests/dev-lead/fixtures/events/pr_review_reviewer_untrusted.json (new): PR author OWNER, reviewer CONTRIBUTOR → skip untrusted-reviewer (the regression guard)
    • tests/dev-lead/fixtures/events/pr_review_reviewer_trusted_bot.json (new): PR author OWNER, trusted bot with assoc NONE → fix-reviews
    • tests/dev-lead/fixtures/events/pr_review_reviewer_missing_assoc.json (new): PR author OWNER, no .review.author_association → skip untrusted-reviewer (fail-closed)

    Notes

  4. added a commit that references this issue on Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugBug reportsdev-leadFor dev-lead agent pickupinitiativeEpic / initiative tracking issueneeds-human-reviewFlagged by automated PR review agentsecuritySecurity-related PRs and issues

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions