Skip to main content

Klaxon

The alarm tells you that. Klaxon tells you what.

Klaxon is a read-only proxy in front of your Wazuh 5 indexer. A language model asks questions in plain language; Klaxon queries the indexer, reads the schema, tests decoders, and reports what it actually found — including when it found nothing, and why. Works with Claude Desktop, Claude Code, local models through Ollama, and Open WebUI.

Every query runs through Klaxon, and the response is masked before it reaches an external LLM: a value under a configured field (user.name, source.ip, …) always becomes the same deterministic token ([USER_…], [IP_…]), aggregation keys included. This is pseudonymization, not anonymization — tokens are deterministic and reversible by anyone holding the salt (see LLM-safety guarantees).


Quick start

Requirements

  • Wazuh 5.x indexer reachable over HTTPS (search/schema also work against 4.x)
  • Python 3.11+ or Docker
  • an MCP client — Claude Desktop, Claude Code, ollmcp, Open WebUI 0.6.31+

1. Install

python3 -m venv .venv
.venv/bin/pip install klaxon-mcp

Or build the Docker image (klaxon-mcp is the entry point):

docker build -t klaxon-mcp .

2. Point it at your indexer

export WAZUH_INDEXER_URL=https://indexer.example:9200
export WAZUH_INDEXER_USER=wazuh-readonly
export WAZUH_INDEXER_PASSWORD=...

WAZUH_INDEXER_URL is the only required variable. Add WAZUH_MANAGER_URL for manager/detectors and WAZUH_ENGINE_URL for tester_sessions; set WAZUH_VERIFY_SSL=false only for a self-signed lab cluster.

3. Enable masking (recommended for external LLMs)

Masking is off by default and opt-in:

export KLAXON_ANONYMIZE_EXTERNAL_LLM=true   # mask tool output for external models
export KLAXON_ANONYMIZATION_SALT=change-me-to-a-long-random-secret  # stable tokens

With the switch on, output is masked unless KLAXON_LLM_BASE_URL points at a loopback address (http://localhost:11434 for Ollama) — a local model keeps receiving unchanged data. An optional config.yaml (KLAXON_CONFIG) holds only what you change; environment variables always win:

anonymization:
  mask_fields:                 # or KLAXON_ANONYMIZATION_MASK_FIELDS
    - "source.ip"
    - "user.name"
    - "host.hostname"
  mask_aggregation_keys: true  # ON by default; false disables agg-key masking

4. Start it

klaxon-mcp    # stdio — your MCP client spawns it (klaxon is an alias)

With Docker: docker run --rm -i --env-file .env klaxon-mcp.

5. First masked result

Ask your client: "Show me the last login by user.name=alice in wazuh-events-v5-*." Klaxon runs search(index="wazuh-events-v5-*", body=…) and returns the masked response:

{
  "hits": { "hits": [ { "_source": {
      "user":    { "name": "[USER_9f2a1c467dd5e2b8]" },
      "source":  { "ip": "[IP_5c01e73f9a2b4c1d]" },
      "message": "user [USER_9f2a1c467dd5e2b8] logged in via ssh from [IP_5c01e73f9a2b4c1d]"
  } } ] }
}

The same value always maps to the same token. Masking is pseudonymization, not anonymization, and it has documented blind spots — see docs/llm-safety.md (in particular the verified leaks in "Known limitations") before pointing an external model at Klaxon.


Basic usage

The handful of tools a normal user needs (full reference: docs/TOOLS.md):

Tool What it does One-liner example
search Any query against any index, raw JSON back search(index="wazuh-events-v5-*", body={"query": {"match_all": {}}})
schema Which fields exist — and which actually carry data schema(index="wazuh-events-v5-*", prefix="wazuh.agent.")
field_coverage How complete each field is, window vs all history field_coverage(index="wazuh-events-v5-*", prefix="event.")
findings_overview Findings by severity, agent, title, category findings_overview(hours=48)
logtest Push a raw line through the decoder chain logtest(event="<raw line>")
gdpr_check Find sensitive fields the mask list should cover gdpr_check(index="wazuh-events-v5-*")
klaxon_posture_check Read-only security posture: facts + gaps, no verdict klaxon_posture_check(tenant="customer-a")

On an unfamiliar cluster, start with field_coverage. Every thin result gets a notice block before the data (empty aggregation, capped size, missing index — all return HTTP 200 with nothing).


Configuration (essentials)

The keys a normal user changes day to day. Full reference: docs/configuration.md.

Variable / key What it does Default
WAZUH_INDEXER_URL Indexer endpoint — (required)
WAZUH_INDEXER_USER / WAZUH_INDEXER_PASSWORD Basic-auth credentials empty
KLAXON_ANONYMIZE_EXTERNAL_LLM Master masking switch false
KLAXON_ANONYMIZATION_SALT Secret for token derivation (stable tokens) random+persisted
KLAXON_ANONYMIZATION_MASK_FIELDS Fields masked wholesale (user.name, source.ip, …) built-in list
KLAXON_ANONYMIZATION_MASK_AGGREGATION_KEYS Mask aggregation bucket keys too true (fail-closed)
KLAXON_ANONYMIZATION_MASK_FREE_TEXT_USERS Mask usernames inside free text true
WAZUH_VERIFY_SSL TLS verification true
WAZUH_MCP_AUTH_TOKEN Required bearer token when serving over HTTP empty

Advanced topics

The deep material lives in dedicated docs — linked, not duplicated:


Development

.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest        # full suite
.venv/bin/mypy          # strict type check
.venv/bin/ruff check src

Option B generator self-tests (see docs/drift-prevention.md):

klaxon masking selftest --tenant customer-a
klaxon masking generate --check   # CI/pre-commit drift check

Deploy the masking artifacts to the indexer in one idempotent, ordered, self-verifying step (preflight + GET-back verification + a _simulate smoke test; --dry-run / --rollback):

klaxon masking deploy --tenant customer-a --dry-run   # plan only, no writes
klaxon masking deploy --tenant customer-a             # needs KLAXON_INDEXER_*

The live integration test (klaxon masking test) needs real indexer credentials — see docs/option-b-masked-stream.md. Release history: CHANGELOG.md.


Documentation


License

Apache-2.0 — see LICENSE.

Built by sec73 GmbH.

Wazuh is a registered trademark of Wazuh Inc. Klaxon is an independent project and is not affiliated with, endorsed by, or sponsored by Wazuh Inc.

Release files for klaxon-mcp 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for klaxon-mcp 0.2.0
File Size Uploaded
klaxon_mcp-0.2.0.tar.gz 347.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for klaxon-mcp 0.2.0
File Interpreter ABI Platform
klaxon_mcp-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 541.3 kB

Release files / klaxon_mcp-0.2.0.tar.gz

Download URL klaxon_mcp-0.2.0.tar.gz
Size 347.3 kB
Tags Source
SHA-256 checksum
How to use checksums
d0e4950680d56d604d659f1313641bc35fa31668bd31e947db017d7833593f50
BLAKE2b-256 checksum
How to use checksums
b09395dcd439184efa7595bff6f520d8f8779feab66ac7a00d42371b81450ec4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release files / klaxon_mcp-0.2.0-py3-none-any.whl

Download URL klaxon_mcp-0.2.0-py3-none-any.whl
Size 194.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
186a0c7307ebba00233fee88052fe1e37fa249b24f6a13171ceaeab6758e57d2
BLAKE2b-256 checksum
How to use checksums
028e3a3dc55f55eb5bf2dde882fa547c576808c7d0e2029b5bc20d043f308e5e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release history Release notifications | RSS feed

0.2.1

2 release files

This release

0.2.0 This release

2 release files

0.1.7

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page