WebGL renderer is always SwiftShader, with no session option to change it

Author: anishscoutCreated Sep 8, 2026Updated Sep 8, 2026

Summary

Every Steel session reports a software WebGL renderer, and the Sessions API offers no way to change it. Because the value is byte-identical across all Steel users, it also works as a cross-customer correlate.

Observed on a standard cloud session:

vendor:   Google Inc. (Google)
renderer: ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)

A real desktop Chrome reports its actual GPU, e.g. ANGLE (Apple, ANGLE Metal Renderer: Apple M1 Pro, …) or ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 Direct3D11 …). SwiftShader is Chrome's software rasteriser, so the string effectively says "this machine has no GPU" — true of almost no consumer device.

Where it comes from

This looks deliberate rather than incidental. In api/src/services/cdp/cdp.service.ts, --disable-gpu is in the always-applied set:

javascript
const staticDefaultArgs = [
  "--remote-allow-origins=*",
  "--disable-dev-shm-usage",
  "--disable-gpu",
  …
];

and headful sessions additionally force software GL:

javascript
const headfulArgs = [
  "--ozone-platform=x11",
  …
  "--use-gl=swiftshader",
  "--in-process-gpu",
  …
];

Why it stands out

The rest of the fingerprint injection is good — navigator.webdriver is false, no cdc_/$cdc keys, window.chrome present, a plausible UA. The WebGL renderer is inconsistent with all of it: everything else looks like ordinary desktop Chrome, and the renderer says software rendering. A fingerprint that disagrees with itself is more distinctive than one that is uniformly honest.

It is also not fixable from the client side. Patching WebGLRenderingContext.prototype.getParameter in an init script changes the string but leaves actual rendering behaviour, supported extensions and timing unchanged, so anything that cross-checks sees the mismatch. A coherent value has to come from the browser.

What we tried

  • stealth_config: { humanizeInteractions: true } — no effect on the renderer string (expected; noting it for completeness).
  • stealth_config: { skipFingerprintInjection: false } (the default) — injection normalises navigator.webdriver and friends but not WebGL. If it is meant to cover the renderer, that looks like a bug.
  • Checked the Sessions API create options for anything GPU-related: stealth_config exposes only autoCaptchaSolving, humanizeInteractions, skipFingerprintInjection; device_config is desktop | mobile. Nothing reaches Chrome's GL flags.

Questions

  1. Are GPU-backed sessions available on any plan, or is software rendering universal across Steel Cloud?
  2. Is there a supported way to override Chrome launch args on a cloud session — an option we have missed, or an experimental_features value?
  3. If not, could fingerprint injection normalise the WebGL vendor/renderer to a plausible GPU consistent with the injected platform and UA? (With the caveat above wherever a site cross-checks rendering behaviour.)
  4. For self-hosting, would you consider making the GL flags configurable (--use-gl / --disable-gpu driven by env), so a self-hoster on a GPU instance can opt into hardware rendering?

Reproduction

javascript
import Steel from "steel-sdk";

const client = new Steel({ steelAPIKey: process.env.STEEL_API_KEY });
const session = await client.sessions.create({});
// connect over CDP, then in the page:
//   const gl = document.createElement("canvas").getContext("webgl");
//   const dbg = gl.getExtension("WEBGL_debug_renderer_info");
//   gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL);
//   gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL);

Returns the SwiftShader strings above on every session we have inspected.

Environment

  • Steel Cloud (api.steel.dev), default session options
  • Connected via Playwright connect_over_cdp
  • Session UA: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
  • Other values observed: navigator.webdriver: false, plugins.length: 0, mimeTypes.length: 0, deviceMemory: undefined, hardwareConcurrency: 6

plugins.length: 0, mimeTypes.length: 0 and an absent navigator.deviceMemory are smaller differences in the same family — real Chrome exposes a handful of plugins/mimeTypes and reports deviceMemory. Noting them here rather than opening separate issues; can split them out if you prefer.