skip to content

Endpoint URL Form

A remote run starts with an address instead of a browser launch, and that address routinely carries the account credential inside it - which is why the endpoint is also a secret.

on this pageshow

explore

questions

5

Why is a remote WebDriver endpoint address that carries user info a secret in its own right?

level: middleimportance: must knowfreq 72%

answer

  1. the secret is inside a URL
  2. URLs get printed everywhere
  3. the client library masks it for a reason
  4. masking stops at the tool's edge
  5. user info beats configured credentials

basics

~20 s

Because the credential is a component of the URL rather than a separate field, every tool that touches the address touches the secret. Selenium's own client both authenticates from that user info and masks it before printing a request.

solid answer

~50 s

In the `user:password@host` form the credential is not a field beside the address — it *is* part of the address. So anything that handles URLs handles the secret: configuration files, error messages, shell history, proxy access logs, a screenshot of a terminal. Selenium's client shows both halves of the problem. `JdkHttpClient` reads the base URI's user info and builds a `PasswordAuthenticator` from it, so the address really is the credential; and the same class exposes `maskUrlCredentials`, which rebuilds the URI with `***` in place of the user info, used in every request-failure message it formats. Selenium Grid does the same through `NodeStatus.getMaskedUri()`. The lesson is not that masking solves it — masking is per-tool, and stops at the edge of the tool that implements it. The lesson is that the whole address must be treated as the secret.

code

java · 12 lines
java
// Credit-union statements portal suite. The address IS the credential,
// so it is built at run time and never stored fully formed.
String raw = String.format("http://%s:%[email protected]:4444/wd/hub",
    System.getenv("GRID_USER"), System.getenv("GRID_PASS"));

// Selenium's own helper rebuilds the URI with *** in place of user info.
// Its test asserts exactly this transformation.
System.out.println(JdkHttpClient.maskUrlCredentials(raw));
// -> http://***@grid.internal:4444/wd/hub

WebDriver driver = new RemoteWebDriver(
    URI.create(raw).toURL(), new ChromeOptions());

go deeper

for a junior

Be ready to say that when the credential sits inside the address, the address is the secret, so it must not be pasted into tickets, chat messages or configuration files.

for a middle

Be ready to explain why this form is unusual: the secret lives inside a value that software is designed to pass around and print, which is why the reference client ships a masking helper for it at all.

for a senior

Be ready to audit a suite for this, naming the places an address escapes beyond the client's own logs, and to explain the precedence rule that makes a half-finished migration look complete.

for a principal

Be ready to decide whether the estate standardises on the embedded form or bans it, knowing that banning it costs compatibility with services and guides that assume it, and that allowing it puts a secret in every URL-shaped surface.

## The credential is a URL component, not a field Most credentials arrive as their own thing: a header, a body member, an environment variable read by one function. The endpoint address form is different. Written as `http://user:password@host:port/path`, the secret is a *component of a URL*, and that changes who handles it. A URL is exactly the kind of value software passes around freely — it goes in configuration, in log lines, in error messages, in dashboards, in shell history, in a colleague's screenshot of a terminal. Every one of those now carries the credential, and none of them was written by anyone thinking about secrets. This is why the endpoint on a rented fleet is so often the credential too. Ggr's own quick start publishes the shape verbatim — `http://test:test-password@localhost:4444/wd/hub` — and Ggr is unmaintained by its own README, but the form long outlived it, because it needs nothing from the client: any HTTP stack understands user info. ## Selenium's own client shows both halves You do not have to take the risk on faith; the reference client behaves in a way that documents both sides of it. - **The address is treated as the credential.** `JdkHttpClient` reads `baseUri.getUserInfo()` and, when it is non-empty, installs a `PasswordAuthenticator` built from it. Only when the user info is absent does it fall back to credentials supplied through `ClientConfig.authenticateAs`. - **The address is treated as a secret.** The same class exposes `maskUrlCredentials`, which rebuilds the URI with `***` in place of the user info. Its own test asserts the transformation directly: `maskUrlCredentials("http://user:[email protected]:4444/wd/hub")` yields `"http://***@my.grid.com:4444/wd/hub"`. - **It is used where addresses escape.** `JdkHttpClient` formats every request-failure message through it, and Selenium Grid's `NodeStatus` exposes `getMaskedUri()` so that a node's external URI is reported masked rather than raw. A library only goes to that trouble for a value it expects to leak. The masking is evidence of the hazard, not a solution to it. ## Masking stops at the edge of the tool that implements it This is the part that catches people out. Selenium masks what Selenium formats. It does not, and cannot, mask: - a shell that echoes the command line that started the run; - a proxy or gateway that logs the request line it forwarded; - a stack trace raised by a different library that happens to embed the address; - a configuration file checked into a repository; - a reporting tool that records the endpoint the run used; - a person pasting "the URL that doesn't work" into a chat message. The address is a secret in every one of those places, and only in the first is anyone masking it. So the operating rule is not "rely on masking" — it is "treat the whole address as the secret, and never let a fully-formed one come to rest anywhere". ## The precedence trap There is a specific way teams believe they have fixed this and have not. Selenium's client prefers the base URI's user info over configured credentials, and `RemoteWebDriver` always installs the URL argument as the client configuration's base URI. So if the credit-union statements portal suite is changed to pass `ClientConfig.authenticateAs(new UsernameAndPassword(...))` but the endpoint value still carries a stale `user:password@`, the address silently wins. Nothing warns. The migration looks done, the old credential is still in use, and it is still inside a URL. The check is mechanical: assert that the endpoint you pass has no user-info component, and fail the run loudly if it does. | where the credential sits | who sees it | what masks it | |---|---|---| | inside the address | anything handling the URL | only tools that implement masking | | beside the address, as data | the client's auth code | not applicable; it is never in a URL | ## Practical rules for this shape 1. **Assemble the address at run time from parts.** A stored, fully-formed address is a stored secret, whatever the file is called. 2. **Prefer the client's credential API** so the value never enters a URL parser or a URL-shaped log line. 3. **Assert the endpoint is clean** once you have migrated, because the precedence rule makes a leftover invisible. 4. **Assume anything holding a URL holds the secret**, and go looking for those places rather than trusting that a library masked it. 5. **Never paste a failing address into a ticket, a chat or a screenshot.** That is the most common way this credential actually escapes, and no tooling anywhere intercepts it. ## What a strong answer sounds like A weak answer says "keep secrets out of source control", which is true of every secret and says nothing about this one. A strong answer names the specific property: the secret is inside a value whose whole purpose is to be passed around and printed. That is what makes the endpoint address unusual, that is why the reference client ships a masking helper for it, and that is why the durable fix is to stop producing the embedded form rather than to get better at hiding it.

  • Selenium already masks the credential in its messages. Why is that not enough?
    Because masking is per-tool. Selenium masks what Selenium formats, so a request-failure message and a node's reported URI are covered. A shell echoing the command line, a proxy logging the request it forwarded, a stack trace from another library, a configuration file, or someone pasting the address into a chat are not. The helper is evidence that leaks are expected, not a control that prevents them.
  • A team says it has moved the credential out of the URL. How would you verify that?
    Assert it in code. Selenium's client prefers the base URI's user info over credentials given through `authenticateAs`, and `RemoteWebDriver` always installs the URL argument as the base URI, so a leftover `user:password@` silently overrides the new path with no warning. Parse the configured endpoint, check its user-info component is absent, and fail the run loudly if it is not.
  • Why does the embedded form persist when a better one exists?
    Because it requires nothing of the client. Any HTTP stack understands user info in a URL, so a service can accept the credential that way without the client library exposing anything special, and a single string is easy to hand to a colleague or paste into a configuration field. The convenience is real; the cost is that a value designed to be passed around now has to be guarded.

saying these in an interview costs you the question

  • Says the address is fine because the client masks it in logs
  • Treats this as generic advice about keeping secrets out of source control
  • Assumes configured credentials override a credential left in the address
  • Thinks an address is safe to paste into a ticket once the run has failed
  • Stores a fully-formed endpoint address in a configuration file
open as a page

In a remote WebDriver endpoint address, what do the scheme, user info, host, port and path each decide?

level: juniorimportance: should knowfreq 66%

basics

~20 s

The scheme decides whether the hop is encrypted, the user info carries the account credential, the host and port name the front door of the service, and the path picks which service behind that door answers WebDriver commands at all.

open as a page

In the W3C WebDriver spec, where should an intermediary node's authentication travel in a new-session request?

level: juniorimportance: should knowfreq 48%

basics

~20 s

The specification recommends sending it as top-level parameters beside capabilities, not inside them, because authentication is an argument to the New Session command rather than a browser feature. Its own example shows user and password at the top level.

open as a page

What can a remote WebDriver endpoint address decide beyond which machine answers the request?

level: middleimportance: should knowfreq 52%

basics

~20 s

A remote WebDriver endpoint address names a front door, not a browser host. The intermediary behind it chooses the data centre and the individual machine, and that same address may itself be relayed onward to a further WebDriver service.

open as a page

A remote WebDriver endpoint address stops authenticating after the credit-union statements portal's account password is rotated to a value containing @ and : characters. What broke?

level: seniorimportance: should knowfreq 44%

basics

~20 s

The address is parsed as a URL before anything reads the credential, so an unescaped at sign leaves parsers disagreeing about where the host starts, and a colon splits the user info early. Keep the credential outside the address.

open as a page