Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/github/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ pub struct PrChangedEvent {
pub head_sha: String,
pub base_ref: String,
pub author_login: String,
/// GitHub's relationship between the PR author and the repository.
pub author_association: Option<String>,
pub draft: bool,
}

Expand Down
2 changes: 2 additions & 0 deletions crates/webhook/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ pub struct PullRequest {
pub head: Option<GitRef>,
pub base: Option<GitRef>,
pub user: Option<User>,
pub author_association: Option<String>,
}

#[derive(Debug, Deserialize)]
Expand Down Expand Up @@ -257,6 +258,7 @@ pub fn parse_event(event_type: &str, payload: &WebhookPayload) -> GitHubEvent {
head_sha: head.sha.clone(),
base_ref: base.git_ref.clone(),
author_login: user.login.clone(),
author_association: pr.author_association.clone(),
draft: pr.draft,
});
}
Expand Down
27 changes: 27 additions & 0 deletions crates/webhook/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,17 @@ async fn event_to_task(state: &AppState, event: GitHubEvent) -> Option<Task> {
let label = e.label_name.as_deref()?;
scope.familiar_by_label(label)?
} else {
// Automatic lifecycle reviews execute repository-controlled
// automation, so only authors GitHub identifies as having a
// trusted repository relationship may start one. Missing or
// external associations fail closed. A maintainer-applied
// review label remains the explicit opt-in path above.
if !matches!(
e.author_association.as_deref(),
Some("OWNER" | "MEMBER" | "COLLABORATOR")
) {
return None;
}
if !scope.reviews_enabled() {
return None;
}
Expand Down Expand Up @@ -1782,6 +1793,7 @@ mod review_lane_tests {
head_sha: "abc123".to_string(),
base_ref: "main".to_string(),
author_login: "octocat".to_string(),
author_association: Some("MEMBER".to_string()),
draft: false,
}
}
Expand Down Expand Up @@ -1830,6 +1842,20 @@ mod review_lane_tests {
);
}

#[tokio::test]
async fn automatic_reviews_reject_untrusted_pr_authors() {
let state = app_state_with_review(review_on());
for association in [None, Some("NONE"), Some("CONTRIBUTOR")] {
let mut event = pr_event("opened");
event.author_association = association.map(str::to_string);
assert!(
event_to_task(&state, GitHubEvent::PullRequestChanged(event))
.await
.is_none()
);
}
}
Comment on lines +1845 to +1857

#[tokio::test]
async fn familiar_authored_prs_are_never_auto_reviewed() {
// The adapter's own draft PRs must not re-trigger reviews (loop guard).
Expand Down Expand Up @@ -1878,6 +1904,7 @@ mod review_lane_tests {
let state = app_state();
let mut event = pr_event("labeled");
event.label_name = Some("coven:review".to_string());
event.author_association = Some("NONE".to_string());
event.draft = true;
let task = event_to_task(&state, GitHubEvent::PullRequestChanged(event))
.await
Expand Down
1 change: 1 addition & 0 deletions crates/webhook/tests/fixtures/pull_request_opened.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"title": "Add spell compiler cache",
"draft": false,
"user": { "login": "octocat" },
"author_association": "MEMBER",
"head": { "ref": "feat/spell-cache", "sha": "abc123def4567890abc123def4567890abc123de" },
"base": { "ref": "main", "sha": "0001112223334445556667778889990001112223" }
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"title": "Harden sigil parser",
"draft": false,
"user": { "login": "hexadecimal-cat" },
"author_association": "MEMBER",
"head": { "ref": "fix/sigil-parser", "sha": "beadfeedbeadfeedbeadfeedbeadfeedbeadfeed" },
"base": { "ref": "main", "sha": "0001112223334445556667778889990001112223" }
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"title": "Add spell compiler cache",
"draft": true,
"user": { "login": "octocat" },
"author_association": "MEMBER",
"head": { "ref": "feat/spell-cache", "sha": "f00dfacef00dfacef00dfacef00dfacef00dface" },
"base": { "ref": "main", "sha": "0001112223334445556667778889990001112223" }
},
Expand Down
1 change: 1 addition & 0 deletions crates/webhook/tests/parse_fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ fn pull_request_opened_fixture_parses() {
assert_eq!(e.head_sha, "abc123def4567890abc123def4567890abc123de");
assert_eq!(e.base_ref, "main");
assert_eq!(e.author_login, "octocat");
assert_eq!(e.author_association.as_deref(), Some("MEMBER"));
assert!(!e.draft);
Comment on lines 141 to 145
}
other => panic!("expected PullRequestChanged, got {other:?}"),
Expand Down
Loading