-
Notifications
You must be signed in to change notification settings - Fork 101
fix(dispatch): distinguish permission-check failures from unauthorized skips #5219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -129,6 +129,8 @@ jobs: | |
| # Uses the collaborator permission API which correctly resolves org | ||
| # membership regardless of visibility (private vs public). | ||
| # See: github/gh-aw-mcpg#2862 | ||
| # Returns 0 if username has write access, 1 if not, 2 on operational failure | ||
| # (mktemp/gh api errors). Callers must distinguish 1 vs 2 when messaging. | ||
| has_write_permission() { | ||
| local username="${1:-}" | ||
| if [[ -z "${username}" ]]; then | ||
|
|
@@ -137,13 +139,13 @@ jobs: | |
| local role api_err | ||
| api_err=$(mktemp) || { | ||
| echo "::warning::Failed to create temp file for permission check of ${username}" >&2 | ||
| return 1 | ||
| return 2 | ||
| } | ||
| role=$(gh api "repos/${GITHUB_REPOSITORY}/collaborators/${username}/permission" \ | ||
| --jq '.role_name' 2>"${api_err}") || { | ||
| echo "::warning::Permission API call failed for ${username}: $(cat "${api_err}")" >&2 | ||
| rm -f "${api_err}" | ||
| return 1 | ||
| return 2 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [MEDIUM] The docstring asserts every
So the common unauthorized case is classified correctly, but a deleted/renamed account gets the "failed to verify permissions" notice, which suggests a retryable infra problem when it is not. Fail-closed either way — message accuracy only. Suggestion: Match Same pattern in Flagged by 3 agents (Claude x2, Grok) — consensus |
||
| } | ||
| rm -f "${api_err}" | ||
| case "${role}" in | ||
|
|
@@ -176,6 +178,34 @@ jobs: | |
| return 1 | ||
| } | ||
|
|
||
| # Helper: check if the comment is from a user | ||
| comment_from_user() { | ||
| if [[ "${COMMENT_USER_TYPE}" == "Bot" ]]; then | ||
| echo "::notice::Skipping dispatch for bot comment" | ||
| return 1 | ||
| fi | ||
| return 0 | ||
| } | ||
|
|
||
| # Helper: check if the comment is from an authorized user | ||
| comment_from_authorized_user() { | ||
| if ! comment_from_user; then | ||
| return 1 | ||
| fi | ||
| local auth_rc=0 | ||
| is_authorized || auth_rc=$? | ||
| case "${auth_rc}" in | ||
| 0) return 0 ;; | ||
| 1) | ||
| echo "::notice::Skipping dispatch for unauthorized comment from ${COMMENT_USER_LOGIN}" | ||
| ;; | ||
| 2) | ||
| echo "::notice::Skipping dispatch: failed to verify permissions for ${COMMENT_USER_LOGIN}" | ||
| ;; | ||
| esac | ||
| return 1 | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [LOW] Missing
Same pattern in the scaffold copy at Suggestion: Add a catch-all before *)
echo "::warning::Unexpected auth exit code ${auth_rc} for ${COMMENT_USER_LOGIN}" >&2
;;Previous review (superseded)[MEDIUM] Missing
Same pattern in the scaffold copy at Suggestion: Add a catch-all before *)
echo "::warning::Unexpected auth exit code ${auth_rc} for ${COMMENT_USER_LOGIN}" >&2
;;Flagged by 3 agents (Claude x2, Grok) — consensus |
||
|
|
||
| COMMAND="" | ||
| if [[ -n "${COMMENT_BODY:-}" ]]; then | ||
| COMMAND="$(printf '%s\n' "${COMMENT_BODY}" | head -1 | tr -d '\r' | awk '{print $1}')" | ||
|
|
@@ -185,34 +215,34 @@ jobs: | |
| issue_comment) | ||
| case "${COMMAND}" in | ||
| /fs-triage) | ||
| if [[ "${COMMENT_USER_TYPE}" != "Bot" ]] && is_authorized; then | ||
| if comment_from_authorized_user; then | ||
| STAGE="triage" | ||
| fi | ||
| ;; | ||
| /fs-code) | ||
| if [[ "${ISSUE_IS_PR}" == "false" ]]; then | ||
| if [[ "${COMMENT_USER_TYPE}" != "Bot" ]] && is_authorized; then | ||
| if comment_from_authorized_user; then | ||
| STAGE="code" | ||
| fi | ||
| fi | ||
| ;; | ||
| /fs-review) | ||
| if [[ "${ISSUE_IS_PR}" == "true" ]]; then | ||
| if [[ "${COMMENT_USER_TYPE}" != "Bot" ]] && is_authorized; then | ||
| if comment_from_authorized_user; then | ||
| STAGE="review" | ||
| fi | ||
| fi | ||
| ;; | ||
| /fs-fix) | ||
| if [[ "${ISSUE_IS_PR}" == "true" ]]; then | ||
| if [[ "${COMMENT_USER_TYPE}" != "Bot" ]] && is_authorized; then | ||
| if comment_from_authorized_user; then | ||
| STAGE="fix" | ||
| TRIGGER_SOURCE="${COMMENT_USER_LOGIN}" | ||
| fi | ||
| fi | ||
| ;; | ||
| /fs-retro|/fullsend) | ||
| if [[ "${COMMENT_USER_TYPE}" != "Bot" ]] && is_authorized; then | ||
| if comment_from_authorized_user; then | ||
| if [[ "${COMMAND}" == "/fullsend" ]]; then | ||
| SECOND_WORD="$(printf '%s\n' "${COMMENT_BODY}" | head -1 | tr -d '\r' | awk '{print $2}')" | ||
| if [[ "${SECOND_WORD}" == "retro" ]]; then | ||
|
|
@@ -224,7 +254,7 @@ jobs: | |
| fi | ||
| ;; | ||
| /fs-prioritize) | ||
| if [[ "${COMMENT_USER_TYPE}" != "Bot" ]] && is_authorized; then | ||
| if comment_from_authorized_user; then | ||
| STAGE="prioritize" | ||
| fi | ||
| ;; | ||
|
|
@@ -233,7 +263,7 @@ jobs: | |
| # re-trigger triage by providing clarification on needs-info | ||
| # issues. Full write-permission check is not required here. | ||
| if has_label "needs-info" && ! has_label "feature"; then | ||
| if [[ "${COMMENT_USER_TYPE}" != "Bot" ]]; then | ||
| if comment_from_user; then | ||
| if [[ "${COMMENT_AUTHOR_ASSOC}" != "NONE" ]] || is_issue_author; then | ||
| STAGE="triage" | ||
| fi | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -224,9 +224,15 @@ func TestDispatchWorkflowContent(t *testing.T) { | |
| assert.Contains(t, s, `COMMENT_AUTHOR_ASSOC`) | ||
| // Auto-triage requires assoc != NONE or issue author | ||
| assert.Contains(t, s, "is_issue_author") | ||
| // Bot filtering | ||
| // Bot filtering and skip notices via shared helpers | ||
| assert.Contains(t, s, `COMMENT_USER_TYPE`) | ||
| assert.Contains(t, s, `!= "Bot"`) | ||
| assert.Contains(t, s, `comment_from_user`) | ||
| assert.Contains(t, s, `comment_from_authorized_user`) | ||
| assert.Contains(t, s, `== "Bot"`) | ||
| assert.Contains(t, s, `Skipping dispatch for bot comment`) | ||
| assert.Contains(t, s, `Skipping dispatch for unauthorized comment`) | ||
| assert.Contains(t, s, `Skipping dispatch: failed to verify permissions`) | ||
| assert.Contains(t, s, `return 2`) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [MEDIUM] Test assertions pin strings anywhere in the file, not the exit-code behavior being fixed All new assertions are file-wide Suggestion: Assert on compound snippets that tie exit codes to their branches (e.g. the literal Flagged by 3 agents (Claude x2, Grok) — consensus; severity settled at MEDIUM (assessed HIGH by one agent, LOW by another) |
||
| // No-fix label check (uses PR_LABELS for pull_request_review events) | ||
| assert.Contains(t, s, "fullsend-no-fix") | ||
| assert.Contains(t, s, "PR_LABELS") | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[MEDIUM] Event-actor auth callers don't distinguish exit codes, violating the new docstring contract
The new docstring declares "Callers must distinguish 1 vs 2 when messaging." But
is_event_actor_authorized()is still called in plain booleanifcontext at 3 call sites (lines ~278, ~282, ~301 in this file; same in scaffold), emitting no skip notices and treating exit codes 1 and 2 identically:Fail-closed is correct — dispatch is blocked for all non-zero. The issue is observability: when
has_write_permissionreturns 2 (API failure) on the event-triggered path, there's zero audit trail, unlike the comment path which now logs distinct notices. The docstring creates a false expectation that all callers handle the distinction.Suggestion: Either (a) narrow the docstring to "Comment-path callers should distinguish 1 vs 2 for observability; event-path callers currently treat all non-zero as skip", or (b) create an
event_from_authorized_actor()wrapper with case-based notices matchingcomment_from_authorized_user().Flagged by 3 agents (Claude x2, Grok) — consensus