CreepJS Prediction Report: Aggregation and Unknown States
Compare CreepJS getPrediction lookup with renderPrediction signal aggregation and unknown states; neither output proves device 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.
BotBrowser can keep a declared browser profile coherent across browser-visible surfaces, but it cannot certify a third-party crowd prediction. In the pinned CreepJS source, getPrediction() matches a crowd-data row and selects candidate values, while renderPrediction() separately reads decryptionData, aggregates signal fields, and displays review or score states. These are distinct source paths; neither output proves a person's identity, a machine's identity, or a bot verdict.
TL;DR
getPredictionsearches the supplied data for a row whoseidequals the requested hash, including a string-normalized comparison. The local variable nameddecryptedis the matching object key; this function does not decrypt the row.- System, GPU, and GPU-brand fields are returned only when their candidate arrays contain exactly one value. A device can also be returned when several names share one common base name.
- The renderer reduces values from 18 named collector records into three JavaScript
Setobjects for GPU brand, GPU, and device.Setremoves duplicate values and the code then removesundefined; this aggregation does not preserve which collector supplied each surviving value. unknownHTML,pendingReview, crowd-blending grades, prediction markers, and a failed-prediction placeholder are different branches. None is a calibrated probability that the label is correct.
Contents
- A prediction label is an aggregation output
- Match the crowd row before reading its fields
- Candidate counts determine which values return
- Eighteen records feed three deduplicated sets
- Read device selection and score states separately
- Keep a source-aware comparison record
- Practical conclusion
A prediction label is an aggregation output
The word “prediction” can invite a stronger interpretation than the implementation supports. A reader may see a device name beside a score and assume the page identified a physical device or measured the probability that the name is correct. The pinned CreepJS implementation supports neither conclusion. It shows how particular values are selected and displayed from its inputs; the source does not provide a calibration study, a confidence interval, or a person-level identifier.
Two functions answer different questions. getPrediction looks up one crowd-data row and reduces four candidate arrays into fields named system, device, gpu, and gpuBrand. renderPrediction receives collector objects, combines selected fields across them, then constructs UI markup for the prediction area and individual signal rows. The lookup result and the rendered collector summary should not be treated as one opaque score.
The CreepJS signal-groups guide explains how to read per-surface labels, while the group cross-check analysis follows the ordered patterns behind botHash. Those topics do not trace the crowd-row match or the prediction panel's candidate and display branches.
The distinction matters when a report changes. A label can disappear because a candidate array now has two values, because collector values no longer share one base name, because a field was not supplied, or because the prediction rendering took its error branch. Those paths have different meanings. A missing single value is not evidence that the browser has no GPU, and an error placeholder is not evidence that every collector ran and disagreed.
The scope is pinned revision 10aa6724cd33a1015db1574211890518cd04f0cc of CreepJS src/prediction/index.ts, not a current observation of the live CreepJS service. The broader /docs/proof/fingerprinting-privacy-threat/ material describes browser fingerprinting signals at a general level but does not map this prediction module's branches.
Match the crowd row before reading its fields
getPrediction receives hash and data. It visits the keys of data, searches the array stored at each key, and compares each item's id with the requested hash. The comparison accepts either direct equality or equality after converting both values to strings. The key containing the matching item is stored in a local variable named decrypted.
That variable name is not evidence of cryptography. In this function, the relevant operation is an array search and an ID comparison. The source does not decrypt the item here. Describe the operation as “crowd-row lookup by hash,” not “decryption of the prediction.” A local variable name does not change the operation performed by the code.
When a matching item is found, the function copies four arrays into local variables: devices, systems, gpus, and gpuBrands. Missing arrays become empty arrays. The result object then selects a public-facing value from each candidate list. The lookup can therefore succeed while one or more returned fields remain undefined: finding a row and finding one unambiguous value are separate steps.
| Source value | Lookup action | Safe interpretation |
|---|---|---|
item.id | Compare with the supplied hash, also allowing string-normalized equality | A matching record was found in the supplied data object. |
item.systems | Use an empty list when absent; return a system only for a single candidate | Zero or multiple candidates do not produce one system value. |
item.gpus | Use an empty list when absent; return a GPU only for a single candidate | A missing returned GPU label is not a statement that the browser lacks a GPU. |
item.gpuBrands | Use an empty list when absent; return a brand only for a single candidate | The function does not rank several brands or report a confidence score for one. |
item.devices | Use an empty list when absent; return one candidate or a shared base name | A common text prefix is a selection rule, not independent hardware verification. |
The last row is especially easy to overread. The code comment gives Android 10 as a base name shared by Android 10 and Android 10 Blah Blah. That example documents a string-containment rule. It does not show that CreepJS confirmed the exact operating-system release on a live browser.
Candidate counts determine which values return
The return rules are deliberately asymmetric. Three fields require exactly one candidate, while device has a fallback for a shared base name. Reading the array length explains why a visible field can be absent even when the crowd row exists.
| Field | Candidate condition in the pinned code | Returned value when the condition fails |
|---|---|---|
system | systems.length == 1 | undefined |
gpu | gpus.length == 1 | undefined |
gpuBrand | gpuBrands.length == 1 | undefined |
device | One device, or one candidate name included in every candidate string | The common-base helper returns undefined when no single candidate is shared by all names. |
For systems, GPU models, and GPU brands, the implementation does not select the most frequent candidate or sort candidates by a score. It asks whether the array has one element. If the answer is no, the field is undefined. That is an explicit ambiguity boundary, not a hidden ranking.
For devices, getBaseDeviceName searches for a candidate string a such that every candidate string b includes a. If there is exactly one candidate, the code returns it directly before using the helper. If there are several candidates, a shared base can be returned; if none is common to every candidate, there is no single device name from this branch. The rule is lexical, so it should not be described as a hardware-distance calculation.
These source-derived examples show the branch, not a live report:
| Candidate list | Code-path result | What the result does not prove |
|---|---|---|
systems has one entry | That entry is returned as system | That the label identifies one person's operating system. |
gpus has two entries | No single gpu value is returned | That either GPU was absent from every collector. |
devices contains Android 10 and Android 10 Blah Blah | The shared Android 10 base can be returned | That a particular Android release was independently verified. |
devices contains unrelated names with no candidate shared by all | The base-name helper returns no value | That the browser is invalid or automated. |
An undefined result is a useful state to preserve. Replacing it with the first candidate, a majority guess, or an inferred device would invent a selection rule that this function does not implement. A comparison worksheet should record “no single value returned” separately from “the source field was not present.”
Eighteen records feed three deduplicated sets
The aggregate gpuSystem and gpuModelSystem fields do not describe WebGL context creation or extension support; the CreepJS WebGL report analysis examines renderer-side context and capability signals separately.
renderPrediction reads named signal objects and builds three Set instances. Each set receives one property from each of 18 records: jsRuntime, emojiSystem, domRectSystem, svgSystem, mimeTypesSystem, audioSystem, canvasSystem, canvasPaintSystem, canvasTextSystem, canvasEmojiSystem, textMetricsSystem, webglSystem, gpuSystem, gpuModelSystem, fontsSystem, voicesSystem, screenSystem, and deviceOfTimezone.
The fields are projected separately. One set collects gpuBrand, another collects gpu, and a third collects device. Each record is guarded with an empty-object fallback before its property is read. The code deletes undefined from each set after construction. Repeated equal values collapse to one entry because a JavaScript Set stores unique values.
| Aggregated set | Property read from each of the 18 records | Direct transformation visible in the source |
|---|---|---|
gpuBrands | .gpuBrand | Build a set, then delete undefined. |
gpus | .gpu | Build a set, then delete undefined. |
devices | .device | Build a set, then delete undefined. |
This operation reduces a collection of named fields to unique values, but the resulting set does not say which collector supplied each value. If five collectors contribute the same gpuBrand, the set contains one value, not a count of five. If two collectors disagree, the set can contain two values, but the display still does not say which API produced each one. The upstream module shows the per-source list in code; a report reader should keep that provenance in a separate record if it matters to a comparison.
The set is also not the same as the crowd-row candidate list. The candidate arrays used by getPrediction answer whether one value can be returned for a hash match. The collector sets used by renderPrediction combine values from signal objects for a displayed summary. Using one word such as “consensus” for both paths would hide that difference.
The 18 source fields can be grouped by the surface they name, but those labels do not imply equal quality or independence. Canvas image, canvas paint, canvas text, and canvas emoji are separate inputs in the code. So are gpuSystem and gpuModelSystem. Treating them as fully independent votes would add a statistical assumption absent from the implementation.
Read device selection and score states separately
Before the UI renders the device label, it computes a special Windows Remote Firefox Privacy selection. The source counts how many device strings contain “Windows” and divides that count by the number of device strings. Only when the ratio is greater than 0.5 does this branch try a fixed preference sequence: Windows 7, Windows 8, Windows 8.1, Windows 11, then Windows 10, checking the 64-bit spelling before the shorter spelling within each version. If the ratio is not above one half, this helper returns undefined.
The final deviceName is the RFP helper's result or, if that is falsy, the common-base device name. The comment says the sample data ignores Firefox RFP because it returns Windows 10, then explains the preferred ordering when several Windows versions are present. This is a source-specific display rule. It is not evidence that the overall population follows that ordering, and it must not be presented as a generally accurate OS detector.
crowdBlendingScoreGrade maps numeric thresholds to letters: A at 90 or above, B at 80, C at 70, D at 60, and F below 60. The markup displays a score percentage and grade only if crowdBlendingScore has JavaScript type number. A separate bot branch displays “locked” instead. The code therefore gives a grade mapping and UI condition; it does not establish what the score predicts or how well it is calibrated.
The module renders pendingReview as a separate note when the field is truthy. That label should be recorded independently from the score and prediction name. The pinned source does not justify replacing it with “failed,” “approved,” or “human review completed” unless another source defines that exact state.
Several display details can be recorded without turning them into confidence claims:
| Visible state | Source condition | Bounded reading |
|---|---|---|
** before the combined label | Both deviceName and gpuBrandName are truthy | The renderer has both display strings. The stars are not a probability. |
* before a single label | Exactly one of those display strings is truthy | Only one of those two display strings is available to this branch. |
| Blank prediction icons | Neither display string is truthy | The combined label is not displayed by this branch. |
pending review: … | pendingReview is truthy | The supplied field is being shown; the pinned source does not define a review outcome. |
crowd-blending score: … plus a grade | The score value has type number and the bot lock branch is false | The UI displays the supplied number with a grade band; accuracy is unknown. |
locked | The bot branch is truthy | The UI uses its locked branch; it does not expose the score there. |
The helper named hasValue needs an additional caution. It searches for the first property value whose type is not undefined; callers negate the returned value to choose unknownHTML. A defined value of false or 0 is itself falsy, so it can enter the unknown-rendering branch. That source behavior means an “unknown” label is not reliable proof that the collector did not run. It may reflect how this helper treats a falsy value. The claim is limited to the pinned implementation and should be checked again if a later revision changes the helper.
predictionErrorPatch is yet another branch. When called with an error and the prediction element exists, it renders “Prediction Failed” and blank icons for many rows. That placeholder describes the error UI, not the candidate-count rule and not a successful run with every signal missing. A record should keep the error branch separate from a normal report whose individual fields render unknown.
Keep a source-aware comparison record
When two reports differ, record the question and source revision before interpreting the labels. The following fields preserve distinctions the implementation makes without copying a raw hash or collecting a full browser identifier:
| Record field | What to preserve | Why it matters |
|---|---|---|
| Source | Repository, commit SHA, and file path | A later revision can change matching or rendering behavior. |
| Lookup state | Whether the supplied data contained a row matching the hash; do not store the hash itself | Row presence and field uniqueness are separate outcomes. |
| Candidate cardinality | Counts for systems, devices, GPUs, and GPU brands | The return rules depend on array length and a shared device base. |
| Aggregation state | Which named collector fields were present and whether each set had zero, one, or several unique values | A deduplicated set does not retain its original contributor list. |
| Rendering state | Prediction marker, unknown branch, pendingReview, numeric score display, locked branch, or error placeholder | Similar-looking empty areas can come from different paths. |
| Limits | Whether the note is a pinned-source reading, a current page observation, or another explicitly bounded source | Source code alone cannot establish current service output or accuracy. |
Do not store the supplied hash just because the lookup uses it. The report question can usually be answered with a Boolean “matching row present” and candidate counts. Retaining a raw identifier would increase the data held without improving the branch explanation.
Use the worksheet to compare public source behavior. It is not a reproduction protocol for the CreepJS backend, a benchmark of CreepJS accuracy, or a method for changing a third-party score. Those claims would need evidence beyond this source file.
Sources
- CreepJS prediction module at commit
10aa6724cd33a1015db1574211890518cd04f0cc: lookup, candidate reduction, collector aggregation, unknown rendering, score display, and error placeholder. - BotBrowser: Fingerprinting and privacy threats: the broader signal and privacy context, not the CreepJS prediction implementation.
- BotBrowser: Automation consistency: how browser-visible configuration surfaces should be considered together; it does not establish a third-party detector's decision.
Practical conclusion
Read the CreepJS prediction panel as a set of source-dependent output branches. First establish whether the crowd data contains a matching row, then preserve candidate counts, unique collector values, and display state separately. A single returned value, a shared device base, a score grade, a pending-review note, and an unknown placeholder are not interchangeable evidence. The pinned code explains how these labels are assembled; it does not turn them into a verified device identity or a calibrated bot judgment.
This article is part of the guide: What Is a Browser Fingerprint?
Related Articles
CreepJS Math Signals: Read the Source Fields
FingerprintCreepJS WebGL Report: Contexts and Capability Signals
FingerprintBrowserLeaks WebGL Report: Collection and Missing States
FingerprintCreepJS Emoji Metrics: What Sets and Sums Reveal
FingerprintCreepJS Audio Reports: Rendering and Missing Values
FingerprintCreepJS Font Tables: 51 Names and 14 Possible Returns
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.