security: separate admin token, verify credit-pack orders, revoke access on lapse
Member disclosure, three findings (fixes 1-3 of 5): 1. WEBHOOK_TOKEN was both the Open Collective webhook URL token and the X-Admin-Token for every /admin/* route, so anything that learned the webhook URL (OC admins, OC logs, this app's access logs) gained portal admin. The URL token is forced by OC's generic webhooks, which cannot set headers — the reuse is not. Introduce ADMIN_TOKEN, header-only. ADMIN_TOKEN_LEGACY=1 (default) still accepts WEBHOOK_TOKEN and logs a warning, so the admin panel and the scheduler keep working; set ADMIN_TOKEN everywhere, then flip it to 0 and rotate. sweep.sh and the digest script now send ADMIN_TOKEN. 2. The credit-pack path fell back to the webhook payload's own amount when OC could not resolve the order, so a forged POST naming its own totalAmount minted credits for any slug. The payload is now a hint only: an unresolvable order is refused, logged loudly, and emailed to the operator for manual reconciliation. Transient OC failures hold instead of granting blind. 3. Lapsing did not revoke access: both deactivation sites (lapse webhook, nightly sweep) discarded the key token deactivate_member() returns. Member keys carry no max_budget of their own, so they inherit the team's — zero the team budget on deactivation, which blocks every key under it. Key deletion is deliberately NOT used: provisioning reuses the stored sk- token on reactivation, and get_or_create_team does not update an existing team's budget, so the two renewal paths now re-sync the budget explicitly. Failures are loud (log + alert email); deactivation still completes.
This commit is contained in:
1 parent
e55d8a3f1e
commit
5720a63958
3 files changed
+110
-17
No files matched your search
@@ -5,19 +5,22 @@
|
||||
# the portal against the live Open Collective member list: provisions missing
|
||||
# members, deactivates lapsed members, and syncs Loomio.
|
||||
#
|
||||
# The WEBHOOK_TOKEN env var is shared with the main app (scheduler runs in the
|
||||
# same launch environment), so we use it to authenticate the call.
|
||||
# The ADMIN_TOKEN env var is shared with the main app (scheduler runs in the
|
||||
# same launch environment), so we use it to authenticate the call. Admin auth is
|
||||
# a header, never a URL token (security fix 2026-10-27). WEBHOOK_TOKEN is only a
|
||||
# fallback until this caller is fully migrated.
|
||||
#
|
||||
# Uses Python (guaranteed present in the base image) rather than curl.
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z "$WEBHOOK_TOKEN" ]; then
|
||||
echo "sweep: WEBHOOK_TOKEN not set; aborting" >&2
|
||||
ADMIN_TOKEN="${ADMIN_TOKEN:-$WEBHOOK_TOKEN}"
|
||||
if [ -z "$ADMIN_TOKEN" ]; then
|
||||
echo "sweep: neither ADMIN_TOKEN nor WEBHOOK_TOKEN is set; aborting" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python3 - "$WEBHOOK_TOKEN" <<'PY'
|
||||
python3 - "$ADMIN_TOKEN" <<'PY'
|
||||
import sys, urllib.request, urllib.error
|
||||
|
||||
token = sys.argv[1]
|
||||
|
||||
Reference in new issue
Block a user