Skip to content

add auth profiles to wrangler #14200

Open
emily-shen wants to merge 2 commits into
mainfrom
emily/profiles-2
Open

add auth profiles to wrangler #14200
emily-shen wants to merge 2 commits into
mainfrom
emily/profiles-2

Conversation

@emily-shen
Copy link
Copy Markdown
Contributor

@emily-shen emily-shen commented Jun 5, 2026

See changeset and discussion #14161 for design.

(i did also delete a bunch of unnecessary snapshots, since adding a new global flag caused snapshot mismatches in way too many places...)


  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s): TODO
    • Documentation not necessary because:

A picture of a cute animal (not mandatory, but encouraged)


Open in Devin Review

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 5, 2026

🦋 Changeset detected

Latest commit: 24ac3d7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
wrangler Minor
@cloudflare/vite-plugin Patch
@cloudflare/vitest-pool-workers Patch
@cloudflare/wrangler-bundler Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-project-automation github-project-automation Bot moved this to Untriaged in workers-sdk Jun 5, 2026
@workers-devprod workers-devprod requested review from a team and penalosa and removed request for a team June 5, 2026 14:21
@workers-devprod
Copy link
Copy Markdown
Contributor

workers-devprod commented Jun 5, 2026

Codeowners approval required for this PR:

  • @cloudflare/cloudchamber
  • @cloudflare/d1
  • @cloudflare/workers-kv
  • @cloudflare/wrangler
Show detailed file reviewers
  • .changeset/auth-profiles.md: [@cloudflare/wrangler]
  • packages/workers-auth/src/auth-config-file.ts: [@cloudflare/wrangler]
  • packages/workers-auth/src/flow.ts: [@cloudflare/wrangler]
  • packages/workers-auth/src/index.ts: [@cloudflare/wrangler]
  • packages/workers-auth/src/profiles.ts: [@cloudflare/wrangler]
  • packages/workers-auth/src/state.ts: [@cloudflare/wrangler]
  • packages/workers-auth/src/token-exchange.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/agent-memory.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/ai.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/cert.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/cloudchamber/create.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/cloudchamber/curl.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/cloudchamber/delete.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/cloudchamber/images.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/cloudchamber/list.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/cloudchamber/modify.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/containers/delete.test.ts: [@cloudflare/cloudchamber @cloudflare/wrangler]
  • packages/wrangler/src/tests/containers/images.test.ts: [@cloudflare/cloudchamber @cloudflare/wrangler]
  • packages/wrangler/src/tests/containers/info.test.ts: [@cloudflare/cloudchamber @cloudflare/wrangler]
  • packages/wrangler/src/tests/containers/instances.test.ts: [@cloudflare/cloudchamber @cloudflare/wrangler]
  • packages/wrangler/src/tests/containers/list.test.ts: [@cloudflare/cloudchamber @cloudflare/wrangler]
  • packages/wrangler/src/tests/containers/push.test.ts: [@cloudflare/cloudchamber @cloudflare/wrangler]
  • packages/wrangler/src/tests/containers/registries.test.ts: [@cloudflare/cloudchamber @cloudflare/wrangler]
  • packages/wrangler/src/tests/containers/ssh.test.ts: [@cloudflare/cloudchamber @cloudflare/wrangler]
  • packages/wrangler/src/tests/d1/d1.test.ts: [@cloudflare/d1 @cloudflare/wrangler]
  • packages/wrangler/src/tests/deployments.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/docs.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/experimental-commands-api.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/hyperdrive.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/index.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/kv/help.test.ts: [@cloudflare/workers-kv @cloudflare/wrangler]
  • packages/wrangler/src/tests/kv/key.test.ts: [@cloudflare/workers-kv @cloudflare/wrangler]
  • packages/wrangler/src/tests/kv/namespace.test.ts: [@cloudflare/workers-kv @cloudflare/wrangler]
  • packages/wrangler/src/tests/mtls-certificates.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/pages/deploy.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/pages/pages.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/profiles.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/queues/queues-subscription.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/queues/queues.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/r2/bucket.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/r2/bulk.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/r2/help.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/r2/local-uploads.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/r2/object.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/secrets-store.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/setup.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/tunnel/tunnel.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/vectorize/vectorize.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/versions/versions.help.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/vpc.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/worker-namespace.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/workflows.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/api/dev.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/core/register-yargs-command.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/core/types.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/dev.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/experimental-flags.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/index.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/pages/dev.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/user/commands.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/user/profiles.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/user/user.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/wrangler-banner.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/yargs-types.ts: [@cloudflare/wrangler]

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented Jun 5, 2026

I'm Bonk, and I've done a quick review of your PR.

This PR adds named auth profiles to wrangler (per-directory bindings, a global --profile flag, and wrangler auth create/delete/activate/deactivate/list).

Two issues, ranked by severity:

1. (medium) wrangler auth token ignores the active profile

getOAuthTokenFromLocalState in packages/workers-auth/src/flow.ts (the function starting at line 327) always reads the default profile's auth state:

let stored = readStoredAuthState({ warningLogger: ctx.logger });   // no profile
...
stored = readStoredAuthState({ warningLogger: ctx.logger });        // re-read, also no profile

Every other read path you touched (getAPIToken, getScopes, loginOrRefreshIfRequired, logout) threads the resolved profile through — but this one was left unchanged.

Its only consumer is wrangler auth token (packages/wrangler/src/user/commands.ts:230), which does not hide the global --profile flag and does not call getProfile(). As a result, wrangler auth token --profile foo (or running it from a directory bound to foo) silently returns the default profile's token instead of foo's — a correctness bug for scripting/CI use cases. There's no test covering auth token with a non-default profile.

Suggested fix: give getOAuthTokenFromLocalState an optional profile?: string parameter, pass it to both readStoredAuthState calls and to the internal refreshToken() call, update the OAuthFlowAPI interface (line 108) and the wrapper in packages/wrangler/src/user/user.ts:265-269, and have commands.ts pass getProfile() (mapping default -> undefined as the other call sites do).

2. (low) listProfilePaths surfaces the default file as a reserved-name profile on non-prod environments

In packages/workers-auth/src/profiles.ts, listProfilePaths() lists every *.toml in the config dir and strips the extension. But getAuthConfigFilePath names the default profile's file <environment>.toml (e.g. staging.toml) on non-production Cloudflare API environments. So on a staging environment the default profile shows up in wrangler auth list as a profile literally named staging — which is one of RESERVED_PROFILE_NAMES and can never be created by a user. Consider mapping the environment-suffixed default file back to default and/or filtering reserved names out of the listing.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented Jun 5, 2026

Posted my review on PR #14200. Summary of what I flagged:

  1. (medium) wrangler auth token ignores the active profilegetOAuthTokenFromLocalState in flow.ts always reads the default profile, unlike every other read path in the PR (getAPIToken, getScopes, logout, etc.). Since auth token doesn't hide the global --profile flag, wrangler auth token --profile foo silently returns the default profile's token. Untested.

  2. (low) listProfilePaths surfaces the default file as staging on non-prod environments — the default profile's file is <environment>.toml, so on staging it lists a profile named staging, which is itself a reserved name that users can't create.

Note: the inline batch review failed with a 422 because the relevant flow.ts function lines fall outside this PR's diff hunks (the function itself was unchanged — the PR added profile support to its siblings but not to it), so I fell back to a single top-level PR comment as instructed.

github run

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 5, 2026

✅ All changesets look good

devin-ai-integration[bot]

This comment was marked as resolved.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 5, 2026

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@14200

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/@cloudflare/deploy-helpers@14200

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@14200

miniflare

npm i https://pkg.pr.new/miniflare@14200

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@14200

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@14200

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@14200

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@14200

@cloudflare/workers-auth

npm i https://pkg.pr.new/@cloudflare/workers-auth@14200

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@14200

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@14200

wrangler

npm i https://pkg.pr.new/wrangler@14200

@cloudflare/wrangler-bundler

npm i https://pkg.pr.new/@cloudflare/wrangler-bundler@14200

commit: 24ac3d7

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

View 9 additional findings in Devin Review.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Deprecated API token warning shows wrong file path for non-default profiles

In state.ts line 106, getAuthConfigFilePath() is called without the profile argument, even though profile is available in scope (destructured at line 77 from the options object). When a non-default profile's config file contains a deprecated api_token field, the warning message will display the default profile's file path instead of the actual profile's path, confusing the user about which file to fix.

(Refers to line 106)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +218 to +219
if (args.profile) {
return args.profile;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Path traversal via unvalidated --profile global flag

The --profile global CLI flag passes user input directly through resolveProfile() (packages/workers-auth/src/profiles.ts:218-219) without any validation. This raw string flows into the command context at packages/wrangler/src/core/register-yargs-command.ts:134-146, where it's stored via run({ ...experimentalFlags, profile }). Subsequently, when getProfile() returns this string, it reaches getAuthConfigFilePath() (packages/workers-auth/src/auth-config-file.ts:48) which interpolates it directly into a file path: fileName = \${resolved}.toml`. A user running wrangler deploy --profile "../../tmp/evil"would cause wrangler to read auth tokens from (and potentially write tokens to)~/.wrangler/config/../../tmp/evil.toml, escaping the intended config directory. During token refresh, writeAuthConfigFilealso callsmkdirSyncwithrecursive: true` along the traversed path, which could create directories in unintended locations.

Affected code path
  1. resolveProfile({ profile: args.profile }) returns raw string
  2. Stored in AsyncLocalStorage context
  3. getProfile() returns it
  4. getAuthConfigFilePath(profile)path.join(base, "config", ${profile}.toml)
  5. Reads/writes to arbitrary path
Prompt for agents
The resolveProfile function at packages/workers-auth/src/profiles.ts:214-236 returns the raw args.profile string without validation. This flows into getAuthConfigFilePath() which interpolates it into a file path, enabling path traversal.

Fix approach: Add validation in resolveProfile when the profile comes from args.profile. Call validateProfileName(args.profile) before returning it. This ensures the --profile flag (which is the untrusted input boundary) is validated the same way as the auth create/delete/activate commands.

Alternatively (defense-in-depth), add validation directly in getAuthConfigFilePath() at packages/workers-auth/src/auth-config-file.ts:40-54 to reject any profile value that doesn't match the safe pattern /^[a-zA-Z0-9_-]+$/ before using it in the file path. This protects against all callers, including potential future code paths that might pass unvalidated strings.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

2 participants