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:
1 parent
d0ca1f7f60
commit
7091a2d0d2
13 files changed
+1017
-146
No files matched your search
+61
-10
@@ -92,9 +92,12 @@ def build_app_manifest(public_base, app_name=DEFAULT_APP_NAME,
|
||||
class SetupStateStore:
|
||||
"""setup state 存储:一次性、1 小时过期,防回调被冒用。
|
||||
|
||||
表 github_app_setup_states:state(主键)、created_at。
|
||||
state 只由持本站 key 调 POST /github-app/setup/state 的
|
||||
操作方签发;回调验到即删,同一 state 第二次回调必然失败。
|
||||
表 github_app_setup_states:state(主键)、created_at、
|
||||
issued_by(签发人用户 id,P0 安全批新增)。新表建表即
|
||||
含该列;老库在 __init__ 里用 ALTER 补列(列已存在时
|
||||
报错直接忽略,老库不炸,见 except 分支)。
|
||||
state 只由全局管理员调 POST /github-app/setup/state
|
||||
签发;回调验到即删,同一 state 第二次回调必然失败。
|
||||
"""
|
||||
|
||||
def __init__(self, db_path):
|
||||
@@ -102,38 +105,86 @@ class SetupStateStore:
|
||||
self.conn = _db_connect(self.db_path)
|
||||
self.conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS github_app_setup_states("
|
||||
"state TEXT PRIMARY KEY, created_at REAL)"
|
||||
"state TEXT PRIMARY KEY, created_at REAL,"
|
||||
" issued_by TEXT)"
|
||||
)
|
||||
self.conn.commit()
|
||||
try:
|
||||
self.conn.execute(
|
||||
"ALTER TABLE github_app_setup_states"
|
||||
" ADD COLUMN issued_by TEXT")
|
||||
self.conn.commit()
|
||||
except Exception:
|
||||
try:
|
||||
self.conn.rollback()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def create_state(self):
|
||||
def create_state(self, issued_by=None):
|
||||
state = secrets.token_urlsafe(32)
|
||||
self.conn.execute(
|
||||
"INSERT INTO github_app_setup_states(state, created_at)"
|
||||
" VALUES(?, ?)",
|
||||
(state, time.time()),
|
||||
"INSERT INTO github_app_setup_states"
|
||||
"(state, created_at, issued_by)"
|
||||
" VALUES(?, ?, ?)",
|
||||
(state, time.time(), issued_by),
|
||||
)
|
||||
self.conn.commit()
|
||||
return state
|
||||
|
||||
def state_issuer(self, state):
|
||||
"""查 state 的签发人(回调审计用,不消耗 state)。"""
|
||||
if not state:
|
||||
return None
|
||||
try:
|
||||
row = self.conn.execute(
|
||||
"SELECT issued_by FROM github_app_setup_states"
|
||||
" WHERE state = ?",
|
||||
(state,),
|
||||
).fetchone()
|
||||
except Exception:
|
||||
return None
|
||||
if row is None:
|
||||
return None
|
||||
try:
|
||||
return row[0]
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def consume_state(self, state, now=None,
|
||||
ttl_seconds=SETUP_STATE_TTL_SECONDS):
|
||||
ttl_seconds=SETUP_STATE_TTL_SECONDS,
|
||||
allowed_issuers=None):
|
||||
"""校验并消耗 state:成功返回 True,失败返回 False。
|
||||
|
||||
不存在 -> False;过期 -> 删行并返回 False;成功 -> 删行
|
||||
并返回 True。``now`` 只为单测注入时间。
|
||||
P0 安全批后:allowed_issuers 非空时,签发人不在该
|
||||
集合里一律返回 False 且不消耗 state(fail-closed);
|
||||
签发人为空(升级前的旧 state)同样不在集合里,等
|
||||
于升级后旧 state 全部作废,需管理员重新签发。注意:
|
||||
回调本身是浏览器无认证重定向,服务器无法从回调请求
|
||||
里认出调用方就是签发人,所以这里核对的是“签发人仍
|
||||
在管理员清单里”,不是“回调者就是签发人”——后者
|
||||
在本流程里做不到,文档与 PR 描述里必须如实写清,
|
||||
不能谎称回调做了身份认证。
|
||||
"""
|
||||
if not state:
|
||||
return False
|
||||
if now is None:
|
||||
now = time.time()
|
||||
row = self.conn.execute(
|
||||
"SELECT created_at FROM github_app_setup_states"
|
||||
"SELECT created_at, issued_by FROM github_app_setup_states"
|
||||
" WHERE state = ?",
|
||||
(state,),
|
||||
).fetchone()
|
||||
if row is None:
|
||||
return False
|
||||
if allowed_issuers is not None:
|
||||
try:
|
||||
issuer = row[1]
|
||||
except Exception:
|
||||
issuer = None
|
||||
if issuer not in allowed_issuers:
|
||||
return False
|
||||
self.conn.execute(
|
||||
"DELETE FROM github_app_setup_states WHERE state = ?",
|
||||
(state,),
|
||||
|
||||
Reference in new issue
Block a user