Skip to content
Merged
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
126 changes: 126 additions & 0 deletions .github/scripts/check-e2e-authorization-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/usr/bin/env bash
# check-e2e-authorization-test.sh — Tests for check-e2e-authorization.sh
#
# Uses a mock gh command to avoid hitting GitHub.
# Run from the repo root: bash .github/scripts/check-e2e-authorization-test.sh

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
AUTH_SCRIPT="${SCRIPT_DIR}/check-e2e-authorization.sh"
FAILURES=0

TMPDIR="$(mktemp -d)"
trap 'rm -rf "${TMPDIR}"' EXIT

MOCK_BIN="${TMPDIR}/bin"
mkdir -p "${MOCK_BIN}"

# Mock gh: default behavior returns CONTRIBUTOR association and no ok-to-test label.
setup_mock_gh() {
Comment thread
ralphbean marked this conversation as resolved.
Resolved
cat > "${MOCK_BIN}/gh" <<'MOCKEOF'
#!/usr/bin/env bash
# Default: PR endpoint returns CONTRIBUTOR with no ok-to-test label
if [[ "$1" == "api" ]] && [[ "$2" == *"/pulls/"* ]] && [[ "$2" != *"/files"* ]]; then
echo '{"author_association": "CONTRIBUTOR", "labels": [], "updated_at": "2026-01-01T00:00:00Z"}'
exit 0
fi
# Collaborator permission endpoint: default to read (not write)
if [[ "$1" == "api" ]] && [[ "$2" == *"/collaborators/"*"/permission"* ]]; then
echo '{"role_name": "read"}'
exit 0
fi
echo "mock-gh: unhandled call: $*" >&2
exit 1
MOCKEOF
chmod +x "${MOCK_BIN}/gh"
}

run_auth() {
# Run the script, capture stdout, suppress stderr
local output
output=$(
export PATH="${MOCK_BIN}:${PATH}"
export GH_TOKEN="fake-token"
export GITHUB_OUTPUT="${TMPDIR}/github-output"
: > "${GITHUB_OUTPUT}"
bash "${AUTH_SCRIPT}" "$@" 2>/dev/null
)
echo "${output}"
}

assert_authorized() {
local test_name="$1"
local output="$2"
if echo "${output}" | grep -q "authorized=true"; then
echo "PASS: ${test_name}"
else
echo "FAIL: ${test_name} — expected authorized=true, got: ${output}"
FAILURES=$((FAILURES + 1))
fi
}

assert_unauthorized() {
local test_name="$1"
local output="$2"
local expected_reason="${3:-unauthorized}"
if echo "${output}" | grep -q "authorized=false" && echo "${output}" | grep -q "reason=${expected_reason}"; then
echo "PASS: ${test_name}"
else
echo "FAIL: ${test_name} — expected authorized=false reason=${expected_reason}, got: ${output}"
FAILURES=$((FAILURES + 1))
fi
}

# --- Test cases ---

# Test: trusted bot (fullsend-ai-coder[bot]) is authorized
setup_mock_gh
export PR_AUTHOR_ASSOCIATION="CONTRIBUTOR"
export PR_AUTHOR_LOGIN="fullsend-ai-coder[bot]"
output=$(run_auth 1 "test-org/test-repo")
Comment thread
ralphbean marked this conversation as resolved.
Resolved
assert_authorized "trusted bot fullsend-ai-coder[bot] is authorized" "${output}"

# Test: MEMBER association is authorized
setup_mock_gh
export PR_AUTHOR_ASSOCIATION="MEMBER"
export PR_AUTHOR_LOGIN="some-human"
output=$(run_auth 1 "test-org/test-repo")
assert_authorized "MEMBER association is authorized" "${output}"

# Test: OWNER association is authorized
setup_mock_gh
export PR_AUTHOR_ASSOCIATION="OWNER"
export PR_AUTHOR_LOGIN="some-human"
output=$(run_auth 1 "test-org/test-repo")
assert_authorized "OWNER association is authorized" "${output}"

# Test: COLLABORATOR association is authorized
setup_mock_gh
export PR_AUTHOR_ASSOCIATION="COLLABORATOR"
export PR_AUTHOR_LOGIN="some-human"
output=$(run_auth 1 "test-org/test-repo")
assert_authorized "COLLABORATOR association is authorized" "${output}"

# Test: CONTRIBUTOR association without bot login is unauthorized
setup_mock_gh
export PR_AUTHOR_ASSOCIATION="CONTRIBUTOR"
export PR_AUTHOR_LOGIN="random-contributor"
output=$(run_auth 1 "test-org/test-repo")
assert_unauthorized "CONTRIBUTOR without bot login is unauthorized" "${output}"

# Test: unknown bot is not trusted
setup_mock_gh
export PR_AUTHOR_ASSOCIATION="CONTRIBUTOR"
export PR_AUTHOR_LOGIN="some-other-bot[bot]"
output=$(run_auth 1 "test-org/test-repo")
assert_unauthorized "unknown bot is not trusted" "${output}"

# --- Summary ---
echo ""
if [[ "${FAILURES}" -gt 0 ]]; then
echo "${FAILURES} test(s) failed"
exit 1
else
echo "All tests passed"
fi
20 changes: 19 additions & 1 deletion .github/scripts/check-e2e-authorization.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
# is a trusted bot, when the collaborator permission API confirms write+
# access, or when a fresh ok-to-test label was applied after the latest push.
#
# Security tradeoff: trusted bots bypass the ok-to-test label gate entirely.
# A compromise of the bot's GitHub App credentials would auto-qualify its PRs
# for credentialed e2e runs. Mitigated by gitleaks + pre-commit running
# upstream in post-code.sh before the bot opens a PR.
#
# Usage: check-e2e-authorization.sh PR_NUMBER OWNER/REPO
#
# Environment (optional, from workflow):
Expand All @@ -22,6 +27,7 @@ PR_NUMBER="${1:?PR number required}"
REPOSITORY="${2:?repository (owner/repo) required}"

TRUSTED_ASSOCIATIONS="OWNER MEMBER COLLABORATOR"
TRUSTED_BOTS="fullsend-ai-coder[bot]"
Comment thread
ralphbean marked this conversation as resolved.
Resolved
OK_TO_TEST_LABEL="ok-to-test"

write_error_output() {
Expand All @@ -46,6 +52,15 @@ is_trusted_author() {
esac
}

is_trusted_bot() {
Comment thread
ralphbean marked this conversation as resolved.
Resolved
Comment thread
ralphbean marked this conversation as resolved.
Resolved
local login="$1"
[[ -z "${login}" ]] && return 1
case " ${TRUSTED_BOTS} " in
*" ${login} "*) return 0 ;;
*) return 1 ;;
esac
}
Comment thread
ralphbean marked this conversation as resolved.
Resolved

# Fallback: check actor has write+ permission via the collaborator permission
# API, which correctly resolves org membership regardless of visibility.
has_write_permission() {
Expand Down Expand Up @@ -76,7 +91,10 @@ else
author_association="$(jq -r '.author_association' <<<"${pr_json}")"
fi

if is_trusted_author "${author_association}"; then
if is_trusted_bot "${PR_AUTHOR_LOGIN:-}"; then
Comment thread
ralphbean marked this conversation as resolved.
Resolved
authorized=true
reason="trusted_bot"
elif is_trusted_author "${author_association}"; then
authorized=true
reason="trusted_author"
elif has_write_permission "${PR_AUTHOR_LOGIN:-}" 2>/dev/null; then
Comment thread
ralphbean marked this conversation as resolved.
Resolved
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ script-test:
$(call run-timed,bash scripts/post-retro-test.sh)
$(call run-timed,bash scripts/post-scribe-test.sh)
$(call run-timed,bash scripts/validate-output-schema-test.sh)
$(call run-timed,bash .github/scripts/check-e2e-authorization-test.sh)

test: script-test
Loading