Harbinger follows Semantic Versioning. Security fixes are backported to the most recent minor release.
| Version | Supported |
|---|---|
| 1.0.x | ✅ |
| < 1.0 | Not supported. Pre-1.0 releases were internal-only; v1.0.0 is the first public release. |
Harbinger is a defensive observability tool. Its security model is built on five properties, each implemented (not aspirational):
Classification runs against a local Ollama HTTP endpoint by default (http://localhost:11434). Event content is not sent to any cloud LLM, telemetry endpoint, or third-party service. There is no code path that would do so.
Harbinger makes only two kinds of outbound HTTPS call, both for cost projections:
prices.azure.com— Azure Retail Prices API. Disabled bypricing.offline: trueor theHARBINGER_PRICING_OFFLINE=1environment variable.api.frankfurter.app— Frankfurter (ECB) FX rates, optional. Disabled bypricing.fx_offline: true. The endpoint is configurable viapricing.fx_api_urlfor operators who want to mirror Frankfurter internally for air-gapped environments.
Neither endpoint is authenticated. Neither endpoint receives event content. The full list of network destinations is enumerated in docs/architecture.md § 4.
The listener binds to 127.0.0.1:1514 by default — unprivileged port, loopback only. Operators set 0.0.0.0 deliberately and pair the change with firewall rules. The systemd unit runs as a dedicated unprivileged user.
| Resource | Bound | Mechanism |
|---|---|---|
| Memory | Bounded queues (10,000 events) | Drop on overflow with counter; never grows unboundedly. |
| Memory | LRU caches | cache_size (classifier), max_pending_multiline_sources (listener). |
| CPU | Token-bucket rate limit | rate_limit_per_source per source IP. |
| Disk | Percentage-of-disk retention | max_disk_percent, clamped between min_size_bytes and max_size_bytes. |
| Network | Two destinations, both disable-able | See above. |
A malicious or misconfigured upstream sender cannot exhaust Harbinger's resources by flooding it.
The shipped systemd unit (scripts/harbinger.service) enables:
NoNewPrivileges=trueProtectSystem=strictProtectHome=truePrivateTmp=truePrivateDevices=trueProtectKernelTunables=trueProtectKernelModules=trueProtectControlGroups=trueRestrictSUIDSGID=trueLockPersonality=trueMemoryDenyWriteExecute=trueRestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX- Read-write paths restricted to
/opt/harbinger/dataand/opt/harbinger/reports
| Threat | Mitigation |
|---|---|
| Malformed syslog payload crashes the listener | Parser is best-effort, never raises; bad input gets rfc="unknown" and the original line is preserved. |
| Oversized message drains memory | 8 KB body limit (anomaly-flagged), 64 KB TCP line limit (line skipped, connection kept). |
| Single bad sender starves the others | Per-source token-bucket rate limit. |
| TCP SYN flood / connection exhaustion | TCP connection cap (default 256), excess connections accepted and immediately closed. |
| Hung Ollama backend blocks the pipeline | Per-event timeout (default 30 s) with Classification.unknown() fallthrough. |
| Disk fill from sustained traffic | Retention policy: oldest windows evicted, VACUUM runs. |
| Classifier hallucinates a "known" label on truly unknown input | Confidence floor (default 0.6) downgrades to unknown; original confidence preserved so the gap is visible. |
| Sensitive data leaking to external services | No cloud path exists for event content. Pricing/FX endpoints receive no event content. Both can be disabled. |
| Stale dependency CVEs | Release CI gated on pip-audit. Every tag-push runs pip-audit against the runtime dependency closure; any direct or transitive CVE in a Harbinger dep blocks the release. |
| Threat | Why not in scope |
|---|---|
| Authenticating syslog senders | The operator's existing syslog relay handles this (TLS termination, source allowlists, mutual auth). Harbinger receives events post-authentication. |
| Confidentiality of the Ollama loopback channel | Loopback HTTP is the only safe plaintext target. Non-loopback endpoints SHOULD use HTTPS or an SSH-tunnelled loopback (documented inline in the example config). |
| Malicious operators | Root on the host already wins. Harbinger is not designed to defend against an attacker who already has its execution privileges. |
| Tampering with the SQLite database | The database is local-only and not transmitted. An attacker with write access to data/ can modify rollups, but they already have shell access to the host. |
| Hosted attacker against the pricing/FX endpoints | Public endpoints. No authentication, no credentials at risk. Worst case: stale pricing in the report (the operator sees the as_of date in the output). |
If you find a security issue in Harbinger, please do not open a public GitHub issue.
Report it privately by:
- GitHub Security Advisories — Report a vulnerability. This is the preferred channel.
Please include:
- A description of the issue.
- Steps to reproduce (or a proof-of-concept).
- The affected version(s) of Harbinger.
- Your assessment of impact.
- Whether you have a suggested fix.
- Acknowledgement within 5 business days.
- Triage within 10 business days — we'll either confirm the issue, ask for more information, or explain why we believe the report does not constitute a vulnerability.
- Fix for confirmed high-severity issues within 30 days; medium and low severity follow a slower cadence aligned with the regular release schedule.
- Coordinated disclosure. We'll work with you on the timing of public disclosure. Default embargo is 90 days from confirmation or until the fix is released, whichever is sooner.
- Credit in the release notes and the security advisory, with your permission.
The following are not considered vulnerabilities and will be closed:
- Stale rates from the pricing or FX cache (operator's responsibility to refresh).
- Bugs that require root on the host running Harbinger.
- Classifier output being wrong on a specific log shape (this is a tuning issue, not a security issue — file a GitHub issue instead).
- Behaviour of the Ollama backend itself (report upstream to https://github.com/ollama/ollama).
- Findings from automated scanners with no demonstration of practical impact.
Harbinger does no application-level cryptography. It relies on:
- The operating system's transport layer for syslog (UDP/TCP).
- httpx's TLS implementation for outbound HTTPS calls to
prices.azure.comandapi.frankfurter.app. - The blake2b implementation in Python's standard library for content-addressed classifier caching (not used for security purposes — collision resistance is required for cache correctness, not authentication).
Runtime dependencies are pinned to minimum versions chosen for security. Notably:
urllib3 >= 2.7.0(closes PYSEC-2026-141 / -142)idna >= 3.15(closes PYSEC-2026-215)
Both are transitive via httpx. The defensive pins ensure pip-audit runs clean on every fresh install regardless of whether httpx has re-pinned upstream.
To verify Harbinger's security posture on your own:
# Run the dependency audit (same gate that runs in release CI)
pip install pip-audit
pip-audit -e .
# Inspect outbound network calls in the source
grep -rn 'httpx\|http://\|https://' harbinger/
# Validate the JSON schema matches a real report
python -c "import jsonschema, json
schema = json.load(open('harbinger/schema/profile.schema.json'))
sample = json.load(open('docs/sample-profile.json'))
jsonschema.validate(sample, schema)
print('valid')"Thanks to everyone reporting issues responsibly. We treat reports seriously and aim to ship fixes promptly.