DEV Community

SteamVaults
SteamVaults

Posted on

A Steam Trade URL Is Not a Password: Validate It Without Overpromising

A Steam Trade URL looks more sensitive than an ordinary profile link. It contains a numeric partner value and a token, so users often assume that anyone who sees it can access their account.

That is not what the URL does.

The URL lets someone outside the account's friend list open a trade offer. It does not reveal the Steam password, bypass Steam Guard, accept an offer, or move an item without the account owner's confirmation.

This distinction creates an interesting validation problem: a website can verify that a submitted URL has the expected Steam structure, but it must not turn that syntax check into a promise that the trader, offer, or transaction is safe.

The expected URL structure

A typical URL looks like this:

https://steamcommunity.com/tradeoffer/new/?partner=123456789&token=AbCdEfGh
Enter fullscreen mode Exit fullscreen mode

The parts worth validating are:

  • the scheme is HTTPS;
  • the host is exactly steamcommunity.com;
  • the path is exactly /tradeoffer/new/;
  • partner contains only decimal digits; and
  • token contains only the characters Steam uses for the token.

Checking the host exactly is important. A string such as steamcommunity.com.example.org belongs to example.org, not to Steam.

A small JavaScript parser

const STEAM_HOST = "steamcommunity.com";
const TRADE_PATH = "/tradeoffer/new/";
const PARTNER_PATTERN = /^\d+$/;
const TOKEN_PATTERN = /^[A-Za-z0-9_-]+$/;

export function parseSteamTradeUrl(input) {
  let url;

  try {
    url = new URL(input.trim());
  } catch {
    return { ok: false, error: "The value is not a valid URL." };
  }

  if (url.protocol !== "https:") {
    return { ok: false, error: "The URL must use HTTPS." };
  }

  if (url.hostname.toLowerCase() !== STEAM_HOST) {
    return { ok: false, error: "The host must be steamcommunity.com." };
  }

  if (url.pathname !== TRADE_PATH) {
    return { ok: false, error: "This is not a Steam trade-offer URL." };
  }

  const partner = url.searchParams.get("partner");
  const token = url.searchParams.get("token");

  if (!partner || !PARTNER_PATTERN.test(partner)) {
    return { ok: false, error: "The partner value is missing or invalid." };
  }

  if (!token || !TOKEN_PATTERN.test(token)) {
    return { ok: false, error: "The token is missing or invalid." };
  }

  return {
    ok: true,
    value: {
      canonicalUrl: `${url.origin}${TRADE_PATH}?partner=${partner}&token=${token}`,
      partner,
      token,
    },
  };
}
Enter fullscreen mode Exit fullscreen mode

This parser deliberately uses URL instead of substring checks. A check such as input.includes("steamcommunity.com") would accept phishing domains that merely contain the trusted name somewhere in the string.

What the validator proves

If the function returns ok: true, it proves only that the submitted value has the structure expected of a Steam Trade URL.

That is useful. It can distinguish a trade URL from:

  • a Steam profile URL;
  • a Community Market listing;
  • a malformed or incomplete link; and
  • a look-alike domain.

It can also produce a canonical URL before storing it, preventing unrelated tracking parameters from being carried through the application.

What it cannot prove

Syntax validation cannot establish that:

  • the URL belongs to the person currently using the site;
  • the recipient of a later offer is trustworthy;
  • an incoming trade contains the expected items;
  • an outside service will settle a transaction correctly; or
  • the user's Steam account is secure.

Those claims require different controls.

Ownership should be tied to a Steam-authenticated session and checked against the account data available to the application. Every actual offer still needs a server-side transaction record containing the expected recipient, item, quantity, and direction. The user must compare those details in Steam before approving the offer.

For a working example of the syntax layer, this Steam Trade URL checker separates profile links, malformed inputs, and correctly structured trade-offer URLs. It intentionally does not label a trader or transaction as safe.

Storage and logging

A Trade URL is not a password, but the token is still unnecessary in many logs.

Practical precautions include:

  • avoid sending the full URL to analytics tools;
  • redact the token from application logs and error reports;
  • do not place the URL in page titles or public support screenshots;
  • restrict database access to the components that create trade offers; and
  • let the user replace the stored URL when they regenerate it in Steam.

A safe log message can retain the validation result and partner value without preserving the token:

logger.info("steam_trade_url_validated", {
  userId,
  partner,
  tokenStored: false,
});
Enter fullscreen mode Exit fullscreen mode

Whether the application needs to store the complete URL depends on its function. A service that creates future trade offers may need it. A simple educational checker usually does not.

The important boundary

Good validation tells the truth about its scope.

The Trade URL is a route for creating an offer, not a credential that approves one. A parser can confirm the route's syntax, but the security-critical decision still happens later: the user must inspect the real Steam confirmation screen and reject any offer whose account, item, quantity, or direction does not match the expected transaction.

That boundary is more valuable than a green “safe” badge. It gives developers a precise contract to implement and gives users a clear reason to slow down before confirmation.

Disclosure: I work on SteamVaults, an independent third-party Steam service. SteamVaults is not affiliated with Valve or Steam. The linked checker is provided as a live example of the limited syntax-validation layer described in this article.``

Top comments (0)