Skip to content

Commit 3857b91

Browse files
Carreauclaude
andcommitted
Add a SUPERUSERS environment variable
Whoever operates a deployment is not necessarily an admin on every repository the bot is installed on, so the bot would refuse their commands. Let a deployment name a set of users who are trusted everywhere. SUPERUSERS is a comma separated list of GitHub usernames, tolerating a leading @ so a value can be pasted straight from a mention, and compared case insensitively. The check short-circuits `Session._get_permission`, which is the single point both the direct scope check and the cross-repository `has_permission` check go through, and it answers without calling GitHub. Unset by default, in which case nobody is treated specially. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mj75Ckx8LdU8sQCgfyyERG
1 parent 5e9ad03 commit 3857b91

4 files changed

Lines changed: 44 additions & 2 deletions

File tree

CONTRIBUTING.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,20 @@ PERSONAL_ACCOUNT_NAME="<account name>"
8080
PERSONAL_ACCOUNT_TOKEN="<github personal access token with repo access>"
8181
```
8282

83+
Optionally:
84+
85+
```
86+
SUPERUSERS="<comma separated github usernames>"
87+
```
88+
89+
Superusers are treated as administrators on every repository the bot is
90+
installed on, whatever their actual GitHub permissions are there. This is
91+
meant for whoever operates the deployment, so that the bot does not refuse
92+
their commands on repositories they do not have admin rights to. A leading
93+
`@` is accepted and the comparison is case insensitive, so
94+
`SUPERUSERS="@Carreau"` and `SUPERUSERS="carreau"` are equivalent. Leave it
95+
unset to give nobody special treatment.
96+
8397
### Code Styling
8498

8599
`MeeseeksDev` has adopted automatic code formatting so you shouldn't

meeseeksdev/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,17 @@ def load_config_from_env():
128128
config["personal_account_name"] = os.environ.get("PERSONAL_ACCOUNT_NAME")
129129
config["personal_account_token"] = os.environ.get("PERSONAL_ACCOUNT_TOKEN")
130130

131+
# Comma separated list of GitHub usernames trusted on every repository the
132+
# bot is installed on; typically whoever runs this deployment. A leading @
133+
# is tolerated so that the value can be pasted straight from a mention.
134+
config["superusers"] = [
135+
u.strip().lstrip("@")
136+
for u in os.environ.get("SUPERUSERS", "").split(",")
137+
if u.strip().lstrip("@")
138+
]
139+
if config["superusers"]:
140+
print("superusers:", ", ".join(config["superusers"]))
141+
131142
return Config(**config).validate()
132143

133144

meeseeksdev/meeseeksbox/core.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ class Config:
3535
webhook_secret = None
3636
personal_account_name = None
3737
personal_account_token = None
38+
# Users trusted on every repository the bot is installed on, regardless of
39+
# their GitHub permissions there. Empty by default, never None, so that
40+
# validate() does not treat "no superusers" as missing configuration.
41+
superusers: list = []
3842

3943
def __init__(self, **kwargs):
4044
self.__dict__.update(kwargs)
@@ -644,6 +648,7 @@ def __init__(self, commands, config):
644648
self.config.key,
645649
self.config.personal_account_token,
646650
self.config.personal_account_name,
651+
self.config.superusers,
647652
)
648653

649654
def sig_handler(self, sig, frame):

meeseeksdev/meeseeksbox/utils.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import re
77
import shlex
88
import subprocess
9-
from typing import Any, Dict, Optional, cast
9+
from typing import Any, Dict, Optional, Sequence, cast
1010

1111
import jwt
1212
import requests
@@ -96,6 +96,7 @@ def __init__(
9696
rsadata: Optional[str],
9797
personal_account_token: Optional[str],
9898
personal_account_name: Optional[str],
99+
superusers: Sequence[str] = (),
99100
):
100101
self.since = int(datetime.datetime.now().timestamp())
101102
self.duration = 60 * 10
@@ -104,6 +105,7 @@ def __init__(
104105
self.rsadata = rsadata
105106
self.personal_account_token = personal_account_token
106107
self.personal_account_name = personal_account_name
108+
self.superusers = frozenset(u.lower() for u in superusers)
107109
self.idmap: Dict[str, str] = {}
108110
self._org_idmap: Dict[str, str] = {}
109111
self._session_class = Session
@@ -120,6 +122,7 @@ def session(self, installation_id: str) -> "Session":
120122
installation_id,
121123
self.personal_account_token,
122124
self.personal_account_name,
125+
self.superusers,
123126
)
124127

125128
def get_session(self, org_repo):
@@ -234,8 +237,11 @@ def __init__(
234237
installation_id,
235238
personal_account_token,
236239
personal_account_name,
240+
superusers=(),
237241
):
238-
super().__init__(integration_id, rsadata, personal_account_token, personal_account_name)
242+
super().__init__(
243+
integration_id, rsadata, personal_account_token, personal_account_name, superusers
244+
)
239245
self.installation_id = installation_id
240246

241247
def token(self) -> str:
@@ -344,6 +350,12 @@ def prepare():
344350
return response # type:ignore[no-any-return]
345351

346352
def _get_permission(self, org: str, repo: str, username: str) -> Permission:
353+
# Superusers are the people running this deployment. They are trusted
354+
# everywhere the bot is installed, whatever GitHub says about their
355+
# access to any one repository, so short-circuit before asking.
356+
if username.lower() in self.superusers:
357+
print("superuser", username, "granted admin on", org, repo)
358+
return Permission.admin
347359
get_collaborators_query = API_COLLABORATORS_TEMPLATE.format(
348360
org=org, repo=repo, username=username
349361
)

0 commit comments

Comments
 (0)