Skip to content

Reject an empty host at the login host prompt - #7195

Open
maksim-zuev-db wants to merge 9 commits into
mainfrom
login-reject-empty-host
Open

maksim-zuev-db wants to merge 9 commits into
mainfrom
login-reject-empty-host

Conversation

@maksim-zuev-db

@maksim-zuev-db maksim-zuev-db commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Changes

databricks auth login and databricks auth token no longer accept an empty host.

  • The host prompt rejects an empty or spaces-only input and asks again.
  • A host that is empty after stripping the trailing slash and the query params (e.g. /, ?o=123)
    fails with invalid host "<input>". The check is in normalizeLoginHost, used by setHost and the
    --skip-workspace block, so it covers --host, the positional argument, profile hosts and the prompt.
    It replaces the device code specific checks from Prompt for the host instead of failing when device code login has none #7179 (errDeviceCodeNeedsHost).
  • Prompts with Validate show a blank glyph until the first edit or Enter, then ✗ or ✔.

Behavior changes:

  • An empty host after "Enter a host" in the login profile picker no longer starts discovery login.
  • auth login --skip-workspace --host '?a=123' fails instead of starting discovery login (already the case on main).

Why

Follow-up to a review comment on #7179. An empty host made login and token resolve the host from
DATABRICKS_HOST, save a profile without a host key, or fail later with host must start with 'https://': .

Tests

  • TestPromptForHostRejectsEmptyHost, TestSetHost, TestLoginDeviceCodeHostEmptyAfterNormalization,
    TestLoginSkipWorkspaceHostEmptyAfterNormalization, and prompt baselines for the glyph states.

This pull request and its description were written by Isaac.

maksim-zuev-db and others added 3 commits October 27, 2026 13:49
Pressing Enter at the "Databricks host" prompt left the host empty, so
login fell back to DATABRICKS_HOST or failed later with an unrelated
error. The prompt now refuses an empty answer, which replaces the
device code specific empty host checks.

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
A host pasted with surrounding spaces failed later with an unrelated URL
error, and a host of only spaces got past the empty host check.

Co-authored-by: Isaac <no-reply@databricks.com>
@maksim-zuev-db
maksim-zuev-db marked this pull request as ready for review October 27, 2026 14:00
@maksim-zuev-db
maksim-zuev-db requested review from a team as code owners October 27, 2026 14:00
@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 50e1992

Run: 40554499126

Env ✅​pass 🙈​skip Time
✅​ aws linux-2core-8gb 281 15 6:03
✅​ aws-windows-latest-4core-16gb 283 13 5:17
✅​ azure linux-2core-8gb 280 15 5:33
✅​ azure-windows-latest-4core-16gb 282 13 4:11
✅​ gcp linux-2core-8gb 281 15 5:43
✅​ gcp-windows-latest-4core-16gb 283 13 5:37
Top 6 slowest tests (at least 2 minutes):
duration env testname
5:26 gcp-windows-latest-4core-16gb TestAccept
5:11 aws-windows-latest-4core-16gb TestAccept
4:07 azure-windows-latest-4core-16gb TestAccept
3:48 gcp linux-2core-8gb TestAccept
3:34 aws linux-2core-8gb TestAccept
3:33 azure linux-2core-8gb TestAccept

@simonfaltum simonfaltum left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Left a few comments inline. The main one is in login.go: removing the post-setHost empty check lets device code login continue with an empty host when the input normalizes to "" (e.g. / or ?o=123).

One more outside the diff: promptForAccountID (cmd/auth/auth.go:67) has the same problem for the account ID. An empty answer leaves AccountID == "", ToOAuthArgument then picks up DATABRICKS_ACCOUNT_ID through ConfigAttributes (or OAuth fails later), and the profile can be saved without account_id. Probably fine as a follow-up.

Comment thread cmd/auth/login.go
if !isDeviceCodeSupportedForHost(authArguments.Host, authArguments.DiscoveryURL) {
return errDeviceCodeWorkspaceOnly
}
if useDeviceCode && !isDeviceCodeSupportedForHost(authArguments.Host, authArguments.DiscoveryURL) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This drops the Host == "" check that ran after setHost. The prompt validation doesn't fully cover it, because setHost can still turn a non-empty input into an empty host (TrimSuffix("/") and then ExtractHostQueryParams). So / or ?o=123 at the prompt, or --host /, ends up as Host == "" here.

With an empty host, isDeviceCodeSupportedForHost("", "") returns true, and ToOAuthArgument resolves the host from DATABRICKS_HOST via ConfigAttributes. If that's an accounts host, we get an account OAuth argument, skip the workspace-only check, and save a profile without a host key. On main this returned errDeviceCodeNeedsHost.

Repro:

DATABRICKS_CLI_DEVICE_CODE=true DATABRICKS_HOST=https://accounts.cloud.databricks.com \
  databricks auth login --use-device-code --profile p --host /

I think we should keep an empty check after setHost (see my comment on promptForHost for where I'd put it).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

added a check inside setHost

Comment thread cmd/auth/auth.go
result, err := cmdio.RunPrompt(ctx, cmdio.PromptOptions{
Label: "Databricks host",
Validate: func(input string) error {
if strings.TrimSpace(input) == "" {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The check runs on the raw input, and setHost still normalizes it afterwards. So / or ?o=123 passes here and becomes "". --host /, a positional /, or a profile with host = / never hit this prompt at all. Browser login then falls back to DATABRICKS_HOST (or fails with host must start with 'https://'), and auth token does the same through setHostAndAccountId (token.go:209, token.go:439).

I think a single check at the end of setHost, after the query param extraction, would cover every source:

if authArguments.Host == "" {
	return errors.New("host is required")
}

Side note: cmd/configure/host.go already has normalizeHost/validateHost for the configure prompt. It isn't a drop-in since login needs to keep ?o=/?a=, but it might be worth sharing some of it so the two host prompts don't drift.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

added a check at the end of setHost
sharing normalizeHost/validateHost require to many code changes for this PR, I'd postpone it

Comment thread cmd/auth/auth.go
return cmdio.RunPrompt(ctx, cmdio.PromptOptions{
result, err := cmdio.RunPrompt(ctx, cmdio.PromptOptions{
Label: "Databricks host",
Validate: func(input string) error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This also changes the browser flow. On main, picking "Enter a host" in the profile picker and pressing Enter on an empty prompt left Host == "", so shouldUseDiscovery kicked in and login went through login.databricks.com. Now the prompt loops on "host is required" and Ctrl+C is the only way out (no way back to "Create a new profile").

Was that intended? If so, I think the PR description and changelog should mention it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, I intended this change. I suppose discovery fallback is unexpected here, as it is a part of 'Enter host manually' flow

Comment thread cmd/auth/auth.go
Label: "Databricks host",
Validate: func(input string) error {
if strings.TrimSpace(input) == "" {
return errors.New("host is required")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Two small nits:

  • The removed errDeviceCodeNeedsHost told users they could pass the host as an argument or with --host. Might be worth keeping that hint in this message.
  • promptModel.glyph() runs Validate on every render, so the prompt now opens with a red ✗ before anything is typed (it used to be a green ✔). Minor, but the first frame reads like an error.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

  1. I'm not sure it is suitable here, as a user already entering the url in an interactive mode
  2. I changed it to show nothing until the first edit (unless input is valid)

Comment thread cmd/auth/auth_test.go Outdated
Comment thread cmd/auth/login_device_code_test.go
Comment thread .nextchanges/cli/login-reject-empty-host.md Outdated
maksim-zuev-db and others added 4 commits October 27, 2026 16:40
setHost strips a trailing "/" and the query params, so "/" or "?o=123"
became an empty host after the prompt check. Login and auth token then
resolved the host from DATABRICKS_HOST or failed later. Checking in
setHost covers the prompt, --host, the positional argument, and profile
hosts, and keeps the hint from the removed errDeviceCodeNeedsHost.

Co-authored-by: Isaac <no-reply@databricks.com>
WaitFor matched the error printed for the first empty answer, so the
test passed even if a whitespace-only host was accepted. Golden steps
compare the rendered screen after each Enter.

Co-authored-by: Isaac <no-reply@databricks.com>
The prompt glyph ran Validate on every render, so a prompt that rejects
empty input, such as the login host prompt, opened with a red cross
before anything was typed. The glyph is now blank until the first edit
or Enter, then shows a cross while the input is invalid and a checkmark
once it is valid.

Co-authored-by: Isaac <no-reply@databricks.com>
Mention auth token, and that an empty host after "Enter a host" no
longer starts discovery login.

Co-authored-by: Isaac <no-reply@databricks.com>
@maksim-zuev-db
maksim-zuev-db force-pushed the login-reject-empty-host branch from 49c8f84 to f80c59f Compare October 27, 2026 17:09
maksim-zuev-db and others added 2 commits October 27, 2026 17:45
With --skip-workspace, login stripped the query params from the host
before setHost ran, so a host like "?a=123" became empty and login went
to discovery instead of failing. Both places now share normalizeHost,
which does the empty host check.

The error is now `invalid host "<input>"`. The previous hint to pass
the host as an argument or with --host did not help, because the error
only happens when a host was given.

Co-authored-by: Isaac <no-reply@databricks.com>
cmd/configure and libs/databrickscfg already have a normalizeHost.

Co-authored-by: Isaac <no-reply@databricks.com>

@simonfaltum simonfaltum left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the fixes, the first-round points look addressed. A few more comments inline, nothing blocking.

The glyph change was my suggestion, my bad. It changes every prompt that uses Validate, so I think it should be rolled back here (see the comment on prompt.go).

[nit] In the description, "auth login --skip-workspace --host '?a=123' fails instead of starting discovery login (already the case on main)" reads like a contradiction. On main the host is empty after the query params are extracted, so shouldUseDiscovery returns true and login goes on to discovery. "On main this started discovery login" would be clearer.

Comment thread cmd/auth/auth.go
return cmdio.RunPrompt(ctx, cmdio.PromptOptions{
result, err := cmdio.RunPrompt(ctx, cmdio.PromptOptions{
Label: "Databricks host",
Validate: func(input string) error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nice to have] Validate only rejects blank input, so / or ?o=123 passes here and then fails in setHost. In auth login, picking "Enter a host" and typing / first asks for a profile name (with [/] as the suggested default), then errors with invalid host "/" and you have to start over. In the auth token "Enter a host" path the command exits instead of asking again.

I think running the same normalization in Validate after the blank check would keep the prompt open with the error under it:

return normalizeLoginHost(&auth.AuthArguments{Host: strings.TrimSpace(input)})

Checking for a missing hostname (like validateHost in cmd/configure/host.go) would also catch https:// and https://?o=123, which currently get through as https:/ and https:. An explicit --host / has the same problem: it's only rejected after the keyring probe and (without --profile) the profile name prompt.

Comment thread cmd/auth/login.go
// host and moves ?o= and ?a= into WorkspaceID and AccountID unless they are set.
// It fails if nothing is left of the host, e.g. for "/" or "?o=123", because
// with an empty host login would use discovery or DATABRICKS_HOST instead.
func normalizeLoginHost(authArguments *auth.AuthArguments) error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nice to have] Only the prompt trims spaces. --host " ", a positional arg or a profile host with spaces gets past this check and still fails later with host must start with 'https://': , which is the error the PR description mentions. With --host " https://x.test/?o=1", url.Parse fails on the leading space, so ?o= never ends up in WorkspaceID.

I think trimming once at the top of normalizeLoginHost would cover every source, and then promptForHost wouldn't need to trim its return value.

Comment thread cmd/auth/login.go
params := auth.ExtractHostQueryParams(authArguments.Host)
authArguments.Host = params.Host
if authArguments.Host == "" {
return fmt.Errorf("%w %q", errInvalidHost, rawHost)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nice to have] invalid host "<input>" is user-visible output from both auth login and auth token, but it's only covered by unit tests, and nothing exercises the auth token path end to end. .agents/rules/testing.md asks for acceptance tests first for cmd/... output. This one doesn't need a terminal, so something like musterr $CLI auth login --host '?o=123' --profile test and musterr $CLI auth token --host '?o=123' under acceptance/cmd/auth/login/ and acceptance/cmd/auth/token/ should work.

Comment thread cmd/auth/login.go
var errInvalidHost = errors.New("invalid host")

// normalizeLoginHost strips the trailing slash and the query params from the
// host and moves ?o= and ?a= into WorkspaceID and AccountID unless they are set.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nit] The comment says it moves ?o= and ?a=, but ExtractHostQueryParams also handles ?w= (which the help text calls the preferred form), ?workspace_id= and ?account_id=. Something like "moves the workspace and account ID query params into ..." would stay accurate.

Comment thread libs/cmdio/prompt.go
if m.validate == nil || m.validate(m.value()) == nil {
return ansiBold + ansiGreen + glyphValid + ansiReset
}
if !m.edited && m.submitErr == nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Coming back on my earlier comment about the red ✗ on the first frame, my bad. This changes every prompt that uses Validate, including the databricks configure host prompt, so I think it's out of scope for this PR. Can we roll back the libs/cmdio/prompt.go change and the TestPromptBaseline_Validate/01-empty.golden update (and drop it from the description)? The two new baseline tests and the cmd/auth goldens should still pass with the old glyph. If we still want the blank glyph, it can go in its own PR.


func TestLoginDeviceCodeHostEmptyAfterNormalization(t *testing.T) {
for _, host := range []string{"/", "?o=123"} {
t.Run(host, func(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[nit] With host == "/" the subtest name ends up as TestLoginDeviceCodeHostEmptyAfterNormalization//, which is awkward to target with -run. Named cases ({name: "slash", host: "/"}) would avoid that.

Small thing: TestLoginSkipWorkspaceHostEmptyAfterNormalization in login_test.go landed between the paired TestLoginRejectsPositionalArgWithHostFlag and ...WithProfileFlag tests.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants