From 3e2b604adcdfeb011bbdac928f47136aa1352456 Mon Sep 17 00:00:00 2001 From: rogu3bear Date: Tue, 30 Jun 2026 18:12:20 -0500 Subject: [PATCH] Fix Email Sending permission scope Live Cloudflare permission-group discovery reports Email Sending Read and Email Sending Write as account-scoped permissions. The cfctl catalog incorrectly modeled them as zone-scoped after adding sender_domain apply support, which can produce misleading bootstrap/token guidance for the exact maildesk sender-domain enable path. Update the permission catalog to account scope and add a static contract assertion for both Email Sending permissions. This keeps cfctl bootstrap guidance aligned with Cloudflare's permission group model before operators mint or rotate a deploy token for sender-domain authentication. Proof: TDD red via verify_static_contract expecting account scope, then green after the catalog change. Also ran verify_static_contract, verify_permission_catalog.py --cfctl ./cfctl, and verify_permission_catalog.py against a full live permission-groups artifact. --- catalog/permissions.json | 4 ++-- scripts/verify_static_contract.sh | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/catalog/permissions.json b/catalog/permissions.json index 82bc5ee..812ed68 100644 --- a/catalog/permissions.json +++ b/catalog/permissions.json @@ -285,13 +285,13 @@ }, { "name": "Email Sending Read", - "scope": "zone", + "scope": "account", "surfaces": ["sender_domain"], "profiles": ["read", "deploy", "full-operator"] }, { "name": "Email Sending Write", - "scope": "zone", + "scope": "account", "surfaces": ["sender_domain"], "profiles": ["deploy", "full-operator"] }, diff --git a/scripts/verify_static_contract.sh b/scripts/verify_static_contract.sh index 994e4c9..84ac6d3 100755 --- a/scripts/verify_static_contract.sh +++ b/scripts/verify_static_contract.sh @@ -303,7 +303,8 @@ assert_jq_file "permission profile minimality policy" ' and (.profiles["security-audit"].allowed_surfaces | index("zone.setting")) != null and (.permissions[] | select(.name == "Zone Settings Read" and .scope == "zone" and (.surfaces | index("zone.setting")) != null)) and (.permissions[] | select(.name == "Zone Settings Write" and .scope == "zone" and (.profiles | index("hostname")) != null)) - and (.permissions[] | select(.name == "Email Sending Write" and .scope == "zone" and (.surfaces | index("sender_domain")) != null and (.profiles | index("deploy")) != null)) + and (.permissions[] | select(.name == "Email Sending Read" and .scope == "account" and (.surfaces | index("sender_domain")) != null and (.profiles | index("deploy")) != null)) + and (.permissions[] | select(.name == "Email Sending Write" and .scope == "account" and (.surfaces | index("sender_domain")) != null and (.profiles | index("deploy")) != null)) and (.profiles.deploy.allowed_surfaces | index("audit.log")) != null and (.profiles.deploy.allowed_surfaces | index("wrangler")) != null and .profiles["full-operator"].allowed_surfaces == ["*"]