fix(github): apply allow_authors to the webhook sender (#7868)

_author_login returned the issue/PR creator for issues and pull_request
events. On edited/labeled/reopened actions the actor is sender, so an
allow_authors user editing someone else's issue was gated on mention,
while anyone editing an allow_authors user's issue bypassed it. Prefer
sender.login and keep the object author as a fallback.

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
aduoge 2026-11-04 21:35:55 +08:00 committed by GitHub
parent 4c4a4e9858
commit 26bf78f2b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 39 additions and 0 deletions

View File

@ -81,6 +81,11 @@ def _comment_body(event: str, payload: dict[str, Any]) -> str:
def _author_login(event: str, payload: dict[str, Any]) -> str | None:
"""Login of the human who triggered the event, for ``allow_authors``."""
# Object authors need not be the actor on edits, labels, or dismissals.
# Keep the object fallback for payloads without a sender identity.
sender_login = (payload.get("sender") or {}).get("login")
if isinstance(sender_login, str):
return sender_login
if event in ("issue_comment", "pull_request_review_comment"):
login = (payload.get("comment") or {}).get("user", {}).get("login")
elif event == "pull_request":

View File

@ -47,6 +47,40 @@ async def _drain(bus: MessageBus) -> list[InboundMessage]:
return out
@pytest.mark.asyncio
@pytest.mark.parametrize("event, object_key", [("issues", "issue"), ("pull_request", "pull_request")])
@pytest.mark.parametrize("author, sender, should_fire", [("owner", "other", False), ("other", "owner", True)])
async def test_allow_authors_uses_update_sender(base_dir: Path, event: str, object_key: str, author: str, sender: str, should_fire: bool) -> None:
"""Editing another user's issue/PR must use the editor's mention exemption."""
_write_agent(
base_dir,
"default",
"reviewer",
{
"name": "reviewer",
"github": {"bindings": [{"repo": "a/b", "triggers": {event: {"actions": ["edited"], "require_mention": True, "allow_authors": ["owner"]}}}]},
},
)
bus = MessageBus()
result = await fanout_event(
bus,
event,
"delivery-edited",
{
"action": "edited",
object_key: {"number": 7, "user": {"login": author}, "title": "Updated title", "body": "No mention"},
"repository": {"full_name": "a/b"},
"sender": {"login": sender},
},
)
assert result["matched_agents"] == ["reviewer"]
assert result["fired_agents"] == (["reviewer"] if should_fire else [])
messages = await _drain(bus)
assert len(messages) == int(should_fire)
if messages:
assert messages[0].user_id == sender
@pytest.mark.asyncio
async def test_fanout_redelivery_progresses_beyond_queue_sized_prefix(base_dir: Path) -> None:
"""A redelivery must not keep failing on the same queue-sized prefix."""