Skip to content

Security: azurebeard/harbinger

SECURITY.md

Security Policy

Supported versions

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.

Security model

Harbinger is a defensive observability tool. Its security model is built on five properties, each implemented (not aspirational):

Event content stays local

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.

Outbound network is minimal and disable-able

Harbinger makes only two kinds of outbound HTTPS call, both for cost projections:

  1. prices.azure.com — Azure Retail Prices API. Disabled by pricing.offline: true or the HARBINGER_PRICING_OFFLINE=1 environment variable.
  2. api.frankfurter.app — Frankfurter (ECB) FX rates, optional. Disabled by pricing.fx_offline: true. The endpoint is configurable via pricing.fx_api_url for 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.

Unprivileged by default

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.

Bounded resource consumption

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.

Hardened systemd deployment

The shipped systemd unit (scripts/harbinger.service) enables:

  • NoNewPrivileges=true
  • ProtectSystem=strict
  • ProtectHome=true
  • PrivateTmp=true
  • PrivateDevices=true
  • ProtectKernelTunables=true
  • ProtectKernelModules=true
  • ProtectControlGroups=true
  • RestrictSUIDSGID=true
  • LockPersonality=true
  • MemoryDenyWriteExecute=true
  • RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
  • Read-write paths restricted to /opt/harbinger/data and /opt/harbinger/reports

Threat model

In scope

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.

Out of scope

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).

Disclosure process

If you find a security issue in Harbinger, please do not open a public GitHub issue.

Report it privately by:

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.

What to expect

  • 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.

Out-of-scope reports

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.

Cryptography

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.com and api.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).

Dependencies

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.

Reproducible audit

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')"

Acknowledgements

Thanks to everyone reporting issues responsibly. We treat reports seriously and aim to ship fixes promptly.

There aren't any published security advisories