From 8fc4e27241e36e69ac895fbd4e042c0c84fd30b2 Mon Sep 17 00:00:00 2001 From: host Date: Tue, 27 Oct 2026 11:19:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20GitHub=20App=20manifest=20=E5=85=A8?= =?UTF-8?q?=E9=87=8F=E8=8E=B7=E5=8F=96=EF=BC=9A=E4=BB=93=E5=BA=93/?= =?UTF-8?q?=E7=BB=84=E7=BB=87/=E4=BC=81=E4=B8=9A/=E8=B4=A6=E5=8F=B7?= =?UTF-8?q?=E5=9B=9B=E7=B1=BB=E6=9C=80=E9=AB=98=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 接上轮仓库最大权限:用户明确要求无论什么身份,取该身份 授权范围内的所有权限。manifest 现按四类全拉满(74 项权限、 39 个事件):仓库补 codespaces_lifecycle_admin、repository_ creation、metadata 只读;组织 members 升 write 并补 organization_administration 等 15 项;企业补 8 项;账号补 blocking、email_addresses、gists、密钥类等 22 项。 身份边界写进注释:GitHub 只授予安装身份能给的那部分,个人 装不上组织/企业、企业装不上仓库/组织;换 token 不传子集, token 本就是安装全量;企业安装另需 App 公开/内部 (GITHUB_APP_PUBLIC=1)。诚实边界:企业类键名未用真实 GitHub 建 App 实测,新建时表单会校验,某键被拒删键再建, 线上旧 App 不受影响。 --- cloud/github_app.py | 109 +++++++++++++++++++++++++++++---- cloud/tests/test_github_app.py | 12 ++++ 2 files changed, 108 insertions(+), 13 deletions(-) diff --git a/cloud/github_app.py b/cloud/github_app.py index 04b946f..92a4b12 100644 --- a/cloud/github_app.py +++ b/cloud/github_app.py @@ -45,18 +45,30 @@ SETUP_STATE_TTL_SECONDS = 3600 DEFAULT_APP_NAME = "codex-app-server" DEFAULT_APP_URL = "https://github.com/openqj/codex-app-server" -# App 权限与事件:主机最大权限(用户 2026-10-27 明确要求扩到 -# 最高、所有能力)。这是 manifest 的默认值,只影响以后新建的 -# App;已存在的线上 App(id 5476837)不会因改代码自动变权限, -# 必须在 GitHub App 设置里改权限并由安装方接受新权限后才生效。 -# 风险明示:administration=write 含改仓库设置/删库/转移, -# secrets=write 含管理仓库 secret,配 All repositories 安装会把 -# 影响面扩到该账号全部仓库——只装在自己信任的账号上。 -# organization/enterprise/billing 类权限不在此列:它们不属于 -# 主机跑任务的能力,且需组织/企业管理员另行审批,不应由一个 -# 代码托管 App 默认索取。 +# App 权限与事件:全量获取(用户 2026-10-27 明确要求:无论 +# 什么身份,取该身份授权范围内的所有权限)。 +# 原则:manifest 里按仓库 / 组织 / 企业 / 账号四类全部拉到最高 +# 档,安装时 GitHub 只授予该身份能给的那部分——个人账号装不上 +# 组织/企业权限,组织装不上企业权限,企业装不上仓库/组织权限, +# 这是 GitHub 的身份边界,不是代码能绕过的。换 token 时 +# exchange_installation_token 不传 permissions 子集(payload +# 为空),所以 token 本来就是安装权限的全量,不会被代码降级。 +# 这是 manifest 的默认值,只影响以后新建的 App;已存在的线上 +# App(id 5476837)不会因改代码自动变权限,必须在 GitHub App +# 设置里改权限并由安装方接受新权限后才生效。企业安装另需 +# App 为公开或内部(GITHUB_APP_PUBLIC=1),私有默认装不上企业。 +# 风险明示:administration / organization_administration / +# enterprise 类 write 含删库、转移、改组织/企业设置、管账单, +# account 类 write 含管用户密钥、Gist、关注、星标,配 All +# repositories 安装时影响面为该身份的全部授权范围——只装在 +# 自己信任的身份上。 +# 诚实边界:仓库/组织/账号键名有官方文档与 manifest 示例佐证; +# 企业类键名(enterprise_*)未在本机用真实 GitHub 创建 App +# 实测过——新建 App 时 GitHub 建 App 表单会逐项校验,若某键 +# 被拒,删该键再建即可,线上旧 App 不受影响。single_file 未 +# 列入:它必须配 single_file_paths 才能生效,空列无意义。 APP_PERMISSIONS = { - # 仓库:除 metadata(GitHub 自动只读)外全部拉到最高档。 + # 仓库:全部拉到最高档,metadata 只读是 GitHub 自动档。 "actions": "write", "actions_variables": "write", "administration": "write", @@ -64,6 +76,7 @@ APP_PERMISSIONS = { "attestations": "write", "checks": "write", "codespaces": "write", + "codespaces_lifecycle_admin": "write", "codespaces_metadata": "read", "codespaces_secrets": "write", "contents": "write", @@ -73,10 +86,12 @@ APP_PERMISSIONS = { "environments": "write", "issues": "write", "merge_queues": "write", + "metadata": "read", "packages": "write", "pages": "write", "pull_requests": "write", "repository_advisories": "write", + "repository_creation": "write", "repository_custom_properties": "write", "repository_hooks": "write", "secret_scanning_alerts": "write", @@ -85,8 +100,52 @@ APP_PERMISSIONS = { "statuses": "write", "vulnerability_alerts": "write", "workflows": "write", - # 组织:只读成员,用于列协作者;不索取组织管理权。 - "members": "read", + # 组织:全部拉到最高档(仅组织安装时生效)。 + "members": "write", + "organization_administration": "write", + "organization_announcement_banners": "write", + "organization_copilot_seat_management": "write", + "organization_custom_properties": "write", + "organization_custom_roles": "write", + "organization_dependabot_secrets": "write", + "organization_events": "read", + "organization_hooks": "write", + "organization_packages": "write", + "organization_plan": "read", + "organization_projects": "write", + "organization_secrets": "write", + "organization_self_hosted_runners": "write", + "organization_user_blocking": "write", + # 企业:全部拉到最高档(仅企业安装且 App 公开/内部时生效)。 + "enterprise_administration": "write", + "enterprise_billing": "write", + "enterprise_copilot_seat_management": "write", + "enterprise_custom_properties_for_organizations": "write", + "enterprise_organization_installation_repositories": "write", + "enterprise_organization_installations": "write", + "enterprise_policies": "write", + "enterprise_rules": "write", + # 账号:全部拉到最高档(仅用户另外授权 App 代表自己时生效)。 + "blocking": "write", + "codespaces_user_secrets": "write", + "copilot_editor_context": "read", + "copilot_messages": "read", + "copilot_requests": "write", + "email_addresses": "write", + "followers": "write", + "gists": "write", + "git_signing_ssh_public_keys": "write", + "git_ssh_keys": "write", + "gpg_keys": "write", + "interaction_limits": "write", + "knowledge_bases": "write", + "plan": "read", + "private_repository_invitations": "read", + "profile": "write", + "starring": "write", + "user_events": "read", + "user_models": "read", + "watching": "write", } APP_EVENTS = [ "push", @@ -100,10 +159,34 @@ APP_EVENTS = [ "check_run", "check_suite", "workflow_run", + "workflow_job", "release", "deployment", "deployment_status", "discussion", + "discussion_comment", + "commit_comment", + "code_scanning_alert", + "secret_scanning_alert", + "label", + "milestone", + "member", + "membership", + "organization", + "team", + "team_add", + "repository", + "fork", + "watch", + "meta", + "status", + "project", + "project_card", + "project_column", + "gollum", + "page_build", + "public", + "repository_dispatch", ] diff --git a/cloud/tests/test_github_app.py b/cloud/tests/test_github_app.py index 81f4bfa..772c30d 100644 --- a/cloud/tests/test_github_app.py +++ b/cloud/tests/test_github_app.py @@ -91,11 +91,23 @@ class ManifestTestCase(unittest.TestCase): self.assertEqual(m["default_permissions"]["administration"], "write") self.assertEqual(m["default_permissions"]["actions"], "write") self.assertEqual(m["default_permissions"]["checks"], "write") + # 全量:组织 / 企业 / 账号三类也必须在 manifest 里,不只仓库。 + self.assertEqual(m["default_permissions"]["members"], "write") + self.assertEqual( + m["default_permissions"]["organization_administration"], + "write") + self.assertEqual( + m["default_permissions"]["enterprise_organization_installations"], + "write") + self.assertEqual(m["default_permissions"]["email_addresses"], "write") + self.assertEqual(m["default_permissions"]["gists"], "write") self.assertIn("issue_comment", m["default_events"]) self.assertIn("pull_request", m["default_events"]) self.assertIn("push", m["default_events"]) self.assertIn("check_run", m["default_events"]) self.assertIn("workflow_run", m["default_events"]) + self.assertIn("organization", m["default_events"]) + self.assertIn("member", m["default_events"]) self.assertFalse(m["public"]) def test_manifest_requires_base(self):