Back to Knowledge Hub
Fingerprint

Cloudflare Bot Score Evidence and Logs

Interpret Cloudflare Bot Score, Bot Management variables, and Logpush records without treating a score, challenge, or HTTP status as identity proof.

BotBrowser Team

Documentation

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.

Cloudflare Bot Score is a request-level signal, not a transparent identity test. Cloudflare publicly documents a 1-99 scale and several variables, while the inputs and weights behind a decision are not a public field-by-field explanation. This source-based article reports no BotBrowser or Cloudflare zone run.

Flow from Cloudflare score fields to separate challenge and HTTP outcome records

TL;DR

  • Cloudflare documents BotScore from 1 to 99. A low score means Cloudflare considers automation more likely, while a high score means it considers a human request more likely; neither is a person-level verdict.
  • A documented score of 0 means Bot Management did not evaluate the request; it is not a safe or human result.
  • cf.bot_management.score, BotScoreSrc, and BotTags describe documented request or event fields. Availability depends on the product, plan, dataset, and record context.
  • A challenge action is a security workflow outcome. An HTTP status is a response outcome. Keep both beside the score instead of treating either as the reason or the identity of the requester.
  • Log only the fields needed for a bounded support or release decision. Do not infer private feature weights, collect a fingerprint for a person, or claim that a challenge proves a human passed it.

Contents

Read the public fields

Cloudflare's Bot Score reference defines a score from 1 to 99. Its examples say that 1 means Cloudflare is quite certain a request was automated and 99 means it is quite certain the request came from a human. That wording describes Cloudflare's likelihood assessment for a request; it does not identify a person, reveal a complete model explanation, or establish intent.

The Bot Management variables page names request variables such as cf.bot_management.score, cf.bot_management.verified_bot, and cf.bot_management.static_resource. The HTTP requests Logpush dataset lists fields including BotScore, BotScoreSrc, and BotTags. A field name is not a promise that every zone, plan, event, or historical row contains a value. Record the dataset and availability state with the field.

BotScoreSrc is useful source metadata when the dataset supplies it, but it is not a disclosure of every detector input. Cloudflare's bot detection engines documentation describes engine categories at a public level. It does not publish a complete feature-weight table that lets a reader reconstruct a score from browser properties. That is why a log review should quote the public field definition, not invent a weighting explanation.

The public field list also needs a time boundary. A score belongs to a request record at a particular point in a particular processing path. It should not be copied into a profile record and reused as if it were a stable browser attribute. If a team compares two requests, keep the route, timestamp, dataset, plan context, and redaction policy beside both rows. A difference can then be routed to the Cloudflare configuration owner, the application owner, or the evidence owner without claiming that the browser itself changed.

Do not use a field name as a substitute for product entitlement. A variable may be documented while being unavailable to a zone, plan, event type, or selected Logpush dataset. The correct response to a missing value is to check the documented availability and export schema. It is not to substitute a guessed score, treat null as a low score, or infer that a detector had no input. This distinction makes a support record auditable when an account changes plan or a schema is revised.

The BotTags collection needs the same care. A tag is useful only with its documented meaning and the record that carried it. Do not treat an empty collection as “trusted,” and do not treat one tag as a complete explanation for a challenge. Preserve the raw tag spelling in a redacted, access-controlled record, then write the human-readable interpretation in a separate field. This keeps a future schema change from silently changing the meaning of an old conclusion.

The variables also represent different kinds of information. cf.bot_management.score is an integer likelihood signal. cf.bot_management.verified_bot is a boolean about whether a request originates from a Cloudflare-allowed bot; it is not a “verified human” bit. cf.bot_management.static_resource identifies configured static-resource extensions and can be relevant to requests such as email image retrieval. Do not combine these into one trusted/untrusted label. A verified bot may be automated by definition, while a static-resource match says something about the requested resource class, not who initiated the request.

These distinctions matter when rules consume the values. A rule that exempts verified_bot or ignores static_resource has a policy purpose and scope chosen by the zone owner. Its presence does not imply that every request sharing the same score should receive the same action. Keep the evaluated variable, the rule owner, and the resulting action in separate evidence fields. The public variable reference documents the meaning of each value; it does not tell a reader which private zone rule ran in a particular case.

For context on the privacy risk of combining browser-visible signals, compare the signal groups and observation limits article and the V8Log runtime evidence guide. Both help keep a compatibility record separate from a person-level profile.

Build a bounded evidence record

Use a stable request identifier and record only the decision inputs that an owner can act on. Keep the source URL and schema revision so a later reader can distinguish a missing field from a changed dataset. This is a documentation fixture, not observed Cloudflare output:

{
  "recordType": "cloudflare-bot-evidence",
  "schema": "public-fields-only-v1",
  "requestId": "redacted-request-01",
  "testedOn": "2026-10-29",
  "dataset": "zone/http_requests",
  "fields": {
    "BotScore": null,
    "BotScoreSrc": null,
    "BotTags": [],
    "challengeAction": "not-recorded",
    "httpStatus": null
  },
  "availability": "source-based-fixture",
  "interpretation": "No runtime result; consult the cited field documentation."
}

The null values and source-based-fixture label are deliberate. They prevent a template from being mistaken for a zone export. In a real, authorized support record, replace them with the returned values, preserve the exact field names, and redact URLs, cookies, request bodies, account identifiers, and full headers unless an owner has documented a need. A retention date and a named owning system are more useful than a large undifferentiated request dump.

An evidence owner should also record how the row was obtained. A Logpush export, a dashboard view, an application response, and a challenge event are different collection paths. Use a short collectionPath value in the surrounding ticket or inventory, not a copied full request. The path tells a reviewer which schema and access policy to check. It does not provide permission to join records across accounts or retain traffic longer than the incident requires.

Redaction should be deterministic. Replace a request identifier with a stable local token, remove query values that can contain user data, and keep only the route class needed to reproduce the decision. Avoid retaining a full header digest as a compromise: a stable digest can still become a cross-context identifier. The W3C guidance is relevant here because privacy risk comes from joining signals, not only from storing a single obvious identifier.

For a release review, define the expected state before looking at a result. For example, the reviewer may require that the selected dataset contains BotScoreSrc, that a challenge record has a matching timestamp, and that the application status is retained separately. A missing requirement should produce unavailable or needs-owner-check, not a successful conclusion. This turns the evidence record into a decision aid rather than a retroactive justification.

If a field is absent, distinguish not-enabled, not-in-dataset, null, and not-requested. Those states answer different operational questions. Do not turn absence into a low score or into evidence that a detector saw nothing. The W3C fingerprinting guidance explains why joining many signals can increase privacy exposure even when each field looks harmless.

Separate score, challenge, and HTTP outcome

Use this decision table when a support or release review receives several records. The choice rule is to assign each observation to its owning layer before asking what changed.

ObservationIt can supportIt cannot establishNext check
BotScore 1-99Cloudflare's documented likelihood signal for that requestA person's identity, feature weights, intent, or a universal bot verdictConfirm plan, dataset, timestamp, and BotScoreSrc
BotScoreSrc or BotTagsPublic source or tag metadata when present in the datasetThe complete detector trace or the reason for every tagRead the matching variable and Logpush schema version
challenge actionThat a security workflow was selected or exposedThat the requester was malicious, human, or successfully verifiedRecord challenge result and application policy separately
HTTP 200, 403, or other statusThe response outcome observed by the client or logWhy the response happened, who initiated it, or whether a person passed a challengeJoin timestamp, route, and action records under an owner-controlled ID

The rows are intentionally non-transitive. A high score followed by 403 does not prove a human was blocked. A low score followed by 200 does not prove automation was accepted. A challenge followed by 200 does not prove a person solved it, because an application can return a response for many policy and flow reasons. Preserve the sequence and the owner of each record instead of collapsing it into a single label.

The sequence matters even when the timestamps are close. A challenge can be selected before an application decides which response body to send, and a response can be generated by an upstream policy rather than by the page that a person sees. If the system cannot correlate the records with an owner-controlled ID, leave the join unmade. “Related by time” is weaker than “joined by a documented event key,” and the weaker relationship should be labeled as such.

HTTP status also has an application meaning that is independent of the Cloudflare score. A 403 can represent a policy rule, a missing permission, an origin response, or a challenge path. A 200 can contain a retry page, an interstitial, or an application-level error. The status row should therefore retain the route class and response owner, while the score row retains its Cloudflare field source. Neither row should be rewritten as “bot” or “human.”

When a challenge is part of the workflow, record the action name and the resulting application state separately. Do not store a person's challenge answers or use a successful browser navigation as a human label. A support team can ask whether the expected path completed, whether the response was delivered, and whether the user-facing fallback worked. Those are operational questions with bounded evidence. They do not require a claim about who operated the browser.

State the inference limits

Cloudflare's public documentation supports statements about the meaning and scope of named fields. It does not support claims about hidden feature weights, a detector's full input vector, or the identity of a requester. MDN's fingerprinting glossary describes fingerprinting as combining browser and device information to distinguish users or devices; that is precisely why a score log should not become an unnecessary cross-context fingerprint.

Keep score, challengeAction, and httpStatus as separate columns in an incident worksheet. If a reviewer needs a conclusion, write the bounded fact first: “the record contained a Cloudflare score of X in dataset Y at time Z.” Then write the operational action: “the application returned status Q after action R.” Avoid “Cloudflare knew this was a bot” or “the user proved they were human.” Those sentences exceed the public evidence.

The same boundary applies to browser fingerprinting. A request can carry browser-visible values, but a Cloudflare score is not a license to collect every available value or to join values to a person. Follow the data-minimization principle in the W3C guidance: identify the purpose, collect the smallest useful set, restrict access, and delete the record when the support or release decision ends. A score plus a full fingerprint is not a more rigorous explanation of the score.

Do not present the public engine categories as a causal trace. “Heuristics,” “machine learning,” or another documented category can describe a public engine concept, but it does not say which browser property produced a particular score. A support note can cite the category page and still say “the private detector input is unknown.” That sentence is more accurate than an invented ranking of signals and remains valid if Cloudflare changes its implementation.

The Logpush dataset page is a schema reference, not a guarantee that a configured job exports every listed field. Check the job's selected dataset, account or zone scope, and field selection before diagnosing a missing column. If a field is absent, first ask whether it was selected and whether the relevant record type supports it. A downstream parser should tolerate an absent or null field without manufacturing a default. Otherwise a schema mismatch can be misreported as a model decision.

If a reader needs to compare releases, compare the record contract first. Check whether the same dataset, plan, route class, timestamp precision, and redaction policy were used. Only then compare the returned fields. A changed schema can look like a changed score source, and a changed route can look like a policy decision. The contract makes those alternatives visible without requiring access to private Cloudflare internals.

Retention is part of the inference boundary. Keep a raw export only for the documented support period, and keep a smaller conclusion after deletion if policy permits. The conclusion should contain the bounded fields, source links, owner, and decision, not a new profile built from the old request. This makes later review possible without turning a one-time Cloudflare signal into a durable identity record.

This topic adds an interpretation method to the matching BotBrowser documentation, but it does not add Cloudflare access. BotBrowser cannot expose Cloudflare's private model inputs or weights, force a challenge outcome, prove human presence, or convert an HTTP status into an identity statement. Repeat the source review when Cloudflare changes field documentation, plan availability, or dataset schema.

BotBrowser supports automating an authorized page workflow and observing browser-visible state, as described in its automation consistency documentation; it cannot read a Cloudflare zone or produce BotScore, BotScoreSrc, and BotTags, which must come from Cloudflare's configured product and dataset.

Sources

Practical conclusion

Use Cloudflare's score and public fields as narrowly defined request evidence. Keep challenge actions and HTTP outcomes in their own records, document availability and timestamps, and stop before inferring feature weights, identity, or certain human intent. The next safe action is to verify the exact dataset and plan against Cloudflare's current field references before adding a field to a production support schema.

#Cloudflare#Bot-Management#Logs#Evidence#Privacy

This article is part of the guide: What Is a Browser Fingerprint?

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.