Skip to main content

Prohori (প্রহরী)

CI CodeQL OpenSSF Scorecard Secret scanning: gitleaks Dependabot

PyPI Python versions Wheel Downloads GitHub release Release workflow Docker stack Docker image Status: alpha Platforms License: MIT

uv Ruff Checked with mypy pre-commit Conventional Commits Contributor Covenant

Clones Last commit Commit activity Open issues Pull requests Wiki PRs welcome

A laptop-sized mini-SOC: from raw logs to detections, enriched incidents and analyst-ready reports, with honest machine-learning evaluation and a streaming data pipeline.

Prohori (প্রহরী) is Bengali for "guard". It is an open-source, defensive security-operations toolkit in Python that runs on a single laptop with free tools only, offline by default.

Status: alpha (v0.2.0). One command goes from a log file to scored incidents and shareable reports, a labelled attack simulator measures the rules, and v0.2.0 adds machine learning: anomaly detectors over your own logs, and flow models on public datasets evaluated with leakage controls, a model card and a cross-dataset test. The streaming pipeline and the LLM analyst follow. See the CHANGELOG and the roadmap.

60-second demo

pipx install prohori        # or: pip install prohori
prohori demo

prohori demo: three simulated attacks, the incidents found and every expected rule fired

The demo simulates an account takeover, an SSH brute force and a web scan, triages each one offline and writes an HTML report per incident to prohori-demo/. It needs no configuration and sends nothing over the network. Sample reports: Markdown, HTML (download and open it) and redacted.

Contents

What it does

flowchart LR
    A[Log files<br>auth.log, nginx, app JSON] --> B[parse<br>common event schema]
    B --> C[detect<br>15 YAML rules<br>+ anomaly detectors]
    C --> D[incidents<br>group, score, ATT&CK]
    E[enrich<br>GeoLite2, AbuseIPDB, VirusTotal] --> D
    D --> F[report<br>Markdown, HTML, JSON, CSV]
Stage Today
Parse Linux auth.log (sshd, sudo, PAM), nginx/Apache access logs, application security events (JSON Lines) and any JSON Lines layout through a YAML field mapping, into one typed event schema. Format detection, streaming with bounded memory, hostile input handled (oversized lines, bad encodings, control characters).
Detect 15 readable YAML rules (Sigma-inspired) with field modifiers, thresholds, distinct counts, ratios, sliding and tumbling windows and ordered sequences. Every rule ships with test fixtures and a runbook; regular expressions run on RE2, so no pattern can hang a run. With --models, anomaly detectors (robust z-score, Isolation Forest) also flag source IPs that behave unlike the rest of the log.
Enrich IP geolocation, network owner and reputation from local GeoLite2 files, AbuseIPDB and VirusTotal, all optional. SQLite cache, daily budgets, rate limiting and bounded retries. Private and documentation addresses never leave the machine.
Incidents Related alerts (same IP, account, request or session within a time gap) become one incident with a points-based score you can read line by line, severity, confidence, MITRE ATT&CK v20.0 mapping, a timeline, recommended actions and stated limitations. Detects sign-ins from an IP or app new to the account.
Simulate Eight labelled attack scenarios (SSH brute force, password spraying, credential stuffing, token reuse, account takeover, web scanning, injection, admin misuse) hidden in realistic background activity, byte-identical for a seed, documentation IP addresses only. Used to measure every rule's precision and recall.
Report Markdown, a self-contained HTML page, JSON or a CSV timeline, with an appendix of input and rule-file SHA-256 hashes. --redact replaces account names, ids and emails (and optionally IPs) with keyed pseudonyms, and refuses to write a report that would still leak one.
Stream The same parsers, rules and incident builder, continuously: lite mode in one process with DuckDB (no Docker), or a Docker stack with Redpanda, one container per stage and a PostgreSQL warehouse. Follow live logs through rotation or replay files, with at-least-once delivery, dead letters and each row stored once.

Prohori only recommends actions. It never blocks addresses, disables accounts or touches remote systems.

Install

Prohori needs Python 3.11 or newer. CI tests it on Linux and Windows; macOS should work but is not tested.

  • From PyPI: pipx install prohori puts the prohori command in its own environment; pip install prohori works in any virtual environment. Optional parts are extras: pip install "prohori[geoip]" for local GeoLite2 files, pip install "prohori[ml]" for the machine-learning code (scikit-learn, pandas, pyarrow) and pip install "prohori[pipeline]" for the streaming pipeline (DuckDB, confluent-kafka, psycopg). The base install never needs them.
  • As a Docker image: docker run --rm ghcr.io/utshabasak/prohori:latest demo runs the same command line with the pipeline extra, for linux/amd64 and linux/arm64. Mount a folder to read your own logs: docker run --rm -v "$PWD:/data" ghcr.io/utshabasak/prohori triage /data/auth.log.
  • From source: install uv, then git clone https://github.com/UtshaBasak/Prohori.git && cd Prohori && uv sync, and run commands as uv run prohori .... The sample files used below are in the repository.

Quick start

Everything below runs offline on the synthetic samples in the repository (run from a clone).

uv run prohori triage samples/logs/account-takeover.jsonl -o triage/   # the whole pipeline
uv run prohori parse samples/logs/auth.log                      # normalise a log
uv run prohori detect samples/logs/auth.log                     # run the 15 rules
uv run prohori enrich 203.0.113.7 --offline                     # look up an IP (demo data)
uv run prohori incidents samples/logs/account-takeover.jsonl    # group alerts into incidents
uv run prohori report samples/incidents/INC-ee7c8818fbb34989.json -f html -o report.html
uv run prohori simulate account-takeover --seed 1 --out out/      # your own labelled attack

prohori triage writes triage/summary.md, triage/incidents.jsonl and an HTML report per incident. Open a report in a browser: one file, no scripts, no external resources. For your own logs, prohori triage /var/log/auth.log --year 2026 -o triage/ works the same way (add --redact-ips before sharing the output).

Example: an account takeover

samples/logs/account-takeover.jsonl is a synthetic attack on a web shop account: twelve wrong passwords from one IP in 33 seconds, a successful sign-in, two-step verification switched off and the bKash payout number changed. Prohori raises four alerts and joins them into one critical incident:

$ uv run prohori incidents samples/logs/account-takeover.jsonl     # first lines
INC-ee7c8818fbb34989  critical  score 100
+-------------------------------------------------------------------------------------------------+
| title       | Successful sign-in after many failures from one IP and 3 related alerts           |
| time (UTC)  | 2026-10-24 21:10:00 - 2026-10-24 21:13:10                                         |
| confidence  | high                                                                              |
| rules       | PRH-ACCT-001, PRH-AUTH-001, PRH-AUTH-003                                          |
| entities    | source.ip: 203.0.113.7                                                            |
|             | user.email_hash: 0cc175b9c0f1b6a831c399e269772661                                 |
|             | user.id: u-2001                                                                   |

Every point of the score is explained (from prohori report):

Points Reason
+50 Most severe alert: PRH-AUTH-003 (critical)
+20 2 more rules fired: PRH-ACCT-001, PRH-AUTH-001
+20 A sign-in succeeded after failed attempts
+15 Sensitive account or admin change: account.2fa.disabled, account.bkash.changed
+20 Reputation of 203.0.113.7: abuse confidence 97; 9 vendors flag it as malicious (from bundled demo data)
+10 Sign-in to u-2001 from 203.0.113.7, an IP not seen for this account before (1 earlier IP)
+5 Sign-in to u-2001 with client web, not seen for this account before
-40 Capped at 100
100 Total

The report adds the timeline, the IP's enrichment, the ATT&CK techniques (T1110 Brute Force, T1078 Valid Accounts, T1098 Account Manipulation, T1556.006 Multi-Factor Authentication), recommended actions from the runbooks and the limitations, such as "single log source" and "enrichment is bundled demo data".

Commands

Command What it does Docs
prohori demo Simulate attacks, triage them and show the results; no configuration, offline CLI.md
prohori triage FILE Log in, incidents and a report per incident out (--format, --redact, --online) CLI.md
prohori parse FILE Parse and normalise a log; table or JSON Lines PARSERS.md
prohori detect FILE Run the 15 built-in rules (and --rules DIR, --models for the anomaly detectors); exit code 3 when alerts are found RULES.md
prohori rules list / validate / test Show, check and test detection rules RULES.md
prohori enrich IP... Geolocation and reputation; --offline never uses the network ENRICHMENT.md
prohori incidents FILE Parse, detect and group alerts into scored incidents; offline enrichment unless --online INCIDENTS.md
prohori report INCIDENT Render an incident as md, html, json or csv; --redact, --redact-ips REPORTS.md
prohori simulate SCENARIO --out DIR Write a labelled attack scenario (--list, --seed, --variant heldout, --set) SIMULATOR.md
prohori score FLOWS --model NAME Score a CSV of network flows with a saved model; needs the ml extra (--fpr, --top, -f json) CLI.md
prohori pipeline replay FILE... / tail FILE... Stream log files through the lite pipeline into DuckDB, or with --bootstrap into the Docker stack; tail follows live logs until Ctrl+C; needs the pipeline extra PIPELINE.md
prohori pipeline up / status / down Start, check and stop the Docker stack (Redpanda, PostgreSQL, one container per stage); needs Docker and a clone PIPELINE.md
prohori version The version, ATT&CK release and built-in rule set

Commands that take a file also read standard input with -, and every command has --help. Exit codes are the same everywhere: 0 nothing found, 3 alerts or incidents found (--exit-zero turns this off for CI pipelines), 1 nothing could be processed and 2 invalid usage. The full reference is docs/CLI.md.

Built-in detection rules

Rule Detects Severity ATT&CK
PRH-AUTH-001 Brute-force sign-in attempts from one IP high T1110, T1110.001
PRH-AUTH-002 Password spraying or credential stuffing from one IP high T1110.003, T1110.004
PRH-AUTH-003 Successful sign-in after many failures from one IP critical T1110, T1078
PRH-AUTH-004 Account lockouts across many accounts high T1110
PRH-SESS-001 Refresh-token reuse high T1528, T1550.001
PRH-ACCT-001 Sensitive account change shortly after sign-in high T1098, T1556.006
PRH-AUTHZ-001 Authorisation denials across many resources medium T1595.003
PRH-ABUSE-001 Rate-limit bursts or bot-check failures from one IP medium T1499.002
PRH-ADMIN-001 Admin role granted high T1098
PRH-ADMIN-002 Admin action at an unusual hour (22:00-07:00 local time) medium T1078
PRH-WEB-001 Web scanning from one IP medium T1595.003
PRH-WEB-002 Injection patterns in request paths medium T1190
PRH-WEB-003 Scanner or empty user agent at a high request rate medium T1595.002
PRH-WEB-004 Probes for sensitive files and admin tools medium T1595.003
PRH-SSH-001 SSH invalid-user storm or repeated authentication-limit disconnects high T1110, T1110.001

ATT&CK tags are verified against the official Enterprise ATT&CK v20.0 data. Each rule has a runbook in src/prohori/runbooks/. Writing your own rules is covered in docs/RULES.md.

How well the rules work

prohori simulate writes each attack scenario in two variants: default, the attack the rules were designed for, and held-out, the same attack done the way a careful attacker would (slower, from more addresses, with a browser user agent, obfuscated, or at another hour). scripts/evaluate_rules.py runs all rules over five seeds of each and compares the alerts with the line labels.

Default Held-out
Attacks caught by at least one expected rule 40/40 35/40

Expected rules that missed in the held-out variants (runs missed out of runs):

  • account-takeover: PRH-ACCT-001 3/5, PRH-AUTH-001 5/5
  • admin-misuse: PRH-ADMIN-002 5/5
  • credential-stuffing: PRH-AUTH-001 5/5, PRH-AUTH-002 5/5, PRH-AUTH-003 5/5
  • password-spraying: PRH-AUTH-001 5/5
  • ssh-bruteforce: PRH-AUTH-001 5/5
  • web-scanning: PRH-WEB-003 5/5

PRH-ACCT-001 has low precision on purpose: it flags every sensitive change soon after a sign-in, including the owners' own. These are synthetic results written by the rules' author; the full per-rule table and caveats are in docs/SIMULATOR.md.

Machine learning

Two tracks, both evaluated the way a SOC would judge them: how many attacks are caught for how many false alerts, never accuracy.

Anomaly detection on your logs

--models on detect, incidents and triage cuts the parsed events into five-minute windows per source IP, computes thirteen behaviour features (sign-in attempts and failures, accounts and paths touched, 4xx share, path entropy, user-agent diversity, new clients, night activity) and flags windows that are unusual for that log: a robust z-score (base install) and an Isolation Forest (ml extra). Their alerts are marked detector_kind: model, explained with numbers only, and join the incidents. Compared with the rules on the simulator's held-out attacks:

Detector Attacks caught Precision False alerts a run Caught only by it
Detection rules (any rule) 36/40 39 % 6.47 -
Robust z-score 15/40 100 % 0.0 0
Fixed thresholds 10/40 49 % 0.93 0
Isolation Forest 17/40 70 % 0.35 0

Held-out variants, seeds 1 to 5; an attack is caught when a true-positive alert is credited to it. Any rule counts here, which is why the rules catch one attack more than in the expected-rule count above.

The detectors catch the loud attacks (brute force, scanning, injection) with almost no false alerts, but nothing the rules miss: a slow or spread-out attack looks ordinary in a five-minute window from one address. They are a quiet second signal, not a new way to find attacks. Details and the false alerts' causes: docs/ANOMALY.md.

Flow models on public datasets

Intrusion-detection models trained on two public network-flow datasets, UNSW-NB15 and the improved CIC-IDS-2017:

  • Leakage-controlled data. Identifier, address, port, time and label-derived columns are dropped; duplicates and contradictory rows are removed; test rows that also occur in training are removed and counted; transforms are learned from training data only. On CIC-IDS-2017, 18 % of the 2.1 million flows are duplicates once identifiers are gone, most of them port-scan probes; on UNSW-NB15, 41 % of the rows. DATASETS.md has the numbers.
  • Two models: gradient boosting (supervised) and an Isolation Forest trained on benign traffic only.
  • Honest metrics. Thresholds are chosen on a validation split for a 1 % or 0.1 % false-positive rate and frozen; the test split is scored once per seed. Reported: PR-AUC, recall, the false-positive rate actually reached on test, false alerts per 10,000 flows and recall per attack class, as mean and standard deviation over three seeds. Never accuracy.
Dataset Model PR-AUC Recall Test FPR False alerts / 10k
unsw-nb15 Gradient boosting (supervised) 0.952 46.00 % 0.07 % 4.2
unsw-nb15 Isolation Forest (benign only) 0.522 0.71 % 0.11 % 6.9
cicids2017 Gradient boosting (supervised) 0.999 99.98 % 0.10 % 8.7
cicids2017 Isolation Forest (benign only) 0.527 0.00 % 0.10 % 8.7

Means over seeds 0, 1, 2 on the cleaned test splits, with thresholds chosen for a 0.1 % false-positive rate on the validation split and then frozen.

What the numbers say: gradient boosting catches about half of UNSW-NB15's attacks at a false-alert rate a SOC could live with, misses most fuzzing and analysis traffic, and on CIC-IDS-2017 catches nearly everything, which says more about that lab dataset than about the model. The 1 % threshold chosen on UNSW-NB15's validation data gave 4.5 % false positives on its test file. The Isolation Forest is not a usable detector on either dataset. Without the leakage controls the same pipeline would report a PR-AUC of 0.989 instead of 0.952 on UNSW-NB15. Details, failure cases and limitations: docs/MODEL_CARD.md.

Do the models work on another network? Not usefully. Trained on the twelve features both datasets share and tested on the other dataset, gradient boosting's ROC-AUC falls from 0.95 and 1.00 to 0.64 and 0.61, weak but above chance, and the threshold that gave 1 % false positives at home flags 42 % to 60 % of the other dataset's benign flows:

Trained on → tested on ROC-AUC, same dataset ROC-AUC, other dataset PR-AUC (chance), same dataset PR-AUC (chance), other dataset Benign flows flagged on the other dataset (1 % threshold)
unsw-nb15 → cicids2017 0.952 0.639 0.918 (0.33) 0.323 (0.16) 42.2 %
cicids2017 → unsw-nb15 1.000 0.614 0.999 (0.16) 0.404 (0.33) 60.5 %

Gradient boosting, means over seeds 0, 1, 2. ROC-AUC: 0.5 is chance. PR-AUC: chance is the attack share, in brackets.

A flow model trained on one lab's traffic learns that lab. The mapping, its losses and the per-class results are in the model card.

uv run python scripts/download_datasets.py fetch cicids2017      # about 330 MB
uv run python -m prohori.ml.train --dataset cicids2017 --seeds 0 1 2
uv run python -m prohori.ml.transfer --seeds 0 1 2               # the cross-dataset test
prohori score flows.csv --model cicids2017/hgb-seed0              # score your own flows

prohori score reads a CSV of flows in the layout the model was trained on (CICFlowMeter output for a CIC-IDS-2017 model) and lists the flagged flows; see docs/CLI.md.

Streaming pipeline

The batch commands read a file and stop. prohori pipeline keeps going. Lite mode runs the same parsers, rules and incident builder as a chain of consumers in one process, without Docker, and stores everything in a DuckDB file:

pip install "prohori[pipeline]"
prohori simulate web-scanning --seed 1 --out logs   # write a simulated log to replay
prohori pipeline replay logs/web-scanning.log --times 2   # replay twice: each row stored once
prohori pipeline tail /var/log/auth.log --year 2026       # follow a live log until Ctrl+C
# query the results with the DuckDB command-line tool (a separate download):
duckdb .prohori/pipeline/prohori.duckdb "SELECT severity, score, title FROM incidents"
flowchart LR
    T[tail / replay] -->|raw-logs| N[normalise]
    N -->|events| D[detect]
    D -->|alerts| I[incidents]
    N & D & I --> S[(DuckDB)]
    N -.->|dead-letter| S
  • Same answers as batch. For every simulator scenario, the incidents the stream writes are exactly those of prohori incidents on the same file. That holds even when the file is replayed twice, which CI tests on Linux and Windows.
  • Nothing lost, nothing doubled. Delivery is at least once (publish, fsync, then commit). Every id is a hash of its content, so a replay, a redelivery or a crash mid-run adds no rows. Duplicate events are dropped before the rules, so they cannot push a threshold over its limit.
  • Bad input is set aside. Lines no parser accepts and malformed messages go to a dead-letter table with the reason, and are counted.
  • Live logs. The tailer survives rotation by rename or truncation (on Windows too, without blocking the writer's rotation) and continues where it stopped after a restart. Ctrl+C finishes cleanly.
  • Honest limits. Lite mode handles about 2,800 lines a second on a laptop, a single host's logs, against about 14,000 for the batch command. Its topics are not trimmed, and --models stays a batch feature.

The Docker stack runs the same stages as separate containers over Redpanda (a Kafka-compatible broker), with a PostgreSQL warehouse, from a clone of the repository:

uv run prohori pipeline up                    # build, start and wait until every service is healthy
uv run prohori simulate web-scanning --seed 1 --out logs   # write a simulated log to replay
uv run prohori pipeline replay logs/web-scanning.log --bootstrap 127.0.0.1:19092
uv run prohori pipeline status                # services, health and rows in the warehouse
uv run prohori pipeline down                  # --volumes also deletes the data
Service What it is Memory limit
redpanda Kafka-compatible broker, one node 1.5 GB
postgres Warehouse (raw schema) 512 MB
normalise, detect, incidents, sink One Prohori container per stage 256-384 MB each

Replaying a 50,700-line log, the whole stack peaked at about 700 MB. A smoke test in CI starts it from empty volumes, replays a simulated attack twice and checks that the warehouse holds each event once and exactly the incidents of the batch command. Images are pinned by digest, the containers run unprivileged, and the ports are bound to 127.0.0.1 only.

Details: docs/PIPELINE.md.

Warehouse models. A dbt project (pipeline/dbt/) turns the stored rows into tested tables on either database:

  • daily attack counts, alerts per rule, top source addresses and incident durations;
  • rule precision and detection latency, and time to detect each simulated attack (from the simulator's labels);
  • flow-model performance over time.
uv sync --group dbt
uv run python scripts/build_lite_warehouse.py --db .prohori/pipeline/prohori.duckdb
uv run dbt build --project-dir pipeline/dbt --profiles-dir pipeline/dbt    # --target postgres for the stack

dbt build runs 68 data-quality checks: keys, nulls, accepted values, ranges, duplicates, cross-table consistency and future timestamps. A separate freshness check covers the inputs. On the simulator's eight scenarios, the models' rule precision equals the simulator's own evaluation alert for alert, which CI checks. Every attack was detected, the fastest in 0 s and the slowest (an admin acting at night) in 252 s. Details: docs/WAREHOUSE.md.

Dashboards. prohori pipeline up also starts Grafana on http://127.0.0.1:13000, with three dashboards provisioned from pipeline/grafana/. They open without a login or any setup:

  • Overview: events, alerts and incidents over time, rule hits and top sources. It reads the raw tables, so it fills as soon as a replay is stored.
  • Detection quality: rule precision, time to detect, detection latency and flow-model performance, from the dbt marts.
  • Pipeline health: time since the last stored event, rows per minute and dead letters.

Overview dashboard after replaying the eight simulator scenarios one day apart

Grafana reads through its own database role, which can only SELECT. The smoke test runs every panel's query through Grafana after a replay. Details: docs/DASHBOARD.md.

Scheduled jobs. prohori pipeline up --orchestration adds Airflow (http://127.0.0.1:18080) with five DAGs:

Job When What it does
Backfill By hand Replays all eight simulated scenarios with their labels, then builds the marts
Data quality Hourly dbt build with its 68 checks, and a freshness check on the events
Nightly evaluation 02:00 UTC Retrains the flow models; the run fails if recall drops below 0.75
Incident digest 06:00 UTC The last day's incidents, rules and dead letters, as Markdown and JSON
Enrichment refresh 03:30 UTC Keeps the incident stage's enrichment cache current (offline without API keys)

Each task runs a prohori command (pipeline digest, check-recall, refresh-enrichment) or dbt, so every job also works without Airflow. CI imports the DAGs with Airflow on Linux and runs each one in the stack. Details: docs/ORCHESTRATION.md.

Privacy and safety

  • Offline by default. Nothing leaves the machine unless you configure API keys and, for incidents, pass --online. Tests block real network access.
  • Private, reserved and documentation IP addresses are never sent to outside services, even when keys are configured. VirusTotal keeps the addresses it is asked about, so querying is a deliberate choice.
  • Logs are untrusted input. Parsers cap line lengths and strip control characters; reports escape every value for their format (HTML, Markdown, CSV formulas) and are tested with hostile input.
  • Shareable reports. --redact / --redact-ips use keyed pseudonyms and fail closed.
  • No secrets in the repository. API keys live in the environment or a git-ignored .env; gitleaks runs on every commit and in CI.
  • Samples are synthetic and use documentation address ranges only. Public datasets are downloaded only on an explicit command, never committed and never fetched at import time.

Third-party data terms (MITRE ATT&CK, GeoLite2, AbuseIPDB, VirusTotal) are summarised in docs/LICENSES.md.

Documentation

Document Contents
CLI reference Every command, the shared options, formats and exit codes
Architecture Components, data flow and run modes
Event schema Event, Alert and Incident fields
Parsers Supported formats, options and field mappings
Rules Rule format, evaluation semantics, testing and the built-in rules
Enrichment Providers, privacy, cache, limits and configuration
Incidents Grouping, the scoring table, confidence and limitations
Reports Formats, sections, redaction and output safety
Simulator Attack scenarios, labels, and rule precision and recall
Datasets UNSW-NB15 and CIC-IDS-2017: getting them, licences, leakage controls
Model card Flow detectors: intended use, evaluation, results, failure cases
Anomaly detection --models: features, detectors, comparison with the rules, false alerts
Streaming pipeline Lite mode and the Docker stack: topics, delivery guarantees, dead letters, tailing, the tables
Warehouse models dbt models, data-quality checks, labels and model results, results on the simulator
Dashboards The Grafana dashboards: panels, set-up, access, time range, changing a dashboard
Scheduled jobs The Airflow DAGs: backfill, data quality, nightly evaluation, digest, enrichment refresh
Decisions Architecture decision records
Releasing How releases are built, tested and published
Third-party terms Data and service licences
Wiki Installation, quick start and guides

Limitations

Prohori is honest about what it does not do:

  • The streaming stack runs on one machine. It is a single-node broker with no authentication, built from a clone; dashboards and scheduled jobs come later in v0.3.0. Prohori never blocks, disables or changes anything.
  • Rules see only what the logs say. Request bodies, headers and encrypted traffic are not inspected; the web rules use the request line only.
  • Thresholds can be evaded. The held-out evaluation shows that slow or widely distributed attacks stay under the brute-force and spraying thresholds; see How well the rules work.
  • Some rules are noisy by design. PRH-ACCT-001 flags every sensitive change shortly after a sign-in, including the owner's own; incidents list such benign causes as limitations.
  • The evaluation is synthetic and written by the rules' author. Test the rules on your own logs before relying on them.
  • Enrichment free tiers are small and non-commercial, and reputation data is evidence, not proof.
  • Supported formats are auth.log, nginx/Apache access logs and JSON Lines (built-in app layout or a field mapping). Windows event logs and cloud audit logs are not supported yet.

Roadmap

Release Contents State
v0.1.0 Batch core: parsers, rules, enrichment, incidents, reports, log simulator with rule evaluation, triage and demo commands Released
v0.2.0 Machine learning: leakage-controlled intrusion detection on public flow datasets, log-derived anomaly detection, model cards Released
v0.3.0 Pipeline: streaming core and lite mode, Redpanda and PostgreSQL in Docker, dbt models and data-quality checks, Grafana dashboards, Airflow jobs; Docker image on GitHub Packages Released
v1.0.0 Optional LLM analyst (opt-in, redacted input, validated output), final polish Planned

Development

uv sync                        # create .venv with the package and dev tools
uv run pre-commit install      # optional: run checks on every commit

uv run ruff check .            # lint
uv run ruff format --check .   # formatting
uv run mypy                    # strict type check
uv run pytest --cov            # tests with coverage (kept at 85 % or more)
uv run prohori rules test      # run every rule against its fixtures
uv run pytest tests/unit/ml -q # dataset and leakage tests (synthetic fixtures, no downloads)
uv run pytest tests/integration # end to end, including lite mode against the batch command
uv sync --group dbt && uv run pytest tests/integration/test_dbt.py  # the dbt models

CI runs on Linux and Windows with Python 3.11 to 3.14, plus CodeQL, secret scanning, parser fuzzing and the OpenSSF Scorecard. All GitHub Actions are pinned to commit SHAs. See CONTRIBUTING.md for conventions.

Security

To report a vulnerability, see SECURITY.md.

License

MIT. Third-party data has its own terms; see docs/LICENSES.md.

Metadata

Release files for prohori 0.3.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 prohori 0.3.0
File Size Uploaded Provenance Actions
prohori-0.3.0.tar.gz 420.1 kB With provenance Details for prohori-0.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for prohori 0.3.0
File Interpreter ABI Platform Actions
prohori-0.3.0-py3-none-any.whl Python 3 none any Details for prohori-0.3.0-py3-none-any.whl

Total release size: 719.5 kB

Release files / prohori-0.3.0.tar.gz

Download URL prohori-0.3.0.tar.gz
Size 420.1 kB
Tags Source
SHA-256 checksum
How to use checksums
3a931507154c687a13864a4f041884bf49c05610f90984355e02895251c41238
BLAKE2b-256 checksum
How to use checksums
ec8c6ddeea73b49dfa5d87ec01d361a8777ba076e0a63959d785f8226ab8a704
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Release files / prohori-0.3.0-py3-none-any.whl

Download URL prohori-0.3.0-py3-none-any.whl
Size 299.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
be5a8ae5faf1646ed372f6c8a339454e1608d35ce02fa3eeae861a5fe568cefb
BLAKE2b-256 checksum
How to use checksums
3a7872452451d818980f80c8a914eef3ceb3a2e1e245beea76b8c8ed08ef51ca
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Security for prohori 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for prohori-0.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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