prohori 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. prohori-0.1.0/.gitignore +70 -0
  2. prohori-0.1.0/CHANGELOG.md +145 -0
  3. prohori-0.1.0/LICENSE +21 -0
  4. prohori-0.1.0/PKG-INFO +376 -0
  5. prohori-0.1.0/README.md +331 -0
  6. prohori-0.1.0/pyproject.toml +200 -0
  7. prohori-0.1.0/src/prohori/__init__.py +9 -0
  8. prohori-0.1.0/src/prohori/__main__.py +6 -0
  9. prohori-0.1.0/src/prohori/cli/__init__.py +5 -0
  10. prohori-0.1.0/src/prohori/cli/app.py +85 -0
  11. prohori-0.1.0/src/prohori/cli/demo.py +120 -0
  12. prohori-0.1.0/src/prohori/cli/detect.py +225 -0
  13. prohori-0.1.0/src/prohori/cli/enrich.py +91 -0
  14. prohori-0.1.0/src/prohori/cli/exit_codes.py +14 -0
  15. prohori-0.1.0/src/prohori/cli/incidents.py +251 -0
  16. prohori-0.1.0/src/prohori/cli/parse.py +310 -0
  17. prohori-0.1.0/src/prohori/cli/report.py +135 -0
  18. prohori-0.1.0/src/prohori/cli/rules.py +180 -0
  19. prohori-0.1.0/src/prohori/cli/simulate.py +91 -0
  20. prohori-0.1.0/src/prohori/cli/triage.py +295 -0
  21. prohori-0.1.0/src/prohori/config.py +1 -0
  22. prohori-0.1.0/src/prohori/enrich/__init__.py +34 -0
  23. prohori-0.1.0/src/prohori/enrich/abuseipdb.py +59 -0
  24. prohori-0.1.0/src/prohori/enrich/addresses.py +97 -0
  25. prohori-0.1.0/src/prohori/enrich/cache.py +137 -0
  26. prohori-0.1.0/src/prohori/enrich/config.py +118 -0
  27. prohori-0.1.0/src/prohori/enrich/data/offline.json +109 -0
  28. prohori-0.1.0/src/prohori/enrich/enricher.py +207 -0
  29. prohori-0.1.0/src/prohori/enrich/geolite.py +92 -0
  30. prohori-0.1.0/src/prohori/enrich/http.py +150 -0
  31. prohori-0.1.0/src/prohori/enrich/offline.py +39 -0
  32. prohori-0.1.0/src/prohori/enrich/provider.py +97 -0
  33. prohori-0.1.0/src/prohori/enrich/ratelimit.py +117 -0
  34. prohori-0.1.0/src/prohori/enrich/virustotal.py +58 -0
  35. prohori-0.1.0/src/prohori/incidents/__init__.py +34 -0
  36. prohori-0.1.0/src/prohori/incidents/attack.py +71 -0
  37. prohori-0.1.0/src/prohori/incidents/builder.py +421 -0
  38. prohori-0.1.0/src/prohori/incidents/events.py +56 -0
  39. prohori-0.1.0/src/prohori/incidents/grouping.py +131 -0
  40. prohori-0.1.0/src/prohori/incidents/novelty.py +126 -0
  41. prohori-0.1.0/src/prohori/incidents/provenance.py +52 -0
  42. prohori-0.1.0/src/prohori/incidents/runbooks.py +94 -0
  43. prohori-0.1.0/src/prohori/incidents/scoring.py +231 -0
  44. prohori-0.1.0/src/prohori/incidents/summary.py +83 -0
  45. prohori-0.1.0/src/prohori/llm/__init__.py +1 -0
  46. prohori-0.1.0/src/prohori/logging.py +1 -0
  47. prohori-0.1.0/src/prohori/ml/__init__.py +1 -0
  48. prohori-0.1.0/src/prohori/parsers/__init__.py +39 -0
  49. prohori-0.1.0/src/prohori/parsers/access_log.py +191 -0
  50. prohori-0.1.0/src/prohori/parsers/app_json.py +123 -0
  51. prohori-0.1.0/src/prohori/parsers/auth_log.py +459 -0
  52. prohori-0.1.0/src/prohori/parsers/base.py +216 -0
  53. prohori-0.1.0/src/prohori/parsers/detect.py +69 -0
  54. prohori-0.1.0/src/prohori/parsers/generic_json.py +209 -0
  55. prohori-0.1.0/src/prohori/parsers/registry.py +41 -0
  56. prohori-0.1.0/src/prohori/parsers/util.py +54 -0
  57. prohori-0.1.0/src/prohori/pipeline/__init__.py +1 -0
  58. prohori-0.1.0/src/prohori/py.typed +0 -0
  59. prohori-0.1.0/src/prohori/reports/__init__.py +30 -0
  60. prohori-0.1.0/src/prohori/reports/make.py +46 -0
  61. prohori-0.1.0/src/prohori/reports/redact.py +190 -0
  62. prohori-0.1.0/src/prohori/reports/render.py +359 -0
  63. prohori-0.1.0/src/prohori/reports/templates/report.html.j2 +208 -0
  64. prohori-0.1.0/src/prohori/reports/templates/report.md.j2 +146 -0
  65. prohori-0.1.0/src/prohori/rules/__init__.py +48 -0
  66. prohori-0.1.0/src/prohori/rules/attack.py +80 -0
  67. prohori-0.1.0/src/prohori/rules/builtin/README.md +8 -0
  68. prohori-0.1.0/src/prohori/rules/builtin/abuse-bursts.tests.yml +65 -0
  69. prohori-0.1.0/src/prohori/rules/builtin/abuse-bursts.yml +23 -0
  70. prohori-0.1.0/src/prohori/rules/builtin/account-lockouts.tests.yml +46 -0
  71. prohori-0.1.0/src/prohori/rules/builtin/account-lockouts.yml +22 -0
  72. prohori-0.1.0/src/prohori/rules/builtin/admin-role-granted.tests.yml +30 -0
  73. prohori-0.1.0/src/prohori/rules/builtin/admin-role-granted.yml +17 -0
  74. prohori-0.1.0/src/prohori/rules/builtin/admin-unusual-hours.tests.yml +38 -0
  75. prohori-0.1.0/src/prohori/rules/builtin/admin-unusual-hours.yml +24 -0
  76. prohori-0.1.0/src/prohori/rules/builtin/authorization-enumeration.tests.yml +59 -0
  77. prohori-0.1.0/src/prohori/rules/builtin/authorization-enumeration.yml +24 -0
  78. prohori-0.1.0/src/prohori/rules/builtin/brute-force.tests.yml +65 -0
  79. prohori-0.1.0/src/prohori/rules/builtin/brute-force.yml +21 -0
  80. prohori-0.1.0/src/prohori/rules/builtin/injection-attempts.tests.yml +58 -0
  81. prohori-0.1.0/src/prohori/rules/builtin/injection-attempts.yml +61 -0
  82. prohori-0.1.0/src/prohori/rules/builtin/password-spraying.tests.yml +65 -0
  83. prohori-0.1.0/src/prohori/rules/builtin/password-spraying.yml +23 -0
  84. prohori-0.1.0/src/prohori/rules/builtin/refresh-token-reuse.tests.yml +31 -0
  85. prohori-0.1.0/src/prohori/rules/builtin/refresh-token-reuse.yml +18 -0
  86. prohori-0.1.0/src/prohori/rules/builtin/scanner-user-agents.tests.yml +79 -0
  87. prohori-0.1.0/src/prohori/rules/builtin/scanner-user-agents.yml +43 -0
  88. prohori-0.1.0/src/prohori/rules/builtin/sensitive-account-change.tests.yml +54 -0
  89. prohori-0.1.0/src/prohori/rules/builtin/sensitive-account-change.yml +33 -0
  90. prohori-0.1.0/src/prohori/rules/builtin/sensitive-file-probes.tests.yml +61 -0
  91. prohori-0.1.0/src/prohori/rules/builtin/sensitive-file-probes.yml +41 -0
  92. prohori-0.1.0/src/prohori/rules/builtin/ssh-invalid-user-storm.tests.yml +62 -0
  93. prohori-0.1.0/src/prohori/rules/builtin/ssh-invalid-user-storm.yml +28 -0
  94. prohori-0.1.0/src/prohori/rules/builtin/success-after-failures.tests.yml +60 -0
  95. prohori-0.1.0/src/prohori/rules/builtin/success-after-failures.yml +26 -0
  96. prohori-0.1.0/src/prohori/rules/builtin/web-scanning.tests.yml +85 -0
  97. prohori-0.1.0/src/prohori/rules/builtin/web-scanning.yml +22 -0
  98. prohori-0.1.0/src/prohori/rules/compiler.py +224 -0
  99. prohori-0.1.0/src/prohori/rules/condition.py +231 -0
  100. prohori-0.1.0/src/prohori/rules/engine.py +531 -0
  101. prohori-0.1.0/src/prohori/rules/loader.py +208 -0
  102. prohori-0.1.0/src/prohori/rules/matching.py +370 -0
  103. prohori-0.1.0/src/prohori/rules/model.py +220 -0
  104. prohori-0.1.0/src/prohori/rules/regex_safety.py +77 -0
  105. prohori-0.1.0/src/prohori/rules/testing.py +331 -0
  106. prohori-0.1.0/src/prohori/runbooks/README.md +25 -0
  107. prohori-0.1.0/src/prohori/runbooks/abuse-bursts.md +34 -0
  108. prohori-0.1.0/src/prohori/runbooks/account-lockouts.md +32 -0
  109. prohori-0.1.0/src/prohori/runbooks/admin-role-granted.md +32 -0
  110. prohori-0.1.0/src/prohori/runbooks/admin-unusual-hours.md +33 -0
  111. prohori-0.1.0/src/prohori/runbooks/authorization-enumeration.md +31 -0
  112. prohori-0.1.0/src/prohori/runbooks/brute-force.md +35 -0
  113. prohori-0.1.0/src/prohori/runbooks/injection-attempts.md +33 -0
  114. prohori-0.1.0/src/prohori/runbooks/password-spraying.md +33 -0
  115. prohori-0.1.0/src/prohori/runbooks/refresh-token-reuse.md +32 -0
  116. prohori-0.1.0/src/prohori/runbooks/scanner-user-agents.md +31 -0
  117. prohori-0.1.0/src/prohori/runbooks/sensitive-account-change.md +35 -0
  118. prohori-0.1.0/src/prohori/runbooks/sensitive-file-probes.md +32 -0
  119. prohori-0.1.0/src/prohori/runbooks/ssh-invalid-user-storm.md +32 -0
  120. prohori-0.1.0/src/prohori/runbooks/success-after-failures.md +34 -0
  121. prohori-0.1.0/src/prohori/runbooks/web-scanning.md +32 -0
  122. prohori-0.1.0/src/prohori/schema/__init__.py +70 -0
  123. prohori-0.1.0/src/prohori/schema/alert.py +55 -0
  124. prohori-0.1.0/src/prohori/schema/event.py +98 -0
  125. prohori-0.1.0/src/prohori/schema/fields.py +77 -0
  126. prohori-0.1.0/src/prohori/schema/ids.py +35 -0
  127. prohori-0.1.0/src/prohori/schema/incident.py +165 -0
  128. prohori-0.1.0/src/prohori/schema/serialization.py +35 -0
  129. prohori-0.1.0/src/prohori/schema/types.py +95 -0
  130. prohori-0.1.0/src/prohori/simulate/__init__.py +23 -0
  131. prohori-0.1.0/src/prohori/simulate/addresses.py +57 -0
  132. prohori-0.1.0/src/prohori/simulate/background.py +394 -0
  133. prohori-0.1.0/src/prohori/simulate/evaluate.py +197 -0
  134. prohori-0.1.0/src/prohori/simulate/formats.py +79 -0
  135. prohori-0.1.0/src/prohori/simulate/model.py +125 -0
  136. prohori-0.1.0/src/prohori/simulate/run.py +127 -0
  137. prohori-0.1.0/src/prohori/simulate/scenarios.py +613 -0
  138. prohori-0.1.0/src/prohori/version.py +3 -0
  139. prohori-0.1.0/tests/__init__.py +0 -0
  140. prohori-0.1.0/tests/fixtures/.gitkeep +0 -0
  141. prohori-0.1.0/tests/fixtures/rules/invalid/aggregate.yml +13 -0
  142. prohori-0.1.0/tests/fixtures/rules/invalid/alias-bomb.yml +11 -0
  143. prohori-0.1.0/tests/fixtures/rules/invalid/bad-condition.yml +8 -0
  144. prohori-0.1.0/tests/fixtures/rules/invalid/metadata.yml +10 -0
  145. prohori-0.1.0/tests/fixtures/rules/invalid/modifiers.yml +12 -0
  146. prohori-0.1.0/tests/fixtures/rules/invalid/python-tag.yml +5 -0
  147. prohori-0.1.0/tests/fixtures/rules/invalid/unknown-field.yml +8 -0
  148. prohori-0.1.0/tests/fixtures/rules/invalid/unsafe-regex.yml +8 -0
  149. prohori-0.1.0/tests/fixtures/rules/invalid/unused-selection.yml +10 -0
  150. prohori-0.1.0/tests/fixtures/rules/valid/brute-force.tests.yml +49 -0
  151. prohori-0.1.0/tests/fixtures/rules/valid/brute-force.yml +17 -0
  152. prohori-0.1.0/tests/fixtures/rules/valid/error-ratio.tests.yml +30 -0
  153. prohori-0.1.0/tests/fixtures/rules/valid/error-ratio.yml +16 -0
  154. prohori-0.1.0/tests/fixtures/rules/valid/password-spraying.tests.yml +36 -0
  155. prohori-0.1.0/tests/fixtures/rules/valid/password-spraying.yml +17 -0
  156. prohori-0.1.0/tests/fixtures/rules/valid/success-after-failures.tests.yml +36 -0
  157. prohori-0.1.0/tests/fixtures/rules/valid/success-after-failures.yml +17 -0
  158. prohori-0.1.0/tests/fixtures/rules/valid/web-scanning.tests.yml +38 -0
  159. prohori-0.1.0/tests/fixtures/rules/valid/web-scanning.yml +20 -0
  160. prohori-0.1.0/tests/golden/.gitkeep +0 -0
  161. prohori-0.1.0/tests/golden/cli/demo.txt +15 -0
  162. prohori-0.1.0/tests/golden/cli/detect-access-log.txt +27 -0
  163. prohori-0.1.0/tests/golden/cli/detect-auth-log.txt +20 -0
  164. prohori-0.1.0/tests/golden/cli/incidents-takeover.txt +55 -0
  165. prohori-0.1.0/tests/golden/cli/parse-app.txt +26 -0
  166. prohori-0.1.0/tests/golden/cli/rules-list.txt +49 -0
  167. prohori-0.1.0/tests/golden/cli/simulate-list.txt +59 -0
  168. prohori-0.1.0/tests/golden/cli/triage-auth-log-summary.md +9 -0
  169. prohori-0.1.0/tests/golden/detect/access-log.jsonl +2 -0
  170. prohori-0.1.0/tests/golden/detect/app-security.jsonl +3 -0
  171. prohori-0.1.0/tests/golden/detect/auth-log.jsonl +1 -0
  172. prohori-0.1.0/tests/golden/incidents/access-log.jsonl +2 -0
  173. prohori-0.1.0/tests/golden/incidents/account-takeover.jsonl +1 -0
  174. prohori-0.1.0/tests/golden/incidents/app-security.jsonl +3 -0
  175. prohori-0.1.0/tests/golden/incidents/auth-log.jsonl +1 -0
  176. prohori-0.1.0/tests/golden/reports/takeover-redacted.md +169 -0
  177. prohori-0.1.0/tests/golden/reports/takeover.csv +18 -0
  178. prohori-0.1.0/tests/golden/reports/takeover.html +229 -0
  179. prohori-0.1.0/tests/golden/reports/takeover.json +487 -0
  180. prohori-0.1.0/tests/golden/reports/takeover.md +170 -0
  181. prohori-0.1.0/tests/golden/simulate/eval-seed1.json +457 -0
  182. prohori-0.1.0/tests/golden/simulate/hashes.json +18 -0
  183. prohori-0.1.0/tests/integration/__init__.py +0 -0
  184. prohori-0.1.0/tests/integration/test_pipeline.py +67 -0
  185. prohori-0.1.0/tests/unit/__init__.py +0 -0
  186. prohori-0.1.0/tests/unit/cli/__init__.py +0 -0
  187. prohori-0.1.0/tests/unit/cli/test_cli_golden.py +81 -0
  188. prohori-0.1.0/tests/unit/cli/test_detect_command.py +173 -0
  189. prohori-0.1.0/tests/unit/cli/test_incidents_command.py +192 -0
  190. prohori-0.1.0/tests/unit/cli/test_parse_command.py +180 -0
  191. prohori-0.1.0/tests/unit/cli/test_report_command.py +150 -0
  192. prohori-0.1.0/tests/unit/cli/test_rules_command.py +155 -0
  193. prohori-0.1.0/tests/unit/cli/test_simulate_command.py +82 -0
  194. prohori-0.1.0/tests/unit/cli/test_triage_demo.py +219 -0
  195. prohori-0.1.0/tests/unit/enrich/__init__.py +0 -0
  196. prohori-0.1.0/tests/unit/enrich/conftest.py +42 -0
  197. prohori-0.1.0/tests/unit/enrich/test_addresses.py +120 -0
  198. prohori-0.1.0/tests/unit/enrich/test_cache_ratelimit.py +167 -0
  199. prohori-0.1.0/tests/unit/enrich/test_cli.py +118 -0
  200. prohori-0.1.0/tests/unit/enrich/test_enricher.py +238 -0
  201. prohori-0.1.0/tests/unit/enrich/test_geolite_offline_config.py +217 -0
  202. prohori-0.1.0/tests/unit/enrich/test_http_providers.py +318 -0
  203. prohori-0.1.0/tests/unit/incidents/conftest.py +5 -0
  204. prohori-0.1.0/tests/unit/incidents/factories.py +83 -0
  205. prohori-0.1.0/tests/unit/incidents/test_attack_runbooks.py +140 -0
  206. prohori-0.1.0/tests/unit/incidents/test_builder.py +263 -0
  207. prohori-0.1.0/tests/unit/incidents/test_events_novelty.py +137 -0
  208. prohori-0.1.0/tests/unit/incidents/test_grouping.py +138 -0
  209. prohori-0.1.0/tests/unit/incidents/test_scoring.py +206 -0
  210. prohori-0.1.0/tests/unit/parsers/__init__.py +0 -0
  211. prohori-0.1.0/tests/unit/parsers/test_access_log.py +178 -0
  212. prohori-0.1.0/tests/unit/parsers/test_auth_log.py +663 -0
  213. prohori-0.1.0/tests/unit/parsers/test_base.py +141 -0
  214. prohori-0.1.0/tests/unit/parsers/test_json_parsers.py +361 -0
  215. prohori-0.1.0/tests/unit/reports/__init__.py +0 -0
  216. prohori-0.1.0/tests/unit/reports/conftest.py +90 -0
  217. prohori-0.1.0/tests/unit/reports/test_redact.py +179 -0
  218. prohori-0.1.0/tests/unit/reports/test_render.py +177 -0
  219. prohori-0.1.0/tests/unit/rules/__init__.py +0 -0
  220. prohori-0.1.0/tests/unit/rules/test_builtin_rules.py +124 -0
  221. prohori-0.1.0/tests/unit/rules/test_compiler_loader.py +414 -0
  222. prohori-0.1.0/tests/unit/rules/test_condition.py +148 -0
  223. prohori-0.1.0/tests/unit/rules/test_engine.py +556 -0
  224. prohori-0.1.0/tests/unit/rules/test_matching.py +249 -0
  225. prohori-0.1.0/tests/unit/rules/test_regex_safety.py +88 -0
  226. prohori-0.1.0/tests/unit/rules/test_rule_testing.py +233 -0
  227. prohori-0.1.0/tests/unit/rules/test_verify_attack_script.py +72 -0
  228. prohori-0.1.0/tests/unit/schema/__init__.py +0 -0
  229. prohori-0.1.0/tests/unit/schema/strategies.py +80 -0
  230. prohori-0.1.0/tests/unit/schema/test_alert_incident.py +156 -0
  231. prohori-0.1.0/tests/unit/schema/test_event.py +191 -0
  232. prohori-0.1.0/tests/unit/schema/test_fields_serialization.py +114 -0
  233. prohori-0.1.0/tests/unit/simulate/__init__.py +0 -0
  234. prohori-0.1.0/tests/unit/simulate/test_evaluate.py +114 -0
  235. prohori-0.1.0/tests/unit/simulate/test_simulate.py +169 -0
  236. prohori-0.1.0/tests/unit/test_docs.py +25 -0
  237. prohori-0.1.0/tests/unit/test_release_notes.py +43 -0
  238. prohori-0.1.0/tests/unit/test_skeleton.py +61 -0
@@ -0,0 +1,70 @@
1
+ # --- Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ *.egg
9
+
10
+ # --- Virtual environments
11
+ .venv/
12
+ venv/
13
+ env/
14
+
15
+ # --- Tooling caches and reports
16
+ .pytest_cache/
17
+ .mypy_cache/
18
+ .ruff_cache/
19
+ .hypothesis/
20
+ .coverage
21
+ .coverage.*
22
+ coverage.xml
23
+ htmlcov/
24
+ .tox/
25
+ .nox/
26
+
27
+ # --- Secrets and local configuration
28
+ .env
29
+ .env.*
30
+ !.env.example
31
+ *.local.toml
32
+ *.local.yaml
33
+ prohori.local.*
34
+
35
+ # --- Datasets, models and generated artefacts (never committed)
36
+ data/*
37
+ !data/README.md
38
+ results/checkpoints/
39
+ models/
40
+ *.joblib
41
+ *.pkl
42
+ *.pickle
43
+ *.onnx
44
+ *.parquet
45
+ *.mmdb
46
+ *.sqlite
47
+ *.sqlite3
48
+ *.db
49
+
50
+ # --- Notebooks
51
+ .ipynb_checkpoints/
52
+
53
+ # --- Pipeline runtime state
54
+ pipeline/.volumes/
55
+ pipeline/dbt/target/
56
+ pipeline/dbt/logs/
57
+ pipeline/dbt/dbt_packages/
58
+ pipeline/airflow/logs/
59
+
60
+ # --- Editors and OS
61
+ .vscode/
62
+ .idea/
63
+ *.swp
64
+ .DS_Store
65
+ Thumbs.db
66
+
67
+ # Simulator output written by examples in the docs
68
+ /out/
69
+ /prohori-demo/
70
+ /triage/
@@ -0,0 +1,145 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
6
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-10-27
11
+
12
+ First public release: the batch core, from a log file to scored incidents and shareable reports,
13
+ offline by default.
14
+
15
+ ### Highlights
16
+
17
+ - `prohori demo`: simulated attacks triaged offline in seconds, with no configuration.
18
+ - `prohori triage FILE`: parse, detect, enrich, build incidents and write a report per incident.
19
+ - Parsers for Linux `auth.log`, nginx/Apache access logs and JSON Lines (app layout or a field
20
+ mapping), into one typed event schema.
21
+ - 15 YAML detection rules with fixtures, runbooks and ATT&CK v20.0 tags, run by a stateful engine
22
+ (thresholds, windows, sequences, time of day).
23
+ - Optional IP enrichment (GeoLite2, AbuseIPDB, VirusTotal) that never sends private or
24
+ documentation addresses, with caching and rate limits.
25
+ - Incidents with a points-based score and its breakdown, confidence and limitations; reports in
26
+ Markdown, self-contained HTML, JSON and CSV, with fail-closed redaction.
27
+ - A labelled attack simulator and published per-rule precision and recall, including held-out
28
+ variants that show where the rules miss.
29
+
30
+ ### Added
31
+
32
+ - Release workflow (`.github/workflows/release.yml`): tag-triggered build, `twine check`, wheel
33
+ smoke tests on Linux and Windows, TestPyPI, PyPI after a maintainer's approval (trusted
34
+ publishing, no tokens), a GitHub release with the changelog section, and an install-from-PyPI
35
+ check. Documented in `docs/RELEASING.md`.
36
+ - `prohori triage FILE|-`: parse, detect, enrich (offline unless `--online`), build incidents and
37
+ write `incidents.jsonl`, one report per incident (`--format md|html|json|csv`) and
38
+ `summary.md`; `--redact` / `--redact-ips` redact every file with one key per run.
39
+ - `prohori demo`: simulates an account takeover, an SSH brute force and a web scan, triages them
40
+ offline with no configuration and shows whether every expected rule fired.
41
+ - `prohori version` shows the ATT&CK release and a fingerprint of the built-in rule set.
42
+ - Command-line reference `docs/CLI.md`, sample reports in `docs/examples/`, a terminal capture of
43
+ the demo (`docs/images/demo.svg`) and `scripts/generate_examples.py` to regenerate them.
44
+ - Golden tests for the table output of the main commands, a `--help` test for every command and
45
+ an end-to-end test (simulator, triage, incidents) against the simulator's ground truth.
46
+ - Log simulator (`prohori.simulate`) and `prohori simulate SCENARIO --seed N --out DIR
47
+ [--variant default|heldout] [--set NAME=VALUE] [--list]`: eight labelled attack scenarios
48
+ (SSH brute force, password spraying, credential stuffing, refresh-token reuse, account takeover
49
+ with a payout change, web scanning, injection attempts, admin misuse) in realistic benign
50
+ background activity, as `auth.log`, access-log or app-JSON files with per-line labels and a
51
+ manifest. Byte-identical for the same seed; documentation IP ranges only.
52
+ - Rule evaluation: `scripts/evaluate_rules.py` writes per-rule precision and recall for the
53
+ default and held-out variants to `results/rules_eval.json`; `scripts/generate_results_table.py`
54
+ puts the tables in `docs/SIMULATOR.md`.
55
+ - Time-of-day matching in rules: a `timezone` key and the `time.hour`, `time.weekday` and
56
+ `time.minute_of_day` fields.
57
+ - Built-in rule PRH-ADMIN-002: admin actions between 22:00 and 07:00 (Asia/Dhaka), with fixtures
58
+ and a runbook (15 built-in rules in total).
59
+ - Runbook actions starting with `For SSH,` or `For the web app,` are shown only for incidents
60
+ with that kind of event.
61
+ - Incident reports (`prohori.reports`) and `prohori report INCIDENT [-f md|html|json|csv]`:
62
+ header, summary, timeline, indicators with enrichment, entities, evidence, ATT&CK mapping,
63
+ score breakdown, recommended actions, confidence and limitations, and an appendix with input
64
+ and rule-file SHA-256 hashes and versions. HTML is one self-contained file with a strict
65
+ Content-Security-Policy; Markdown escapes every value; CSV cells cannot become formulas.
66
+ Documented in `docs/REPORTS.md`.
67
+ - `--redact` and `--redact-ips`: account names, ids, email hashes, emails and optionally IP
68
+ addresses become keyed pseudonyms (HMAC-SHA256, random key per run); the command refuses to
69
+ write a report that would still contain an original value.
70
+ - `Incident.provenance` (tool version, input files with SHA-256, rules fired with rule-file
71
+ SHA-256, enrichment mode), filled in by `prohori incidents`; `TimelineEntry.account` and
72
+ `TimelineEntry.source_ip`.
73
+ - Sample incident `samples/incidents/INC-ee7c8818fbb34989.json`.
74
+ - `jinja2` is now a core dependency.
75
+ - Incident builder (`prohori.incidents`) and `prohori incidents FILE|-`: alerts that share an IP,
76
+ account, request or session within a time gap become one incident, with a points-based score
77
+ and its breakdown, severity, confidence, ATT&CK mapping (with the ATT&CK version), a timeline,
78
+ enriched indicators, recommended actions from the runbooks, and limitations. Sign-in novelty
79
+ (a new IP or client for an account) is incident context from a bounded per-account history.
80
+ Shared IP addresses stop linking alerts after 10 accounts. Enrichment is offline unless
81
+ `--online` is given. Documented in `docs/INCIDENTS.md`.
82
+ - `Incident.attack_version` and `prohori.schema.incident_id()` (deterministic incident ids).
83
+ - Sample `samples/logs/account-takeover.jsonl`: brute force, sign-in, 2FA disabled, payout change.
84
+ - IP enrichment (`prohori.enrich`) and `prohori enrich IP... [--offline]`: providers for local
85
+ MaxMind GeoLite2 databases (`prohori[geoip]` extra), AbuseIPDB and VirusTotal, a bundled
86
+ synthetic fixture for offline runs, a SQLite cache with TTLs, daily budgets, a token bucket,
87
+ bounded retries and timeouts. Private, reserved and documentation addresses are never sent to
88
+ external services. Documented in `docs/ENRICHMENT.md`; third-party terms in `docs/LICENSES.md`.
89
+ - `httpx` is now a core dependency.
90
+ - `prohori detect FILE|-`: parse a log file and run the built-in rules plus any `--rules PATH`,
91
+ with table or JSON Lines output, out-of-order tolerance (`--lateness`) and exit code 3 when
92
+ alerts are found (`--exit-zero` to disable).
93
+ - Built-in rules PRH-ABUSE-001, PRH-ADMIN-001, PRH-WEB-001 to 004 and PRH-SSH-001, with fixtures
94
+ and runbooks (14 built-in rules in total).
95
+ - `urldecode` rule modifier: percent-decode a field before matching.
96
+ - Built-in rules PRH-AUTH-001 to 004, PRH-SESS-001, PRH-ACCT-001 and PRH-AUTHZ-001, each with
97
+ fixtures (including real log lines through the parsers) and a runbook in `prohori/runbooks/`.
98
+ - ATT&CK tags verified against Enterprise ATT&CK v20.0 (`prohori.rules.attack`), with
99
+ `scripts/verify_attack.py` to re-check the list against the official STIX data.
100
+ - Stateful rule engine (`prohori.rules.engine`): single-pass evaluation of stateless rules,
101
+ `aggregate` thresholds (count, distinct, ratio; sliding and tumbling windows) and `sequence`
102
+ rules, with a reorder buffer for out-of-order events, protection against bogus future
103
+ timestamps, and bounded state with eviction counters.
104
+ - Rule test harness and `prohori rules test`: each rule runs against its `<rule>.tests.yml`
105
+ fixtures (generated event timelines or real log lines through a parser).
106
+ - Detection rule format (`prohori.rules`): YAML rules with selections, field modifiers
107
+ (`contains`, `startswith`, `endswith`, `re`, `cidr`, `gt`/`gte`/`lt`/`lte`, `exists`, `all`,
108
+ `cased`), wildcards, a boolean `condition` language, and `aggregate` / `sequence` blocks that are
109
+ validated now and evaluated by the upcoming stateful engine. Documented in `docs/RULES.md`.
110
+ - Safe rule loading: size, depth and node limits, safe YAML only, no anchors or aliases, and every
111
+ error reported with file, line and path.
112
+ - Rule regular expressions run on RE2 (`google-re2`), so matching time is linear in the input and
113
+ no pattern can hang a run.
114
+ - `prohori rules list` and `prohori rules validate`.
115
+ - `prohori parse FILE|-`: parse and normalise logs with format detection, table or JSON Lines
116
+ output, a parse summary with error examples, and exit codes (`0` ok, `1` every line failed,
117
+ `2` usage error).
118
+ - `access_log` parser for nginx and Apache combined/common log formats, with custom patterns
119
+ and query strings kept in `extra.query`.
120
+ - `app_json` parser for application security events in JSON Lines.
121
+ - `generic_json` parser driven by a validated YAML field-mapping file.
122
+ - Sample access, application and generic JSON logs, and a sample mapping file.
123
+ - `pyyaml` and `rich` as core dependencies.
124
+ - Parser framework (`prohori.parsers`): bounded binary line reading, sanitising of control,
125
+ ANSI and invisible characters, per-run statistics with error examples, and a parser registry.
126
+ - `auth_log` parser for Linux `auth.log` / `secure`: sshd logins, invalid users, too many
127
+ authentication failures, disconnects, `pam_unix` and sudo lines; RFC 3164 and RFC 3339
128
+ timestamps with year inference, New Year rollover and configurable timezone.
129
+ Documented in `docs/PARSERS.md`.
130
+ - Synthetic sample logs in `samples/logs/`.
131
+ - Atheris fuzz harness for the parser, run in CI.
132
+ - `tzdata` dependency on Windows for IANA timezone support.
133
+ - Common schema (`prohori.schema`): immutable `Event`, `Alert` and `Incident` models with UTC-only
134
+ timestamps, IPv4/IPv6 validation, truncation of untrusted text, deterministic ids, dotted-path
135
+ field access and JSON / JSON Lines helpers. Documented in `docs/SCHEMA.md`.
136
+ - `pydantic` as a core dependency.
137
+ - Project skeleton: `src/` layout package with typed, importable subpackages.
138
+ - `prohori` command-line entry point with `--version` and `version`.
139
+ - Tooling: uv, Ruff, mypy (strict), pytest with coverage, pre-commit with secret scanning.
140
+ - Continuous integration on Linux and Windows for Python 3.11 to 3.14, CodeQL and Dependabot.
141
+ - Contributor, security and code-of-conduct documents.
142
+ - OpenSSF Scorecard workflow, README status badges and a project wiki.
143
+
144
+ [Unreleased]: https://github.com/UtshaBasak/Prohori/compare/v0.1.0...HEAD
145
+ [0.1.0]: https://github.com/UtshaBasak/Prohori/releases/tag/v0.1.0
prohori-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Utsha Basak
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
prohori-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,376 @@
1
+ Metadata-Version: 2.5
2
+ Name: prohori
3
+ Version: 0.1.0
4
+ Summary: A laptop-sized mini-SOC: from raw logs to detections, enriched incidents and analyst-ready reports.
5
+ Project-URL: Homepage, https://github.com/UtshaBasak/Prohori
6
+ Project-URL: Repository, https://github.com/UtshaBasak/Prohori
7
+ Project-URL: Issues, https://github.com/UtshaBasak/Prohori/issues
8
+ Project-URL: Changelog, https://github.com/UtshaBasak/Prohori/blob/main/CHANGELOG.md
9
+ Author: Utsha Basak
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: detection-engineering,incident-response,log-analysis,security,siem,soc,threat-detection
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Information Technology
16
+ Classifier: Intended Audience :: System Administrators
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Topic :: Security
25
+ Classifier: Topic :: System :: Logging
26
+ Classifier: Topic :: System :: Monitoring
27
+ Classifier: Typing :: Typed
28
+ Requires-Python: >=3.11
29
+ Requires-Dist: google-re2>=1.1
30
+ Requires-Dist: httpx>=0.28
31
+ Requires-Dist: jinja2>=3.1
32
+ Requires-Dist: pydantic>=2.10
33
+ Requires-Dist: pyyaml>=6.0
34
+ Requires-Dist: rich>=13.0
35
+ Requires-Dist: typer>=0.15
36
+ Requires-Dist: tzdata>=2024.1
37
+ Provides-Extra: all
38
+ Requires-Dist: geoip2>=5.0; extra == 'all'
39
+ Provides-Extra: geoip
40
+ Requires-Dist: geoip2>=5.0; extra == 'geoip'
41
+ Provides-Extra: llm
42
+ Provides-Extra: ml
43
+ Provides-Extra: pipeline
44
+ Description-Content-Type: text/markdown
45
+
46
+ # Prohori (প্রহরী)
47
+
48
+ <!-- Build and security -->
49
+ [![CI](https://github.com/UtshaBasak/Prohori/actions/workflows/ci.yml/badge.svg)](https://github.com/UtshaBasak/Prohori/actions/workflows/ci.yml)
50
+ [![CodeQL](https://github.com/UtshaBasak/Prohori/actions/workflows/codeql.yml/badge.svg)](https://github.com/UtshaBasak/Prohori/actions/workflows/codeql.yml)
51
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/UtshaBasak/Prohori/badge)](https://scorecard.dev/viewer/?uri=github.com/UtshaBasak/Prohori)
52
+ [![Secret scanning: gitleaks](https://img.shields.io/badge/secrets-gitleaks-blue?logo=git)](https://github.com/gitleaks/gitleaks)
53
+ [![Dependabot](https://img.shields.io/badge/dependabot-enabled-025E8C?logo=dependabot)](https://github.com/UtshaBasak/Prohori/blob/main/.github/dependabot.yml)
54
+
55
+ <!-- Package -->
56
+ [![PyPI](https://img.shields.io/pypi/v/prohori?logo=pypi&logoColor=white)](https://pypi.org/project/prohori/)
57
+ [![Python versions](https://img.shields.io/pypi/pyversions/prohori?logo=python&logoColor=white)](https://pypi.org/project/prohori/)
58
+ [![Wheel](https://img.shields.io/pypi/wheel/prohori)](https://pypi.org/project/prohori/#files)
59
+ [![Downloads](https://img.shields.io/pypi/dm/prohori)](https://pypistats.org/packages/prohori)
60
+ [![GitHub release](https://img.shields.io/github/v/release/UtshaBasak/Prohori)](https://github.com/UtshaBasak/Prohori/releases)
61
+ [![Release workflow](https://github.com/UtshaBasak/Prohori/actions/workflows/release.yml/badge.svg)](https://github.com/UtshaBasak/Prohori/actions/workflows/release.yml)
62
+ ![Status: alpha](https://img.shields.io/badge/status-alpha-yellow)
63
+ ![Platforms](https://img.shields.io/badge/platform-Linux%20%7C%20Windows%20%7C%20macOS-lightgrey)
64
+ [![License: MIT](https://img.shields.io/github/license/UtshaBasak/Prohori)](https://github.com/UtshaBasak/Prohori/blob/main/LICENSE)
65
+
66
+ <!-- Tooling and conventions -->
67
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
68
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
69
+ [![Checked with mypy](https://img.shields.io/badge/mypy-strict-2A6DB2)](https://mypy-lang.org/)
70
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
71
+ [![Conventional Commits](https://img.shields.io/badge/conventional%20commits-1.0.0-FE5196?logo=conventionalcommits&logoColor=white)](https://www.conventionalcommits.org/)
72
+ [![Contributor Covenant](https://img.shields.io/badge/contributor%20covenant-2.1-4baaaa)](https://github.com/UtshaBasak/Prohori/blob/main/CODE_OF_CONDUCT.md)
73
+
74
+ <!-- Repository -->
75
+ [![Last commit](https://img.shields.io/github/last-commit/UtshaBasak/Prohori)](https://github.com/UtshaBasak/Prohori/commits/main)
76
+ [![Commit activity](https://img.shields.io/github/commit-activity/m/UtshaBasak/Prohori)](https://github.com/UtshaBasak/Prohori/graphs/commit-activity)
77
+ [![Open issues](https://img.shields.io/github/issues/UtshaBasak/Prohori)](https://github.com/UtshaBasak/Prohori/issues)
78
+ [![Pull requests](https://img.shields.io/github/issues-pr/UtshaBasak/Prohori)](https://github.com/UtshaBasak/Prohori/pulls)
79
+ [![Wiki](https://img.shields.io/badge/docs-wiki-blue?logo=github)](https://github.com/UtshaBasak/Prohori/wiki)
80
+ [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](https://github.com/UtshaBasak/Prohori/blob/main/CONTRIBUTING.md)
81
+
82
+ **A laptop-sized mini-SOC: from raw logs to detections, enriched incidents and analyst-ready
83
+ reports, with honest machine-learning evaluation and a streaming data pipeline.**
84
+
85
+ *Prohori* (প্রহরী) is Bengali for "guard". It is an open-source, defensive security-operations
86
+ toolkit in Python that runs on a single laptop with free tools only, offline by default.
87
+
88
+ > **Status: alpha (v0.1.0).** The batch core is complete: one command goes from a log file to
89
+ > scored incidents and shareable reports, and a labelled attack simulator measures the rules.
90
+ > Machine learning, the streaming pipeline and the LLM analyst follow in later releases. See the
91
+ > [CHANGELOG](https://github.com/UtshaBasak/Prohori/blob/main/CHANGELOG.md) and the [roadmap](#roadmap).
92
+
93
+ ## 60-second demo
94
+
95
+ ```bash
96
+ pipx install prohori # or: pip install prohori
97
+ prohori demo
98
+ ```
99
+
100
+ ![prohori demo: three simulated attacks, the incidents found and every expected rule fired](https://raw.githubusercontent.com/UtshaBasak/Prohori/main/docs/images/demo.svg)
101
+
102
+ The demo simulates an account takeover, an SSH brute force and a web scan, triages each one
103
+ offline and writes an HTML report per incident to `prohori-demo/`. It needs no configuration and
104
+ sends nothing over the network. Sample reports: [Markdown](https://github.com/UtshaBasak/Prohori/blob/main/docs/examples/account-takeover.md),
105
+ [HTML](https://github.com/UtshaBasak/Prohori/blob/main/docs/examples/account-takeover.html) (download and open it) and
106
+ [redacted](https://github.com/UtshaBasak/Prohori/blob/main/docs/examples/account-takeover-redacted.md).
107
+
108
+ ## Contents
109
+
110
+ - [What it does](#what-it-does)
111
+ - [Install](#install)
112
+ - [Quick start](#quick-start)
113
+ - [Example: an account takeover](#example-an-account-takeover)
114
+ - [Commands](#commands)
115
+ - [Built-in detection rules](#built-in-detection-rules)
116
+ - [How well the rules work](#how-well-the-rules-work)
117
+ - [Privacy and safety](#privacy-and-safety)
118
+ - [Documentation](#documentation)
119
+ - [Limitations](#limitations)
120
+ - [Roadmap](#roadmap)
121
+ - [Development](#development)
122
+
123
+ ## What it does
124
+
125
+ ```mermaid
126
+ flowchart LR
127
+ A[Log files<br>auth.log, nginx, app JSON] --> B[parse<br>common event schema]
128
+ B --> C[detect<br>15 YAML rules]
129
+ C --> D[incidents<br>group, score, ATT&CK]
130
+ E[enrich<br>GeoLite2, AbuseIPDB, VirusTotal] --> D
131
+ D --> F[report<br>Markdown, HTML, JSON, CSV]
132
+ ```
133
+
134
+ | Stage | Today |
135
+ |-------|-------|
136
+ | **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). |
137
+ | **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. |
138
+ | **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. |
139
+ | **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. |
140
+ | **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. |
141
+ | **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. |
142
+
143
+ Prohori only **recommends** actions. It never blocks addresses, disables accounts or touches
144
+ remote systems.
145
+
146
+ ## Install
147
+
148
+ Prohori needs Python 3.11 or newer. CI tests it on Linux and Windows; macOS should work but is
149
+ not tested.
150
+
151
+ - **From PyPI:** `pipx install prohori` puts the `prohori` command in its own environment;
152
+ `pip install prohori` works in any virtual environment. Local GeoLite2 support is an extra:
153
+ `pip install "prohori[geoip]"`.
154
+ - **From source:** install [uv](https://docs.astral.sh/uv/), then
155
+ `git clone https://github.com/UtshaBasak/Prohori.git && cd Prohori && uv sync`, and run
156
+ commands as `uv run prohori ...`. The sample files used below are in the repository.
157
+
158
+ ## Quick start
159
+
160
+ Everything below runs offline on the synthetic samples in the repository (run from a clone).
161
+
162
+ ```bash
163
+ uv run prohori triage samples/logs/account-takeover.jsonl -o triage/ # the whole pipeline
164
+ uv run prohori parse samples/logs/auth.log # normalise a log
165
+ uv run prohori detect samples/logs/auth.log # run the 15 rules
166
+ uv run prohori enrich 203.0.113.7 --offline # look up an IP (demo data)
167
+ uv run prohori incidents samples/logs/account-takeover.jsonl # group alerts into incidents
168
+ uv run prohori report samples/incidents/INC-ee7c8818fbb34989.json -f html -o report.html
169
+ uv run prohori simulate account-takeover --seed 1 --out out/ # your own labelled attack
170
+ ```
171
+
172
+ `prohori triage` writes `triage/summary.md`, `triage/incidents.jsonl` and an HTML report per
173
+ incident. Open a report in a browser: one file, no scripts, no external resources. For your own
174
+ logs, `prohori triage /var/log/auth.log --year 2026 -o triage/` works the same way (add
175
+ `--redact-ips` before sharing the output).
176
+
177
+
178
+ ## Example: an account takeover
179
+
180
+ `samples/logs/account-takeover.jsonl` is a synthetic attack on a web shop account: twelve wrong
181
+ passwords from one IP in 33 seconds, a successful sign-in, two-step verification switched off and
182
+ the bKash payout number changed. Prohori raises four alerts and joins them into **one critical
183
+ incident**:
184
+
185
+ ```text
186
+ $ uv run prohori incidents samples/logs/account-takeover.jsonl # first lines
187
+ INC-ee7c8818fbb34989 critical score 100
188
+ +-------------------------------------------------------------------------------------------------+
189
+ | title | Successful sign-in after many failures from one IP and 3 related alerts |
190
+ | time (UTC) | 2026-10-24 21:10:00 - 2026-10-24 21:13:10 |
191
+ | confidence | high |
192
+ | rules | PRH-ACCT-001, PRH-AUTH-001, PRH-AUTH-003 |
193
+ | entities | source.ip: 203.0.113.7 |
194
+ | | user.email_hash: 0cc175b9c0f1b6a831c399e269772661 |
195
+ | | user.id: u-2001 |
196
+ ```
197
+
198
+ Every point of the score is explained (from `prohori report`):
199
+
200
+ | Points | Reason |
201
+ |-------:|--------|
202
+ | +50 | Most severe alert: PRH-AUTH-003 (critical) |
203
+ | +20 | 2 more rules fired: PRH-ACCT-001, PRH-AUTH-001 |
204
+ | +20 | A sign-in succeeded after failed attempts |
205
+ | +15 | Sensitive account or admin change: account.2fa.disabled, account.bkash.changed |
206
+ | +20 | Reputation of 203.0.113.7: abuse confidence 97; 9 vendors flag it as malicious (from bundled demo data) |
207
+ | +10 | Sign-in to u-2001 from 203.0.113.7, an IP not seen for this account before (1 earlier IP) |
208
+ | +5 | Sign-in to u-2001 with client web, not seen for this account before |
209
+ | -40 | Capped at 100 |
210
+ | **100** | **Total** |
211
+
212
+ The report adds the timeline, the IP's enrichment, the ATT&CK techniques (T1110 Brute Force,
213
+ T1078 Valid Accounts, T1098 Account Manipulation, T1556.006 Multi-Factor Authentication),
214
+ recommended actions from the runbooks and the limitations, such as "single log source" and
215
+ "enrichment is bundled demo data".
216
+
217
+ ## Commands
218
+
219
+ | Command | What it does | Docs |
220
+ |---------|--------------|------|
221
+ | `prohori demo` | Simulate attacks, triage them and show the results; no configuration, offline | [CLI.md](https://github.com/UtshaBasak/Prohori/blob/main/docs/CLI.md#prohori-demo) |
222
+ | `prohori triage FILE` | Log in, incidents and a report per incident out (`--format`, `--redact`, `--online`) | [CLI.md](https://github.com/UtshaBasak/Prohori/blob/main/docs/CLI.md#prohori-triage) |
223
+ | `prohori parse FILE` | Parse and normalise a log; table or JSON Lines | [PARSERS.md](https://github.com/UtshaBasak/Prohori/blob/main/docs/PARSERS.md) |
224
+ | `prohori detect FILE` | Run the 15 built-in rules (and `--rules DIR`); exit code 3 when alerts are found | [RULES.md](https://github.com/UtshaBasak/Prohori/blob/main/docs/RULES.md) |
225
+ | `prohori rules list` / `validate` / `test` | Show, check and test detection rules | [RULES.md](https://github.com/UtshaBasak/Prohori/blob/main/docs/RULES.md) |
226
+ | `prohori enrich IP...` | Geolocation and reputation; `--offline` never uses the network | [ENRICHMENT.md](https://github.com/UtshaBasak/Prohori/blob/main/docs/ENRICHMENT.md) |
227
+ | `prohori incidents FILE` | Parse, detect and group alerts into scored incidents; offline enrichment unless `--online` | [INCIDENTS.md](https://github.com/UtshaBasak/Prohori/blob/main/docs/INCIDENTS.md) |
228
+ | `prohori report INCIDENT` | Render an incident as `md`, `html`, `json` or `csv`; `--redact`, `--redact-ips` | [REPORTS.md](https://github.com/UtshaBasak/Prohori/blob/main/docs/REPORTS.md) |
229
+ | `prohori simulate SCENARIO --out DIR` | Write a labelled attack scenario (`--list`, `--seed`, `--variant heldout`, `--set`) | [SIMULATOR.md](https://github.com/UtshaBasak/Prohori/blob/main/docs/SIMULATOR.md) |
230
+ | `prohori version` | The version, ATT&CK release and built-in rule set | |
231
+
232
+ Commands that take a file also read standard input with `-`, and every command has `--help`.
233
+ Exit codes are the same everywhere: `0` nothing found, `3` alerts or incidents found
234
+ (`--exit-zero` turns this off for CI pipelines), `1` nothing could be processed and `2` invalid
235
+ usage. The full reference is [docs/CLI.md](https://github.com/UtshaBasak/Prohori/blob/main/docs/CLI.md).
236
+
237
+ ## Built-in detection rules
238
+
239
+ | Rule | Detects | Severity | ATT&CK |
240
+ |------|---------|----------|--------|
241
+ | PRH-AUTH-001 | Brute-force sign-in attempts from one IP | high | T1110, T1110.001 |
242
+ | PRH-AUTH-002 | Password spraying or credential stuffing from one IP | high | T1110.003, T1110.004 |
243
+ | PRH-AUTH-003 | Successful sign-in after many failures from one IP | critical | T1110, T1078 |
244
+ | PRH-AUTH-004 | Account lockouts across many accounts | high | T1110 |
245
+ | PRH-SESS-001 | Refresh-token reuse | high | T1528, T1550.001 |
246
+ | PRH-ACCT-001 | Sensitive account change shortly after sign-in | high | T1098, T1556.006 |
247
+ | PRH-AUTHZ-001 | Authorisation denials across many resources | medium | T1595.003 |
248
+ | PRH-ABUSE-001 | Rate-limit bursts or bot-check failures from one IP | medium | T1499.002 |
249
+ | PRH-ADMIN-001 | Admin role granted | high | T1098 |
250
+ | PRH-ADMIN-002 | Admin action at an unusual hour (22:00-07:00 local time) | medium | T1078 |
251
+ | PRH-WEB-001 | Web scanning from one IP | medium | T1595.003 |
252
+ | PRH-WEB-002 | Injection patterns in request paths | medium | T1190 |
253
+ | PRH-WEB-003 | Scanner or empty user agent at a high request rate | medium | T1595.002 |
254
+ | PRH-WEB-004 | Probes for sensitive files and admin tools | medium | T1595.003 |
255
+ | PRH-SSH-001 | SSH invalid-user storm or repeated authentication-limit disconnects | high | T1110, T1110.001 |
256
+
257
+ ATT&CK tags are verified against the official Enterprise ATT&CK v20.0 data. Each rule has a
258
+ runbook in [`src/prohori/runbooks/`](https://github.com/UtshaBasak/Prohori/blob/main/src/prohori/runbooks/). Writing your own rules is covered in
259
+ [docs/RULES.md](https://github.com/UtshaBasak/Prohori/blob/main/docs/RULES.md).
260
+
261
+ ## How well the rules work
262
+
263
+ `prohori simulate` writes each attack scenario in two variants: **default**, the attack the rules
264
+ were designed for, and **held-out**, the same attack done the way a careful attacker would
265
+ (slower, from more addresses, with a browser user agent, obfuscated, or at another hour).
266
+ `scripts/evaluate_rules.py` runs all rules over five seeds of each and compares the alerts with
267
+ the line labels.
268
+
269
+ <!-- results:start -->
270
+ | | Default | Held-out |
271
+ |--|--------:|---------:|
272
+ | Attacks caught by at least one expected rule | 40/40 | 35/40 |
273
+
274
+ Expected rules that missed in the held-out variants (runs missed out of runs):
275
+
276
+ - `account-takeover`: PRH-ACCT-001 3/5, PRH-AUTH-001 5/5
277
+ - `admin-misuse`: PRH-ADMIN-002 5/5
278
+ - `credential-stuffing`: PRH-AUTH-001 5/5, PRH-AUTH-002 5/5, PRH-AUTH-003 5/5
279
+ - `password-spraying`: PRH-AUTH-001 5/5
280
+ - `ssh-bruteforce`: PRH-AUTH-001 5/5
281
+ - `web-scanning`: PRH-WEB-003 5/5
282
+ <!-- results:end -->
283
+
284
+ PRH-ACCT-001 has low precision on purpose: it flags every sensitive change soon after a sign-in,
285
+ including the owners' own. These are synthetic results written by the rules' author; the full
286
+ per-rule table and caveats are in [docs/SIMULATOR.md](https://github.com/UtshaBasak/Prohori/blob/main/docs/SIMULATOR.md#results).
287
+
288
+ ## Privacy and safety
289
+
290
+ - **Offline by default.** Nothing leaves the machine unless you configure API keys and, for
291
+ incidents, pass `--online`. Tests block real network access.
292
+ - **Private, reserved and documentation IP addresses are never sent** to outside services, even
293
+ when keys are configured. VirusTotal keeps the addresses it is asked about, so querying is a
294
+ deliberate choice.
295
+ - **Logs are untrusted input.** Parsers cap line lengths and strip control characters; reports
296
+ escape every value for their format (HTML, Markdown, CSV formulas) and are tested with hostile
297
+ input.
298
+ - **Shareable reports.** `--redact` / `--redact-ips` use keyed pseudonyms and fail closed.
299
+ - **No secrets in the repository.** API keys live in the environment or a git-ignored `.env`;
300
+ gitleaks runs on every commit and in CI.
301
+ - **Samples are synthetic** and use documentation address ranges only.
302
+
303
+ Third-party data terms (MITRE ATT&CK, GeoLite2, AbuseIPDB, VirusTotal) are summarised in
304
+ [docs/LICENSES.md](https://github.com/UtshaBasak/Prohori/blob/main/docs/LICENSES.md).
305
+
306
+ ## Documentation
307
+
308
+ | Document | Contents |
309
+ |----------|----------|
310
+ | [CLI reference](https://github.com/UtshaBasak/Prohori/blob/main/docs/CLI.md) | Every command, the shared options, formats and exit codes |
311
+ | [Architecture](https://github.com/UtshaBasak/Prohori/blob/main/docs/ARCHITECTURE.md) | Components, data flow and run modes |
312
+ | [Event schema](https://github.com/UtshaBasak/Prohori/blob/main/docs/SCHEMA.md) | Event, Alert and Incident fields |
313
+ | [Parsers](https://github.com/UtshaBasak/Prohori/blob/main/docs/PARSERS.md) | Supported formats, options and field mappings |
314
+ | [Rules](https://github.com/UtshaBasak/Prohori/blob/main/docs/RULES.md) | Rule format, evaluation semantics, testing and the built-in rules |
315
+ | [Enrichment](https://github.com/UtshaBasak/Prohori/blob/main/docs/ENRICHMENT.md) | Providers, privacy, cache, limits and configuration |
316
+ | [Incidents](https://github.com/UtshaBasak/Prohori/blob/main/docs/INCIDENTS.md) | Grouping, the scoring table, confidence and limitations |
317
+ | [Reports](https://github.com/UtshaBasak/Prohori/blob/main/docs/REPORTS.md) | Formats, sections, redaction and output safety |
318
+ | [Simulator](https://github.com/UtshaBasak/Prohori/blob/main/docs/SIMULATOR.md) | Attack scenarios, labels, and rule precision and recall |
319
+ | [Decisions](https://github.com/UtshaBasak/Prohori/blob/main/docs/DECISIONS.md) | Architecture decision records |
320
+ | [Releasing](https://github.com/UtshaBasak/Prohori/blob/main/docs/RELEASING.md) | How releases are built, tested and published |
321
+ | [Third-party terms](https://github.com/UtshaBasak/Prohori/blob/main/docs/LICENSES.md) | Data and service licences |
322
+ | [Wiki](https://github.com/UtshaBasak/Prohori/wiki) | Installation, quick start and guides |
323
+
324
+ ## Limitations
325
+
326
+ Prohori is honest about what it does not do:
327
+
328
+ - **It reads logs after the fact.** There is no live monitoring yet (the streaming pipeline is
329
+ planned for v0.3.0), and it never blocks, disables or changes anything.
330
+ - **Rules see only what the logs say.** Request bodies, headers and encrypted traffic are not
331
+ inspected; the web rules use the request line only.
332
+ - **Thresholds can be evaded.** The held-out evaluation shows that slow or widely distributed
333
+ attacks stay under the brute-force and spraying thresholds; see
334
+ [How well the rules work](#how-well-the-rules-work).
335
+ - **Some rules are noisy by design.** PRH-ACCT-001 flags every sensitive change shortly after a
336
+ sign-in, including the owner's own; incidents list such benign causes as limitations.
337
+ - **The evaluation is synthetic** and written by the rules' author. Test the rules on your own
338
+ logs before relying on them.
339
+ - **Enrichment free tiers** are small and non-commercial, and reputation data is evidence, not
340
+ proof.
341
+ - **Supported formats** are `auth.log`, nginx/Apache access logs and JSON Lines (built-in app
342
+ layout or a field mapping). Windows event logs and cloud audit logs are not supported yet.
343
+
344
+ ## Roadmap
345
+
346
+ | Release | Contents | State |
347
+ |---------|----------|-------|
348
+ | v0.1.0 | Batch core: parsers, rules, enrichment, incidents, reports, log simulator with rule evaluation, `triage` and `demo` commands | Released |
349
+ | v0.2.0 | Machine learning: leakage-controlled intrusion detection on public flow datasets, log-derived anomaly detection, model cards | Planned |
350
+ | v0.3.0 | Pipeline: streaming core, Redpanda and PostgreSQL in Docker, dbt models, dashboard, orchestration | Planned |
351
+ | v1.0.0 | Optional LLM analyst (opt-in, redacted input, validated output), final polish | Planned |
352
+
353
+ ## Development
354
+
355
+ ```bash
356
+ uv sync # create .venv with the package and dev tools
357
+ uv run pre-commit install # optional: run checks on every commit
358
+
359
+ uv run ruff check . # lint
360
+ uv run ruff format --check . # formatting
361
+ uv run mypy # strict type check
362
+ uv run pytest --cov # tests with coverage (kept at 85 % or more)
363
+ uv run prohori rules test # run every rule against its fixtures
364
+ ```
365
+
366
+ CI runs on Linux and Windows with Python 3.11 to 3.14, plus CodeQL, secret scanning, parser
367
+ fuzzing and the OpenSSF Scorecard. All GitHub Actions are pinned to commit SHAs. See
368
+ [CONTRIBUTING.md](https://github.com/UtshaBasak/Prohori/blob/main/CONTRIBUTING.md) for conventions.
369
+
370
+ ## Security
371
+
372
+ To report a vulnerability, see [SECURITY.md](https://github.com/UtshaBasak/Prohori/blob/main/SECURITY.md).
373
+
374
+ ## License
375
+
376
+ [MIT](https://github.com/UtshaBasak/Prohori/blob/main/LICENSE). Third-party data has its own terms; see [docs/LICENSES.md](https://github.com/UtshaBasak/Prohori/blob/main/docs/LICENSES.md).