Repository navigation
feat(auth): request a product-area scope bundle at login, step up only for sensitive scopes - #317
Merged
jpage-godaddy merged 8 commits intoOct 12, 2026
Conversation
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Several destructive capabilities remain login defaults, and Shopping grants checkout authority during read-only browsing.
2 open findings
What changed in this PR
Expands login-time OAuth scopes while reserving sensitive permissions for step-up authorization.
Changes:
- Adds a centralized
STEP_UP_ONLYscope policy. - Expands default login scopes and registry flags.
- Adds an invariant test for scope classification.
| File | Description |
|---|---|
rust/src/scopes.rs |
Defines step-up policy, updates registry defaults, and adds validation. |
rust/src/environments/mod.rs |
Expands the default OAuth login bundle. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…y for sensitive scopes Login now requests every permission scope the CLI uses except a short step-up-only list, so a first-time "buy a domain, buy hosting, deploy a site" session needs three consent screens (login plus one per purchase) instead of about a dozen. Each scope step-up is a browser round trip through the consent screen, which now groups scopes by product area with a read-only / read-and-manage label, so the wider login grant stays readable. Scopes that spend money (domain registration, shopping), can take a site offline (nameserver update), destroy resources (app delete), or open raw data access (database tunnel) stay out of the bundle and prompt when the command that needs them runs. `scopes::STEP_UP_ONLY` records that list and a new test enforces that the login defaults are exactly every declared scope minus it, plus offline_access. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
nverbos-godaddy
force-pushed
the
feat/auth-login-scope-bundle
branch
from
October 12, 2026 18:30
08369a6 to
3808d65
Compare
…or checkout `shopping.catalog:read` only reads the public product catalog, so it joins the login bundle. The catalog commands now declare just that scope instead of the full shopping trio, so browsing never triggers a step-up; checkout and order commands keep `shopping.checkout:execute` + `shopping.order:read` as a pair so the first purchase prompts once and the follow-up order read does not prompt again. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- `shopping order get` declares only `shopping.order:read`; the checkout commands keep the checkout + order pair, so reading an order never asks for spending authority while a post-purchase `order get` stays covered. - Shopping `make_client` resolves the credential from the command's own `.with_scopes` declaration via `ctx.credential()`, matching `domain::make_client`, instead of taking a second copy of the scopes. - The wired-to-a-command test now covers every permission scope, not only non-defaults, so a scope cannot outlive its last command unnoticed. - The defaults invariant test also compares lengths so a duplicated default entry fails instead of being hidden by the set comparison. - "Adding a scope" docs gain the default-versus-step-up decision; the step-up-only constants are marked in their doc comments and the block-level rule replaces per-constant "requested at login" notes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
nverbos-godaddy
marked this pull request as ready for review
October 12, 2026 19:08
nverbos-godaddy
requested review from
axburgess-godaddy,
jpage-godaddy,
jpearlman-godaddy,
mguerrero3-godaddy,
qcai-godaddy,
rts1-godaddy and
sswaminathan-godaddy
as code owners
October 12, 2026 19:08
Contributor
Author
…lags Per review: SCOPE_REGISTRY is now the single source of truth. Each entry's `default` flag decides whether a scope is requested at login or only when a declaring command runs; `scopes::DEFAULT_OAUTH_SCOPES` is derived from those flags at first use, and the hand-maintained `environments::DEFAULT_OAUTH_SCOPES` and `STEP_UP_ONLY` lists are gone. The step-up policy is recorded in the registry docs and pinned by a test that names the six sensitive scopes, so flipping a flag is an explicit change rather than a side effect. Also drops a code comment aimed at the coding session rather than readers. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
nverbos-godaddy
force-pushed
the
feat/auth-login-scope-bundle
branch
from
October 12, 2026 19:42
8379c31 to
3da29f0
Compare
jpage-godaddy
approved these changes
Oct 12, 2026
jpage-godaddy
left a comment
Collaborator
There was a problem hiding this comment.
Beautiful, thanks.
… scopes Per review: with the login bundle in place, the non-default scopes are exactly the sensitive ones, so help text that recommended `auth login --scope` for every non-default scope would defeat the step-up policy. `gddy auth scope` and the gddy skill now describe non-default as "prompts on first use, on purpose" and tell agents to plan for those commands instead. The platform guide and email help no longer tell users to add `--scope` for scopes that are now login defaults. The registry-derivation test asserts independent properties instead of repeating the derivation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
nverbos-godaddy
requested review from
sgimpel-godaddy,
sgopalan-godaddy,
smukherjee-godaddy,
srao-godaddy and
wcole1-godaddy
as code owners
October 12, 2026 19:55
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…-bundle # Conflicts: # rust/src/environments/mod.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
gddy auth loginnow requests a bundle of every permission scope the CLI uses except a short step-up-only list, instead of justapps.app-registry:read,domains.domain:read,offline_accessand the OIDCopenid/profilepair.domains.domain:create,domains.nameserver:update,hosting.application:delete,hosting.database.tunnel:execute, andshopping.checkout:execute+shopping.order:read. Editing scopes that also permit deleting a sub-resource (dns delete,hosting secret delete,hosting domain detach,platform app archive) stay defaults on purpose: the authorization server offers no finer split, and prompting fordomains.dns:updatewould put a consent screen back into every site setup.shopping.catalog:readonly reads the public product catalog, so it is a login default. The catalog commands now declare just that scope (SHOPPING_CATALOG_SCOPES) instead of the full shopping trio, so browsing never steps up. The checkout commands declareSHOPPING_PURCHASE_SCOPES(checkout:execute+order:read) so the first purchase prompts once and a laterorder getis already covered;shopping order getitself declares onlyshopping.order:read, so reading an order never asks for spending authority. The shopping client helper now resolves its credential from the command's own.with_scopesdeclaration (ctx.credential(), likedomain::make_client) rather than taking a second copy of the scopes.SCOPE_REGISTRYis the single source of truth: each entry'sdefaultflag decides login versus step-up, andscopes::DEFAULT_OAUTH_SCOPESis derived from those flags (the hand-maintainedenvironments::DEFAULT_OAUTH_SCOPESlist is removed). The step-up policy is written on the registry and pinned by a test naming the six sensitive scopes, so flipping a flag is an explicit change. The wired-to-a-command test now covers every permission scope, not just non-defaults, so a default scope cannot outlive its last command unnoticed. The module's "Adding a scope" steps include the default-versus-step-up decision.gddy auth scope --defaults-only/--non-defaultreport the split straight from the registry..with_scopesdeclarations change. Commands keep declaring what they need so a token obtained before this change still steps up correctly.Why
A first-time user driving the CLI from an agent through "search for a domain, buy it, buy hosting, deploy a site, connect the domain" hit roughly a dozen OAuth consent screens, one per command that needed a scope the token did not have. Every scope step-up is a full browser round trip through the consent screen, so the fix is to authorize less often.
With this bundle the same journey is three consent screens: login, register the domain, buy hosting. Everything from app create through deploy, secrets, logs, and DNS is covered by the login grant. The consent screen groups scopes by product area with a "Read only" / "Read & manage" label, so the longer login grant renders as a handful of collapsed rows rather than a wall of bullets.
Fewer, more distinctive prompts also mean the prompts that remain get read, which is the point of having them.
What the login screen shows
openid,profiledomains.domain:read,domains.dns:updateemail.mailbox:read,email.mailbox:createapps.app-registry:read,apps.app-registry:writesecurity.website-security-scan:executeshopping.catalog:readoffline_accessWhat still steps up after this PR
domains.domain:createdomain purchaseshopping.checkout:executeshopping checkout create/get/update/completeshopping.order:readshopping order get; also granted with checkoutorder getnever prompts; alone it prompts only for the readdomains.nameserver:updatedomain nameserver sethosting.application:deletehosting app deletehosting.database.tunnel:executedb tunnelEach prompt happens once per environment, not once per use: the granted scope joins the single stored token and stays for the life of the rotating refresh token.
gddy auth scope --non-defaultprints this list from the running binary.Out of scope / related
openidandprofilecome from feat(auth): upgrade cli-engine to 0.10.1; log in with an ephemeral callback port and OIDC scopes #314 (merged), whose registry entries carrydefault: true, so they are part of the derived login request here; the consent screen gains the Sign-in & profile row.ScopeHierarchy(write implies read) and an agent-facing login preset are follow-ups; with the full bundle they change nothing for the current commands.Known CI failure, not from this PR
cargo auditbegan failing on 2026-10-12 when RUSTSEC-2026-0338 through -0342 (quinn-proto0.11.16, a transitive dependency) entered the advisory database; the identical lockfile passed onmainearlier the same day andmain's next run hits it too. This PR deliberately does not touchCargo.lock; the bump will be picked up frommainonce it lands there.Behavior for existing users
Nothing changes until the next authorization. A stored token keeps refreshing with its narrow scope set; the first step-up it hits (or a
gddy auth logout+login) moves it onto the bundle. No migration.Requirements
scopes::ALLplusoffline_access, on every environment. With the wider login request, a single unregistered scope would failgddy auth loginfor that environment rather than one command. All 27 permission scopes were already registered on the CLI client (each was already requestable via step-up) and the OTE and prod registrations were confirmed, so no client registration change is needed.Test plan
cargo fmt --checkcargo clippy --workspace -- -D warningscargo test --workspace(1083 passed ingodaddy-cli, including the new registry-derivation and step-up-set tests)./rust/scripts/check-module-size.shgddy auth logout && gddy auth loginproduced one consent screen and a token with the 22 bundle scopes (auth status).shopping catalog search,domain available,domain list,dns list,hosting app listandemail listall ran without a prompt (some returned API 404/502s from the test account's data, none were auth failures).shopping order gettriggered exactly one step-up requesting the 24-scope union (run before the order-read narrowing; it now requests onlyorder:readon top of the bundle); after approval the token held 24 scopes and a secondshopping order getdid not prompt.🤖 Generated with Claude Code