Skip to content

Detection Rules

Utsha Basak edited this page Oct 26, 2026 · 4 revisions

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.

Built-in rules

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.

A rule in one glance

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

Building blocks

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.

Commands

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

How rules are evaluated

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

Testing a rule

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.

Writing a good rule

  1. Use a unique id, a clear title, a status and a severity.
  2. Tag ATT&CK techniques and tactics.
  3. Prefer exact values and contains / startswith / endswith over regular expressions.
  4. Exclude known benign sources with a filter selection and and not filter.
  5. List false positives and link a response runbook.
  6. Run prohori rules validate until it is clean.
  7. Write <name>.tests.yml with cases that should and should not alert, and run prohori rules test.

Clone this wiki locally