Loghound parses hostile input by definition. That is not a caveat, it is the job description.
Log lines contain attacker-controlled paths, User-Agents and referrers. The beacon endpoint is a public, unauthenticated write path. Enrichment responses come from third parties. None of it is ever trusted.
cip@opensolr.com with LOGHOUND SECURITY in the subject line
Please do not open a public issue for a security problem. Useful things to include, in rough order of usefulness: the affected file and line, a minimal reproduction, the version or commit, and what an attacker gets out of it. A proof of concept is welcome and never required — a clear description of the flaw is worth more than a weaponised exploit.
What to expect: acknowledgement within a few days. This is a small project maintained by one person; there is no bug bounty and no service-level agreement, and claiming one would be dishonest. There is no embargo requirement — publish when you think it is right — but a little warning is appreciated so a fix can land alongside the disclosure. Credit in the changelog unless you would rather not be named.
| Adversary | Capability | What they want |
|---|---|---|
| Anyone who can send a request to a monitored site | Full control of the request path, query string, User-Agent, referrer and every other header — all of which end up as bytes in a log line Loghound parses | Stored scripting in the panel, query injection, path traversal, exhausting the ingest daemon |
| Anyone on the internet | Can post to the beacon collector, which is public and unauthenticated by design | Forge sessions, poison timing metrics, exhaust the staging table, get code execution on a public write path |
| A local unprivileged user on the same box | Can read world-readable files | The API key, the beacon signing secret, the traffic data |
| Another site on the same shared host | Executes as the shared web server user | The same secrets, via a readable configuration file |
| A compromised panel session | Authenticated, can drive every panel action | Pivot from reading the dashboard to reading arbitrary files or running arbitrary queries |
| A compromised enrichment provider | Controls the response body | Injection through a field the operator assumes is trustworthy |
Explicitly out of scope: an attacker with root on the box; a compromised Solr; denial of service by sheer traffic volume against the origin web server; and the security of your own web server, PHP build and TLS configuration.
Figure 1 — the trust boundary. The important consequence: there is no point in the pipeline where a log-derived value becomes safe. It is escaped at the sink, every time, in the encoding that sink requires.
Output escaping — contextual, never generic
There is exactly one implementation of each escaper. HTML text or attribute, script context, and URL attributes each get their own, and the URL one is a scheme allowlist plus a control-character check — because referrer values from the wire routinely contain script-URL payloads. Client-side the DOM is built from text nodes; the one place markup is built from data is the chart tooltips, because the charting library renders a formatter’s return value as HTML, and every interpolated value in every formatter goes through the escaper. The content policy would stop an injected payload executing, but a defence resting entirely on one header is not a defence — and a network operator name is chosen by whoever holds the address block.
Query injection
- User text is never spliced into a query, a filter, a sort or a field list. It goes in as a bound parameter.
- Row and offset counts are clamped. Deep paging is both a denial-of-service vector and a useless interface.
- Field names used for sorting and faceting must pass a strict pattern; core names must too.
- Caller-supplied shard, handler, writer, streaming and distribution parameters are never accepted.
- There is no raw Solr passthrough, and there will not be one. The panel talks to Solr only through one class, which builds every request server-side.
The beacon collector — the public write path
- POST only, with a hard payload cap, rejected before it is parsed if larger.
- A signed token bound to the origin it was issued to, so one minted for one site is refused when presented from another. That binding is what makes a wildcard allow-origin safe here.
- The first call never returns an existing session id. It mints a fresh provisional one and never creates a session row. Returning the real id would let any page on the internet make a simple cross-origin post from a visitor’s browser, read the credentials off the response headers, and then submit whatever it liked about that visitor’s real session — in a bot-detection product, “report this human as headless”.
- Timing sanity is checked against the token’s own issue time, and an impossible claim is recorded as a signal rather than discarded.
- Client-supplied address, User-Agent, host and referrer are never trusted; they are read from the connection. The one exception is the hostname, and that page is explicit about what it does and does not protect.
- Rate limited per address, per session and per hostname, with the address hashed in the limiter so the table cannot quietly undo the configured privacy mode.
- It always responds with an empty success, so it never leaks whether a token was valid.
- It does not touch Solr. It writes one row locally; the scorer merges it later. That keeps the public path cheap and keeps the Solr credentials entirely out of the most exposed file in the project.
Filesystem, regular expressions and proxies
- Every configured log path is resolved and checked against the allowed roots, and the comparison is against the resolved root with a trailing separator, so a similarly named sibling directory cannot match and a symlink cannot escape.
- Globs are re-validated at read time, not only at save time. Validating only the pattern would leave a hole.
- Source files are opened read-only and never any other mode, and a test asserts a source file’s size is unchanged after a full read.
- A custom log pattern is untrusted code that will run against every line of a firehose, so it is refused at save time — the only moment a human is present to fix it — with a length cap, a compile under a reduced backtrack limit against a subject engineered to expose blowup, and a wall-clock budget.
- The forwarded-address header is honoured only when the immediate peer is a proxy you configured, and then the right-most hop that is not itself trusted. Getting this wrong lets any visitor forge their own source address and poisons every address-based signal.
Things worth doing that the installer cannot decide for you:
- Do not expose the panel to the internet if you do not need to. It is the highest-value target in the whole system: it contains every visitor, path and address on your site.
- Use a real TLS certificate. The installer refuses to deploy one that does not chain to a trusted root, but it will let you choose self-signed. Do not leave it that way.
- Add an address allowlist in the vhost if your operators come from known addresses. Two lines, and it removes the panel from the internet’s attack surface entirely.
- Set the address mode deliberately — privacy.
- Set a retention window you can justify and confirm the timer is enabled — retention.
- Configure the trusted proxy list if anything sits in front of your web server, or every address-based signal is worthless.
- Keep the raw line copy only if you need retroactive re-scoring. It roughly doubles the index and stores the complete original line, including any secret that ended up in a query string.
- Never point Loghound at its own vhost’s access log.
- Check the configuration directory is not readable by the web server user. The installer verifies it; verify it again after any change:
sudo -u www-data test -r <prefix>/config && echo EXPOSED - Rotate the beacon secret if you suspect it leaked — re-run the wizard and accept the rotation. It costs one heartbeat interval of beacon data and nothing else.
- Watch the parse and Solr error counters. A sudden climb in parse errors is usually a format change, but it is also what a log-injection attempt looks like.
| Secret | If it leaks |
|---|---|
| The Opensolr API key | Full control of every index on your account |
| The beacon signing secret | Anyone can forge beacon payloads and poison your timing metrics and verdicts |
| The address salt | Only sensitive in hashed mode — with it, hashed addresses can be brute-forced back |
Exactly one reason: if it is ever served by a misconfigured web server it executes and returns nothing, instead of printing the secrets as text. It also opens with a guard that exits unless the application defined its marker. It is written atomically so a crash mid-write cannot leave a half-parsed configuration, it lives at a restrictive mode in a directory the web server user cannot reach, and both vhost examples deny the sensitive directories twice over — once by directory and once by URL path.
Because a security document that only lists strengths is marketing.
- The sign-in lockout is per address, and only per address. That is deliberate rather than an oversight — the reasoning is here — but the cost is that a distributed guesser is not slowed by it at all.
- There is no audit log of panel actions. No record of who looked at what.
- The raw line copy stores complete log lines, including any credential that ended up in a query string on your site. That is your data and your risk; the switch exists so you can decide.
- Enrichment responses are parsed, not verified. A compromised provider could inject a value. It is escaped at every output point, but it can still make a field say something untrue.
- The JA4 field is defined and never populated.
- The beacon can be blocked, and a token can be replayed within its window. The signature proves the token was issued by us for that session and that origin — not that this particular client is the one it was issued to. What that buys an attacker is the ability to lie about dwell time for their own session, which is why forged timings are recorded as a signal rather than trusted or dropped.
- Rollup documents are currently counted as sessions in the panel’s totals. Not a security issue; listed here because it is a wrong number the product presents as evidence.
- No formal third-party security audit has been performed. If you do one, the results are very welcome.