mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 08:03:15 +00:00
Some checks failed
CI / Lint (push) Waiting to run
CI / Format check (push) Waiting to run
CI / Unit tests (push) Waiting to run
CI / Playwright E2E (push) Waiting to run
Build & publish app artifacts / Publish + deploy (dev) (push) Has been cancelled
Build & publish app artifacts / Publish + deploy (prod) (push) Has been cancelled
Infra CD / Infra CI (pre-deploy) (push) Has been cancelled
Infra CD / Deploy open-swe-dev (push) Has been cancelled
Infra CD / Deploy open-swe-prod (push) Has been cancelled
* feat: default Slack/dashboard/schedule PRs + commits to the app identity (#57) Slack/dashboard/schedule runs now author PRs and run git/gh operations as the GitHub App seahaven-openswe[bot] by default (matching GitHub-issue runs), so the self-review 422 is impossible by construction rather than guarded in the prompt. A profile flag author_prs_as_user restores per-user attribution. - open_pull_request._resolve_pr_author_token + auth.resolve_github_token: default to the installation token for these sources; per-user only when opted in. - authorship: commit identity -> seahaven-openswe[bot] (numeric noreply; accepted Vercel-resolution risk, documented inline). - self-trigger safety: INTERNAL_BOT_LOGINS + webapp/reviewer_reconcile/reply markers recognize seahaven-openswe[bot] (bot-authored events are now ours). Supersedes the prompt-only guard in #58. * fix: author commits as the app bot in the default path (SH-IDSPLIT-01) Security review found the commit identity was NOT actually unified to the bot: resolve_triggering_user_identity got a 403 from the installation token and fell back to configurable['github_login'], so commits were still authored as the triggering user (commit=user, push+PR=bot — a three-way split that missed the stated goal). Now gate the triggering-user identity resolution on the same default-bot decision as the token: slack/dashboard/schedule default to the app bot identity unless author_prs_as_user is set. * docs(security): record AUTHZ-SLACK-BOT-DEFAULT-001 as an accepted residual (#59) Single-user deployment; bounded by App-on-pilot + ALLOWED_GITHUB_REPOS lock. Revisit (add a per-user gate) before expanding users or the App installation.
74 lines
2.5 KiB
Python
74 lines
2.5 KiB
Python
"""GitHub organization membership checks for webhook gating."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
import httpx
|
|
|
|
from .github_app import get_github_app_installation_token
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Bot logins whose webhook events are our OWN actions — never re-process them
|
|
# (self-trigger guard). Sea Haven's App is `seahaven-openswe[bot]`; the upstream
|
|
# `open-swe[bot]` / `openswe-dev[bot]` are kept for historical/test events.
|
|
INTERNAL_BOT_LOGINS: frozenset[str] = frozenset(
|
|
{"seahaven-openswe[bot]", "open-swe[bot]", "openswe-dev[bot]"}
|
|
)
|
|
|
|
|
|
async def is_user_active_org_member(username: str, org: str) -> bool:
|
|
"""Return True if ``username`` is an *active* member of ``org``.
|
|
|
|
Uses the GitHub App installation token so that private organization
|
|
memberships are visible (the same approach as the reference
|
|
``tag-external-contributions.yml`` workflow). On any API error, returns
|
|
``False`` — fail-closed for security.
|
|
|
|
Requires the GitHub App to have the ``Organization -> Members: Read-only``
|
|
permission; the ``GET /orgs/{org}/memberships/{username}`` endpoint returns
|
|
403 (-> ``False``) without it. See INSTALLATION.md.
|
|
"""
|
|
if not username or not org:
|
|
return False
|
|
|
|
token = await get_github_app_installation_token()
|
|
if not token:
|
|
logger.warning(
|
|
"GitHub App token unavailable; cannot verify org membership for %s", username
|
|
)
|
|
return False
|
|
|
|
url = f"https://api.github.com/orgs/{org}/memberships/{username}"
|
|
try:
|
|
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
response = await client.get(
|
|
url,
|
|
headers={
|
|
"Authorization": f"Bearer {token}",
|
|
"Accept": "application/vnd.github+json",
|
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
},
|
|
)
|
|
except Exception:
|
|
logger.exception("Error calling GitHub org membership API for %s/%s", org, username)
|
|
return False
|
|
|
|
if response.status_code == 404:
|
|
return False
|
|
if response.status_code != 200:
|
|
logger.warning(
|
|
"Unexpected status %s checking %s membership for %s",
|
|
response.status_code,
|
|
org,
|
|
username,
|
|
)
|
|
return False
|
|
|
|
try:
|
|
state = response.json().get("state")
|
|
except ValueError:
|
|
logger.warning("Failed to parse org membership response for %s/%s", org, username)
|
|
return False
|
|
return state == "active"
|