Repository navigation
Detection Rules
Prohori detects suspicious activity with readable YAML rules. The full reference is
docs/RULES.md.
The format is inspired by Sigma but is smaller and adds thresholds and sequences. It is not Sigma-compatible.
| Id | Severity | Detects |
|---|---|---|
| PRH-AUTH-001 | high | 10+ failed sign-ins (app or SSH) from one IP in 5 minutes |
| PRH-AUTH-002 | high | Failed sign-ins to 5+ different accounts from one IP in 10 minutes (spraying, credential stuffing) |
| PRH-AUTH-003 | critical | 5+ failures then a successful sign-in from the same IP within 15 minutes |
| PRH-AUTH-004 | high | 3+ different accounts locked out within 15 minutes |
| PRH-SESS-001 | high | A refresh token used twice (probably stolen) |
| PRH-ACCT-001 | high | Password, payout number or two-step setting changed within 30 minutes of a sign-in |
| PRH-AUTHZ-001 | medium | Access refused to 5+ different paths from one IP in 10 minutes |
| PRH-ABUSE-001 | medium | 20+ rate-limit rejections or failed bot checks from one IP in 5 minutes |
| PRH-ADMIN-001 | high | An admin role granted |
| PRH-ADMIN-002 | medium | Any admin action between 22:00 and 07:00 Dhaka time (copy and adjust for other sites) |
| PRH-WEB-001 | medium | 403/404 responses for 10+ different paths from one IP in 5 minutes |
| PRH-WEB-002 | medium | 3+ requests with SQL injection, path traversal or script-injection text in 10 minutes |
| PRH-WEB-003 | medium | 30+ requests a minute with a scanning-tool or empty user agent |
| PRH-WEB-004 | medium | Probes for /.env, /.git/, /wp-login.php, /phpmyadmin and similar |
| PRH-SSH-001 | high | 5+ SSH attempts for nonexistent users or MaxAuthTries disconnects in 10 minutes |
Every built-in rule has test cases (including real log lines), a runbook with recommended actions,
and ATT&CK tags verified against ATT&CK Enterprise v20.0. Their measured precision and recall on
simulated attacks are on the Simulator page. Known limits of each rule are listed in
docs/RULES.md.
id: PRH-AUTH-001
title: Brute-force sign-in attempts from one IP
status: stable
severity: high
tags: [attack.credential_access, attack.t1110]
logsource: {category: authentication}
detection:
selection:
event.action: [auth.signin.failure, ssh.login.failure]
aggregate:
group_by: [source.ip]
window: 5m
count_gte: 10
condition: selection and aggregate
falsepositives: [Users behind a shared NAT]
response: runbooks/brute-force.md| Block | What it does |
|---|---|
| Selections | Named groups of field conditions, such as http.path|startswith: /admin
|
| Modifiers |
contains, startswith, endswith, re, cidr, gt/gte/lt/lte, exists, plus all, cased and urldecode
|
| Condition | Combines selections with and, or, not, parentheses and 1 of probe_*
|
aggregate |
Thresholds per group and time window: a count, distinct values, or a ratio |
sequence |
Ordered steps for the same entity, such as many failures followed by a success |
Strings match case-insensitively, * and ? are wildcards, and null means the field is
missing. Regular expressions run on RE2, so matching time is linear and no pattern can hang a run.
prohori rules validate path/to/rules/ # every problem with file and line
prohori rules list path/to/rules/ # valid rules with severity and ATT&CK tags
prohori rules test path/to/rules/ # run each rule against its test cases
prohori detect app.log --rules path/to/rules/ # run built-in and your rules over a log- Events are processed in one pass, in timestamp order. A configurable lateness lets slightly out-of-order events still count; events that arrive too late are dropped and counted.
- Sliding windows cover the last N minutes; tumbling windows cover fixed blocks such as each clock minute.
- When a threshold or sequence fires, that group starts again from zero, so one burst gives one alert.
- State is bounded: limits on buffered events and tracked groups, with counters when anything is evicted.
Every rule <name>.yml needs a test file <name>.tests.yml beside it, with at least one case that
should alert and one that should not. prohori rules test fails for a rule without one.
rule: PRH-AUTH-001
start: 2026-10-24T13:50:00Z
defaults: {log_source: app-security, event.category: authentication}
cases:
- name: ten failures within five minutes alert once
expect: {alerts: 1, entities: [{source.ip: 203.0.113.7}]}
events:
- {event.action: auth.signin.failure, source.ip: 203.0.113.7, repeat: 10, every: 20s}
- name: nine failures do not alert
expect: {alerts: 0}
events:
- {event.action: auth.signin.failure, source.ip: 203.0.113.7, repeat: 9, every: 20s}
- name: real log lines through a parser
expect: {alerts: 0}
log:
parser: auth_log
lines:
- "Oct 24 13:50:02 web01 sshd[1]: Failed password for root from 203.0.113.7 port 1 ssh2"Cases are either a short event timeline (repeat, every, after, at) or real log lines run
through a parser. See
Rule tests in docs/RULES.md.
- Use a unique
id, a cleartitle, astatusand aseverity. - Tag ATT&CK techniques and tactics.
- Prefer exact values and
contains/startswith/endswithover regular expressions. - Exclude known benign sources with a
filterselection andand not filter. - List false positives and link a response runbook.
- Run
prohori rules validateuntil it is clean. - Write
<name>.tests.ymlwith cases that should and should not alert, and runprohori rules test.
Repository · Changelog · Security · MIT License
Prohori
Contributing
Reference