CreepJS WebRTC Report: Trace the Fields
Trace CreepJS WebRTC report fields to pinned APIs, SDP transforms and partial-result branches without treating them as identity.
BotBrowser Team
Want the structured docs for Fingerprint?
This article lives in the editorial library. For step-by-step setup, reference material, and ongoing updates, jump into the docs section.
A CreepJS WebRTC panel is a composed report, not a packet capture or a list of everything the browser can do. The pinned collector at commit 10aa6724cd33a1015db1574211890518cd04f0cc combines device-kind enumeration, six media-decoding probes, an offer's SDP, and selected ICE-event data. BotBrowser documents candidate, proxy, and ICE-server controls for authorized workflows; those controls cannot certify a CreepJS report or identify a physical interface or person.
TL;DR
- The collector maps
enumerateDevices()rows to device kinds and sorts them; it does not read labels in that helper. - It asks
decodingInfo()about six fixed audio/video configurations, keeps supported rows, and reduces each to smooth and power-efficient labels. - SDP extension IDs are removed before the remaining extension strings are deduplicated and sorted. The displayed list is not the original SDP.
- The first nonempty event string supplies
iceCandidateandfoundation; another event can later complete the result. - No
RTCPeerConnectionreturnsnullimmediately. A separate three-second timeout can return partial fields, so null, partial output, and an omitted field are not interchangeable verdicts.
Contents
- Collector boundary and pinned revision
- Field provenance map
- What the SDP-derived fields omit
- Candidate fields and partial results
- What the report hash represents
- A bounded reading record
- Practical conclusion
Collector boundary and pinned revision
The source links below identify one public CreepJS revision. The statements describe that code, not a run of the current CreepJS website, BotBrowser, or stock Chrome. The scope is narrower than a WebRTC configuration guide: visible report values are mapped to their producing operations and to the branches where the implementation discards, summarizes, or times out data.
The distinction matters because the neighboring WebRTC codec comparison discusses codec APIs and compatibility, while ICE candidate pooling and privacy timing examines pre-gathering and event timing. WebRTC leak prevention covers proxy and ICE configuration. This page instead follows one collector's report construction; it does not replace those operational guides.
Field provenance map
| Report value or group | Pinned source operation | Transformation or missing state | What it supports |
|---|---|---|---|
mediaDevices | navigator.mediaDevices.enumerateDevices() | Maps each row to kind, then sorts; unavailable API returns null | Which device kinds this helper received, not labels, physical devices, or user identity |
codecsSdp | createOffer() SDP and media format IDs | Summarizes MIME type, clock rates, channels, format parameters, and feedback | A parsed view of selected offer capabilities, not every SDP line or playback success |
extensions | SDP a=extmap lines | Removes the numeric ID, deduplicates, sorts | The remaining extension strings under this parser, not original order or IDs |
iceCandidate and foundation | First nonempty icecandidate event | Extracts foundation once; address success maps known values, timeout keeps it raw | The first event string and a branch-dependent display value, not an interface inventory |
address | connection.localDescription.sdp | Parses a connection line first, otherwise a candidate-shaped substring; excludes 0.0.0.0 | An address returned by this parser, not proof that no other address exists |
foundationProp and stunConnection | Candidate event that reaches the successful address branch | Returned with the parsed SDP address; it may be a later event than the first saved candidate | Fields associated with the event that completed this branch, not necessarily the event behind iceCandidate |
| Display hash | codecsSdp, extensions, foundation, foundationProp, address, and mediaDevices | Hashes a selected subset; iceCandidate and stunConnection are not in this object | Equality of those selected inputs under this implementation, not equality of the full report |
The table is a source map rather than a measurement. The RTCPeerConnection API and the W3C WebRTC specification define the browser-facing interface, but the names and transformations above belong to this pinned collector. A report field with a familiar API name can still be a lossy projection of that API's output.
What the SDP-derived fields omit
The extension helper extracts extmap lines, strips the numeric extension identifier, then applies Set deduplication and lexical sorting. Two SDP offers whose extension IDs or source ordering differ can therefore yield the same displayed extension list if the remaining strings match. That list cannot be used to reconstruct the original lines. The display makes one question easier to answer, “which extension names survived parsing?”, while making two questions impossible: “which numeric ID was assigned?” and “in what order did the offer list them?” A report consumer should not infer either discarded value from the sorted strings.
Codec output is also summarized. The collector reads audio and video media format IDs, finds matching rtpmap, fmtp, and rtcp-fb lines, and combines entries by MIME type. The result keeps selected fields such as clock rates, channels, format parameters, and feedback support. This is useful for reading what the panel chose to display; it is not an unmodified SDP archive or a test that a remote peer accepted every codec.
The parser's order matters. It first finds the media-section format identifiers, then searches the complete offer SDP for lines carrying each identifier. An rtpmap line provides a MIME type and clock rate; for audio, a third slash-separated component is used as the channel count, with a default of one when that component is absent or evaluates to zero. An rtcp-fb line is added to feedback support. Other matching lines can contribute format-specific parameters, split on semicolons. These are presentation fields assembled from matching lines, not a standards validator.
The collector also limits repeated codec detail. RTX entries are treated specially, and an RTX counter prevents a second RTX description from being added through the same route. Entries with the same MIME type are merged: clock rates are combined as unique values, and format parameters or feedback values are combined when those lists are present. If a media section has no format identifiers or no matching lines, its description list can be empty. That says what this parser found in this offer; it does not prove that the browser lacks a decoder or that playback will fail.
This distinction is useful when a panel appears shorter than the SDP. A shorter row can follow from grouping, duplicate removal, a selected field list, or an absent matching line. It is not automatically evidence that a codec was removed from the offer. To answer an offer-level question, inspect a separately authorized and redacted SDP capture; do not reconstruct it from the report table.
The separate decodingInfo() helper builds six fixed configurations: four audio types and two video types, with fixed dimensions, bitrate, frame rate, channel count, and sample rate. The audio inputs are Ogg Vorbis, Ogg FLAC, MP4/AAC, and MPEG audio; the video inputs are Ogg/Theora and MP4/AVC. The fixed video object uses 1920 by 1080, 120000 bits per second, and 60 frames per second. The audio object uses two channels, 300000 bits per second, and a sample rate of 5200. The source sends only the audio object to audio entries and only the video object to video entries.
It keeps only rows whose returned supported value is true, then emits smooth and efficient labels when those booleans are true. A supported row can therefore display no label when both booleans are false. A missing row can mean the reducer omitted a response whose supported value was false; a rejected promise follows a logging path and leaves no supported row to reduce. Neither state alone says why a format is unavailable in another application. The inputs are particular requests, not every channel count, sample rate, resolution, codec profile, or frame rate a reader might use.
This is different from asking which media formats the platform universally supports. The Media Capabilities specification describes the API's support, smoothness, and power-efficiency fields; the pinned collector decides which six inputs to ask and which returned values to keep. The decodingInfo() reference explains the method contract, not the collector's display policy. If an application uses 30 frames per second while the report asks about 60, those are distinct queries. A result for one configuration cannot be silently carried over to the other. The same limitation applies to audio sample rate and channel count: a two-channel request at the fixed source value does not enumerate all possible audio configurations.
Candidate fields and partial results
The callback stores the first nonempty candidate string and parses its foundation, but it keeps listening until getIPAddress() can parse an address from the connection's local-description SDP. On that address-success path, knownInterface maps 842163049 to public interface and 2268587630 to WireGuard; the three-second timeout instead returns the raw parsed foundation. The displayed value therefore depends on the stop branch and does not establish an interface inventory. The returned foundationProp and stunConnection come from the event that satisfied the address branch, so those fields need not all originate from one candidate event.
There are also two different stop conditions. If window.RTCPeerConnection is absent, the collector resolves null before creating a connection. Otherwise it creates a connection with a pool size of one and two Google STUN URLs, creates a data channel, and requests an offer with audio and video receive options. It calls setLocalDescription() and immediately reads the offer object's SDP for the extension and codec summaries. The code does not wait for setLocalDescription() to settle before this first parsing step.
The collector then starts a three-second timer while listening for icecandidate events. If the timer fires with an offer SDP, the result can contain codec, extension, foundation, and candidate fields without the later address-dependent fields; without SDP, it resolves null. The timeout removes the listener and closes the connection. This is a source-defined time window, not proof that ICE gathering finished or that the final candidate set is complete. The source does not label all these cases with one universal reason code.
getIPAddress() tries an SDP connection line first and falls back to a candidate-shaped substring. It excludes the literal 0.0.0.0, but an undefined parser result only establishes that this code did not return an address. It does not prove that the browser had no interface, that no candidate existed, or that a remote service accepted or rejected the session. The parser is not a network inventory: it returns one matching address according to its connection-line-first rule. The separate mDNS candidate privacy guide covers what a candidate's address form can and cannot reveal.
There is a subtle association boundary in the returned object. iceCandidate and foundation are set only while the saved string is empty, so they preserve the first nonempty event string. Address parsing instead reads the current connection.localDescription.sdp. When an event reaches an SDP containing a parseable address, the callback returns foundationProp and stunConnection from that current event. A reader who groups every returned field under “the first candidate” would conflate two different selection rules.
The implementation can consequently return a mixed-provenance object: an early event's saved string and foundation alongside an address found in the current local description and event-specific fields from the event that satisfied the address check. “Mixed” here describes which code branch supplied each property; it does not establish a race, inconsistency, or network defect. A report consumer should preserve the source distinction instead of asserting that all properties describe a single packet or interface.
The callback also ignores events with no candidate object and returns early when the current local-description text does not yield an address. It does not end collection on every event. The timer remains the other termination path, and its partial result contains fewer properties than the address-success path. When comparing two reports, first ask whether both reached the same branch; otherwise a difference in object shape may be a timing or availability difference rather than a changed value in a common field.
What the report hash represents
The display code builds its hash input from codecsSdp, extensions, foundation, foundationProp, address, and mediaDevices. It does not include every returned value: iceCandidate and stunConnection are not members of that hash input. A changed raw candidate string could therefore be absent from the hash comparison if the selected fields stay the same; conversely, a changed selected field can change the summary without identifying why it changed.
Hash equality is an implementation-specific comparison aid. It does not establish that two browsers are the same device, that two complete reports match, or that an external site assigns the same outcome. If two digest values differ, the source tells us that at least one selected input differs under the hash implementation; the digest itself does not say which input changed. If the digests match, omitted fields can still differ because they were not included in the input object. The displayed digest is therefore a compact cue to inspect the selected fields, not a substitute for them.
The producer also computes mediaDevices independently of the peer-connection result. The HTML rendering code consumes that list to map device kinds to short labels, and the hash input includes the list alongside SDP-derived values. A permission-filtered or unavailable device enumeration can change that portion of the hash even when the offer fields remain the same. Conversely, an offer-side change can alter a selected SDP field while the device-kind list stays constant. The digest combines categories that have different APIs and collection paths.
For a careful comparison, keep three levels separate: the source operation, its returned value, and the display projection. For example, enumerateDevices() returns device records; this helper keeps only kinds; the UI then maps those kinds to labels. The source list, helper output, and visible label are not interchangeable. Likewise, decodingInfo() returns a support object; the reducer keeps a subset; the panel formats the surviving fields. The code path, not just the final text, defines what the report can support.
Maintenance of this source map should track changes to fixed inputs, retained properties, timeout handling, or hash fields in later CreepJS revisions. Comparing only a rendered label would miss which stage changed. Pin the revision, compare the producer and consumer functions, and update only the relevant row in the provenance map. This procedure describes public-source maintenance, not a browser-build test. For layer boundaries beyond this collector, see public detector-suite coverage.
A bounded reading record
When preserving a source interpretation, record the source revision and only the field-level distinctions needed for the question. Do not retain raw addresses, device labels, full SDP, or candidate strings when a redacted state is sufficient. A useful record identifies whether a value came from an API call, an offer description, an event callback, or a UI projection. It also records whether the property was absent, filtered out, or returned by a timeout path. Those labels make two reports comparable without copying their sensitive contents.
{
"sourceCommit": "10aa6724cd33a1015db1574211890518cd04f0cc",
"field": "extensions",
"sourceOperation": "getExtensions(sdp)",
"steps": ["remove numeric extmap id", "deduplicate", "sort"],
"state": "parsed",
"interpretationLimit": "not original SDP order or extension IDs"
}
The example records a code path, not a browser observation. If a real support investigation separately needs runtime evidence, capture it under an approved test plan and redact values before sharing; the source review here does not claim such a run. Avoid storing a full SDP or an unredacted address merely to prove that a parser returned a field. A boolean state such as addressPresent, a named branch, and the source revision may answer the operational question with less retained data. The record should never be presented as evidence of a person's identity or a remote decision.
FAQ
Does a CreepJS WebRTC hash identify a browser?
No. The pinned display code hashes selected report inputs. It neither hashes every returned field nor proves a physical device, person, or external service decision.
Does a missing address prove WebRTC is disabled?
No. The parser may return no address while the collector has other output, and the timeout may return partial data. The source has a separate immediate null branch when RTCPeerConnection is unavailable.
Is the extension list the original SDP?
No. Numeric IDs are stripped, duplicates are removed, and values are sorted. The list cannot reconstruct the source line order or identifiers.
Does decodingInfo() show every supported media format?
No. This collector asks six fixed configurations and retains only supported responses with selected labels. Another configuration, browser version, or device context may produce a different result.
Sources
- Pinned CreepJS WebRTC collector
- W3C WebRTC
- RFC 8866: SDP
- W3C Media Capabilities
- MDN: RTCPeerConnection
- MDN: MediaCapabilities.decodingInfo()
- BotBrowser WebRTC leak prevention
Practical conclusion
Read the CreepJS WebRTC panel as a set of fields with different producers and transformations. Keep the source revision, distinguish absent API from timeout and filtered output, and avoid turning a partial report or selected-field hash into a device, identity, or verdict claim.
This article is part of the guide: What Is a WebRTC IP Leak? Prevention and Browser Privacy
Related Articles
CreepJS Math Signals: Read the Source Fields
FingerprintCreepJS Audio Reports: Rendering and Missing Values
FingerprintBrowserLeaks WebGL Report: Collection and Missing States
FingerprintCreepJS WebGL Report: Contexts and Capability Signals
FingerprintWebRTC Codec Fingerprinting: Media Capabilities Leak
FingerprintCreepJS Intl Report: Fields and Missing Values
Take BotBrowser from research to production
The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.