The beacon works on a host this Loghound installation has no access log for. Paste the same snippet. Nothing else is installed there: no agent, no log shipping, no second Loghound.
<script src="https://loghound.example.com/b.js?v=1" defer></script>
This is how a hosted search page at search.example.com, running on a different machine from the one Loghound is on, appears in the same panel as everything else.
Figure 1 — the two modes. You do not declare which applies. Configuration says what somebody intended; evidence says what is true, and only the second is safe to act on.
Whether a host is already covered is derived from whether this installation has actually ingested log lines for that hostname in the last seven days. Asking you to state the mode in a second place would guarantee the two drifted the first time a site moved.
'beacon' => [
'allowed_hosts' => ['search.example.com', 'shop.example.com'],
],Empty by default, so an installation that says nothing behaves exactly as it did before this existed. A beacon from a hostname that is not on the list keeps the ordinary behaviour precisely: a fresh provisional session id, a staged row, merge only, no session created, and no hostname and no search term recorded anywhere.
Being on the list buys three things and nothing else:
- Its beacons may create a session when no log source covers the host.
- Its reported hostname is recorded, so it sits in the Virtual host dimension beside hosts that came from a log line and filters identically.
- Search terms from it are kept.
How a listed host is told apart from the open internet
Three facts have to agree, and the collector checks all three:
- What the page says it is. Entirely attacker-chosen, so on its own it is worth nothing.
- What the browser says it is — the
Originheader. A user agent sets this on every cross-origin request and page script cannot change it: a page on another domain cannot make a browser send somebody else’s origin. This is the fact that makes the first one worth reading. They are required to agree, and a disagreement is refused rather than reconciled. A request with noOriginis refused for this purpose too, because the beacon is cross-origin by construction and a browser running it always sends one. - What you said — the hostname is on the list.
Origin binds browsers. Anything that is not a browser — curl, a script, a load generator — sends whatever headers it is told to. So somebody who knows a hostname is on your list can fabricate sessions attributed to it. They can make up dwell times, invent visitors, and report a human as headless automation.
What bounds that:
- It reaches only the hostnames you listed. It cannot touch another host’s data and it cannot invent a hostname you did not name.
- It reads nothing. The collector answers
204with an empty body to every request, success or failure, so it is not an oracle for anything. - It cannot reach the log-backed planes. A forged beacon cannot create a log line, a status code, a byte count or a reverse DNS result, and it cannot alter a session that has those — the merge never overwrites a fact the log supplied.
- Rate limits apply per address, per session and per hostname, so a listed host is not a way around the limiter.
This is the same exposure every client-side analytics product carries — they all accept a beacon whose only claim to authenticity is a site identifier visible in the page source. Loghound’s answer is not to pretend the problem is solved. It is to mark what the evidence actually is.
A standalone session has no transport plane: no status code, no bytes, no server timing, no conditional-request behaviour, no inter-request rhythm, no reverse DNS from a log line. Two guarantees, and both are needed.
- On the document. None of those fields is written. Not zero — absent. A zero in any of them is a measurement, and an asset ratio of zero reads as “this client fetched only markup”, which is a bot signal, about a visitor whose asset fetching was never observable from here.
- In the ruleset. Five rules are silenced: no JS on HTML, no sub-resources, no conditional requests, single page under ten seconds, and periodic timing. Without that, the absent counters would be read back as the zeros PHP hands out — and a zero in those fields is not a neutral value, it is an accusation. The silencing makes that a guarantee rather than a coincidence: the no-sub-resources rule, for instance, also requires that an HTML 200 was served, which a beacon-only session has no record of, so it could not fire anyway — but relying on a second rule’s precondition to save you is exactly the kind of thing that stops being true after a refactor. The no-interaction rule is deliberately not silenced, because it reads the beacon’s own interaction count, which a beacon-only session has.
The session carries a planes value — log only, log and beacon, or beacon only — as a facetable dimension, plus a reason code worth no points, present so the verdict explains itself. The vocabulary page lists all three values.
The field is newer than the product, so every session indexed before it existed has no value for it — and every one of those came from a log. The negation includes that history; the positive form would silently exclude all of it. The None of operator is what spells it.
You add the vhost, or move the site onto the Loghound machine. From that moment both planes describe the same visit, and this produces one session, not two:
- The coverage check flips as soon as hits appear for that hostname, so nothing new is promoted to standalone.
- Beacons for a visit already in flight are claimed by the log-backed session through the client key — the mechanism that has always existed for a beacon arriving before its log line.
- Any provisional standalone document already published for those rows is deleted by id before the run indexes anything. Deleting first means a failure leaves a gap, never a double count.
History is untouched. A session that really was beacon-only when it happened stays beacon-only, because a reconfiguration today is not evidence about last week.