P0 security batch: true global admin, quota gates on all spending paths, default quota for quota-less users, fix-review author trust + docker/owner attribution, GitHub App setup issuer binding, real cancel kill, wider shell env exclusion

- CODEX_GLOBAL_ADMINS replaces maintainer-derived global admin;
  admin endpoints (quotas, sweep, backup, push dispatch) fail-closed
  403 when unset, with audit on allow and deny.
- continue/followups/messages/retry check both caller and original
  creator quotas (USD + unpriced token cap), 402 on either overrun.
- Quota-less users now get the #86 default quota instead of unlimited;
  init_db backfills and OAuth create/bind/existing-login branches
  INSERT OR IGNORE a default row; explicit NULL row stays unlimited.
  user_quotas added to migration tables.
- /fix-review: untrusted authors rejected before dedup claim, no task;
  trusted tasks force executor=docker, created_by=project owner, owner
  quota gate, prompt is a fixed template plus a 2000-char excerpt.
- GitHub App setup state only issued by global admins, state rows bind
  issued_by (ALTER-compatible for old DBs), callback rejects issuers
  no longer in the admin list without consuming the state.
- Cancel: popen poll checks DB status every second, Docker path runs a
  cancel watchdog that kills by container name, write-back is CAS-guarded
  so late results never overwrite a new status.
- SHELL_ENV_EXCLUDE adds *_TOKEN, *_AUTH*, *_CREDENTIAL*, *API_KEY*;
  comment states the residual risk: same-UID /proc environ remains
  readable, true isolation needs UID separation or hidepid.
- Tests: new cloud/tests/test_p0_security.py (17 cases); old tests
  updated to the new semantics (admin env, default quota, trusted
  fix-review author) without weakening any gate.
This commit is contained in:
host committed 2026-10-27 15:35:23 +00:00
1 parent d0ca1f7f60
commit 7091a2d0d2
13 files changed
+1017 -146

No files matched your search

+11 -3
View File
@@ -72,6 +72,8 @@ class MultiuserGapsTestCase(unittest.TestCase):
self.db_path = os.path.join(self.tmp.name, "control.db")
self.auth = AuthStore(self.db_path)
self.owner_id, self.owner_key = self.auth.create_user("owner")
self._old_admins = os.environ.get("CODEX_GLOBAL_ADMINS")
os.environ["CODEX_GLOBAL_ADMINS"] = self.owner_id
self.viewer_id, self.viewer_key = self.auth.create_user("viewer")
self.outsider_id, self.outsider_key = self.auth.create_user("outsider")
self.server = create_server(self.db_path, "127.0.0.1", 0)
@@ -95,6 +97,10 @@ class MultiuserGapsTestCase(unittest.TestCase):
except Exception:
pass
self.tmp.cleanup()
if getattr(self, "_old_admins", None) is None:
os.environ.pop("CODEX_GLOBAL_ADMINS", None)
else:
os.environ["CODEX_GLOBAL_ADMINS"] = self._old_admins
# -- helpers -------------------------------------------------------
def _create_task(self, key=None, body_extra=None):
@@ -264,13 +270,15 @@ class MultiuserGapsTestCase(unittest.TestCase):
finally:
_setenv("CLOUD_QUOTA_UNPRICED_TOKEN_CAP", old)
def test_unpriced_tokens_unlimited_without_quota_row(self):
def test_unpriced_token_cap_blocks_without_quota_row(self):
# P0 安全批后:无配额行不再等于不限额,无价 cap 照样挡。
for i in range(7):
self._insert_unpriced_cost("donor-%d" % i, 100, 50)
old = _setenv("CLOUD_QUOTA_UNPRICED_TOKEN_CAP", "1000")
try:
s, task = self._create_task()
self.assertEqual(s, 201, task)
s, body = self._create_task()
self.assertEqual(s, 402, body)
self.assertEqual(body["error"], "quota_exceeded")
finally:
_setenv("CLOUD_QUOTA_UNPRICED_TOKEN_CAP", old)