Browsers · WhiskerJS 0.1.0

DuckDuckGo

Since signalsVersion 10 the DuckDuckGo browsers have their own class, duckduckgo. Nothing is excluded; reads follow the engine underneath, WebKit on Apple devices and Chromium elsewhere.

In plain words

DuckDuckGo makes its own browser for Mac, Windows, iPhone and Android. Underneath it uses the engine each system offers: Apple’s WebKit on the Mac and iPhone, Chromium on Windows and Android. To a web page it looks much like Safari or Chrome, so WhiskerJS checks for DuckDuckGo first and reports its own class, duckduckgo. Nothing is left out on purpose here; everything else works as in the browser with the same engine.

At a glance

  • Browser class: duckduckgo since signalsVersion 10 (navigator.duckduckgo with an isDuckDuckGo method, or a Ddg/ or DuckDuckGo/ token in the user agent). Before, the class of its engine: safari on macOS and iOS, chromium or chromium_webview elsewhere.
  • Excluded components: none. The duckduckgo row of the exclusion table is empty.
  • Engine: WebKit on macOS and iOS (vendor Apple Computer, Inc.), Chromium on Windows and Android. Engine-dependent reads and checks follow it.
  • On macOS and iOS, as in Safari: device_memory, heap_limit, ua_client_hints, ua_versions and keyboard_layout are unavailable / no_api; a CPU count of 4 or 8 is the bucket 4_8; audio_latency is read.
  • Lab runs: on one Mac, 10 of 11 manual demo runs kept one visitorId at signalsVersion 9; not yet run at signalsVersion 10 or 11. See Lab runs.

How DuckDuckGo is recognised

The class test checks for DuckDuckGo right after Brave and before everything else, because on Apple devices DuckDuckGo would otherwise match the Safari step. It looks for two things, without calling or awaiting anything:

  1. navigator.duckduckgo with an isDuckDuckGo method. DuckDuckGo’s open-source content-scope scripts define it.
  2. The browser’s own token in the user agent: Ddg/<version> on macOS, DuckDuckGo/<version> on iOS and Android.

Stock Safari, Safari with Advanced Fingerprinting Protection and look-alikes (the DuckDuckBot/ crawler, a bare window.duckduckgo, an object without the method) are not classed duckduckgo; the library’s tests cover each case. Like every class, it is a hint from values the client controls.

What works

  • On macOS and iOS (WebKit) WhiskerJS reads what it reads in Safari: canvas, WebGL, offline audio, fonts, font preferences, Math, codecs, plugins, locale, screen, storage presence, permission states and the display preference queries, plus audio_latency. Whether apple_pay and private_click report a value or none depends on what the app turns on.
  • On Windows and Android (Chromium) the Chromium-only components (device_memory, heap_limit, ua_client_hints, ua_versions) can be read, as in Chrome.
  • canvas and webgl_image: since signalsVersion 10, when the first two renders differ, up to two more run within the component’s 1000 ms timeout, and the value is accepted once two in a row agree. Only a mismatch that persists is noisy / render_mismatch.
  • content_blockers: DuckDuckGo hides ads by default and collapses the closest empty parent of a hidden ad, so the bait container can disappear. Since signalsVersion 10 that reports every bait name, and layout is judged from a plain control element. It is outside both ids.

What is unavailable or blocked

On macOS and iOS the five Chromium-only components report unavailable / no_api, as in Safari: device_memory, heap_limit, ua_client_hints, ua_versions and keyboard_layout. A non-ok status counts the same in every run, so these do not make the id unstable. webgpu depends on the version and the system.

What WhiskerJS excludes and why

Nothing. DuckDuckGo’s help page on its web tracking protections names no API it randomises or replaces with a fixed value, so there is no evidence for a row. If DuckDuckGo adds seeded noise anyway, the known-answer probes turn canvas, webgl_image or audio into noisy / seeded_noise instead of letting the id change.

Integrity flags to expect

  • engine_mismatch checks the engine behind the class, which for duckduckgo is decided from navigator.vendor and the user agent: WebKit behaviour on Apple devices, V8 behaviour elsewhere. A user-agent switcher can raise it.
  • ua_ch_mismatch can only run where Client Hints exist (Windows and Android).
  • native_tampering and prototype_lie are designed to spot scripts that override functions. Whether DuckDuckGo’s own injected scripts trip them has not been measured.

What the lab runs showed

On one Mac (macOS 26.5.1), manual demo runs in the lab mode, a check, not a benchmark:

  • signalsVersion 9: 11 runs over 6 page loads. 10 runs had one visitorId; one run had a second, because one canvas render differed from the other (canvas noisy / render_mismatch), which changed the gpu part. In 7 of the 11 runs content_blockers was unavailable / no_layout. The class was safari, as signalsVersion 9 had no DuckDuckGo class. The visitorIds differed from those of Brave, Chrome and Firefox on the same Mac.
  • These two findings led to the extra renders and the content-blocker fix of signalsVersion 10.
  • signalsVersion 10 and 11 (11 is the live demo’s version since 2026-10-26; it changes nothing for DuckDuckGo): not yet run. DuckDuckGo has no automation interface, so the automated local run left it out; a manual run is pending.

DuckDuckGo on Windows, iOS and Android has not been run at all.

Testing it yourself

Open the live demo in DuckDuckGo: browser_class should say duckduckgo, and the demo’s detected browser should say DuckDuckGo. Run it several times, close the window, and run again; the lab mode copies one line per run, so you can count how many visitorIds you see. Nothing is sent anywhere.

Lab runs for DuckDuckGo on macOS

Aggregated outcomes from the lab runs (updated 2026-10-26): how many runs and how many different visitorIds. A check on one device, not a benchmark.

signalsVersionMethodBrowserRunsvisitorIdsNote
9manual demo runs, 2026-10-26DuckDuckGo, macOS (classed safari at signalsVersion 9)1126 page loads; one run had a noisy canvas render (render_mismatch) that changed the gpu part; content_blockers was not ok in 7 of 11 runs

Support table for DuckDuckGo on macOS

Expected status of every component in this browser at signalsVersion 11, from the support matrix (updated 2026-10-26). Basis "code" means expected from the library code and documentation, not yet measured; "lab" means seen in the early lab sample at signalsVersion 1; "run" means seen in every run of the automated local run at signalsVersion 10 on a Mac; "ci" means seen in the CI browser matrix at signalsVersion 10 on Linux (automated, headless).

ComponentStatusBasisNote
apple_payvariescodeDepends on whether the app offers ApplePaySession; none where it is missing.
audiookcodeNo exclusion row for duckduckgo.
audio_latencyokcodeRead on the WebKit engine; the context is closed at once.
browser_classokcodeClass duckduckgo since signalsVersion 10 (navigator.duckduckgo with isDuckDuckGo, or a Ddg/ token in the user agent); signalsVersion 9 classed it safari.
canvasokcodeRendered again when the first two renders differ (since signalsVersion 10); only a persistent mismatch is noisy / render_mismatch. At signalsVersion 9 one of 11 manual runs had a noisy canvas render.
canvas_readbackokcodeexact, unless an extension adds canvas noise (then altered).
color_depthokcode
color_gamutokcode
content_blockersokcodeDuckDuckGo hides ads by default, so expect bait names or none. At signalsVersion 9 it was unavailable / no_layout in 7 of 11 manual runs; fixed in signalsVersion 10, not yet rerun here.
cpu_architectureokcode
device_memoryunavailablecodeWebKit does not offer navigator.deviceMemory. Seen unavailable / no_api in the manual runs at signalsVersion 9.
dynamic_rangeokcode
font_preferencesokcode
fontsokcode
fonts_canvasokcode
forced_colorsokcode
hardware_concurrencyokcodeRead 16 times; a 4 or an 8 is reported as the bucket 4_8, because WebKit alternates between them for scripts on its known-fingerprinting list.
heap_limitunavailablecodeperformance.memory is a Chromium API. Reports unavailable / no_api. Seen unavailable / no_api in the manual runs at signalsVersion 9.
intl_localeokcode
keyboard_layoutunavailablecodeNo navigator.keyboard here: unavailable / no_api. Seen unavailable / no_api in the manual runs at signalsVersion 9.
languagesokcode
mathokcode
media_codecsokcode
noise_profileokcodeexact unless something adds noise to the test drawings.
pdf_viewerokcode
permissionsokcode
pixel_ratiookcode
platformokcode
pluginsokcode
prefers_contrastokcode
private_clickvariescodeDepends on whether the app turns on Private Click Measurement; none where it is off.
reduced_motionokcode
screen_frameokcode
screen_resolutionokcode
speech_voicesokcodeDigest of the voice list, or none; some browsers fill the list late.
storageokcode
time_zoneokcode
touch_supportokcode
ua_client_hintsunavailablecodenavigator.userAgentData (Client Hints) is a Chromium API. Reports unavailable / no_api. Seen unavailable / no_api in the manual runs at signalsVersion 9.
ua_versionsunavailablecodenavigator.userAgentData (Client Hints) is a Chromium API. Reports unavailable / no_api. Seen unavailable / no_api in the manual runs at signalsVersion 9.
vendorokcode
vendor_flavorsokcode
webgl_imageokcodeRendered again when the first two renders differ (since signalsVersion 10).
webgl_paramsokcodeWhere WebGL 1 is available; unavailable / no_context without it.
webgl_rendererokcodeOften a generic string such as Apple GPU.
webgl_vendorokcode
webgpuvariescodeDepends on browser version, operating system and GPU; needs a secure context. unavailable / no_api without navigator.gpu, no_context without an adapter. Seen ok in every run of the automated local run at signalsVersion 10 (default settings).

Questions and answers

Why does DuckDuckGo need its own class?

On a Mac or an iPhone the DuckDuckGo browser looks like Safari to a page: the Apple vendor and a Safari-like user agent. Until signalsVersion 9 WhiskerJS classed it safari, so a server could not tell the two apart. Since signalsVersion 10 it is classed duckduckgo, recognised by navigator.duckduckgo with an isDuckDuckGo method or by the browser's own user-agent token.

Does WhiskerJS exclude anything in DuckDuckGo?

No. DuckDuckGo's help page on its web tracking protections names no browser API it randomises, so the duckduckgo row of the exclusion table is empty. If a measurement shows otherwise, the row changes and signalsVersion goes up.

Is the id stable in DuckDuckGo?

Not measured at signalsVersion 10 or 11 yet. At signalsVersion 9, on one Mac, 11 manual demo runs gave one visitorId 10 times; the odd run had one perturbed canvas render. signalsVersion 10 renders again when two renders differ, which is meant to settle such a one-off, but a manual rerun is still pending.

Can the local real-browser script test DuckDuckGo?

No. DuckDuckGo offers no WebDriver or other automation interface, so the library's script lists it as manual. DuckDuckGo is tested by hand with the demo's lab mode.