Risk rules & customization

tirreno is designed to be customized for your specific security needs. No CLA or pull request is required for local modifications.

The two main customization points are:

  1. Custom rules Create detection rules based on user behavior
  2. Suspicious pattern lists Adjust URL, user agent, and email pattern detection

Rule presets

tirreno includes pre-configured rule sets for common security scenarios. Presets provide a quick starting point—select one from the Rules page dropdown and click Apply.

PresetUse Case
defaultEmpty rules (start from scratch)
account_takeoverDetect compromised accounts via new devices, locations, password changes
credential_stuffingDetect automated login attempts and brute force attacks
content_spamDetect spam content and suspicious posting patterns
account_registrationProtect registration from fake accounts and bots
fraud_preventionGeneral fraud detection across multiple vectors
insider_threatDetect unusual employee behavior and data exfiltration
bot_detectionIdentify automated traffic and crawlers
dormant_accountMonitor reactivation of long-inactive accounts
multi_accountingDetect users with multiple accounts
promo_abuseDetect promotional code and offer abuse
api_protectionProtect APIs from abuse and scanning
high_risk_regionsFlag traffic from high-fraud geographic regions

Each preset assigns weights to specific rules. You can customize the weights after applying a preset.

Rule weights:

WeightValueEffect on Risk Score
Positive-20Decreases risk (trusted behavior)
None0Rule disabled
Medium10Moderate risk increase
High20Significant risk increase
Extreme70Major risk increase

Rule organization

Rules are organized by namespace (core vs custom) and category (prefix letter).

Namespaces:

NamespaceDirectoryDescription
\Tirreno\Rules\Coreassets/rules/core/Built-in rules (109 rules)
\Tirreno\Rules\Customassets/rules/custom/Your custom rules

Rule categories by prefix:

PrefixCategoryExample
AAccount takeoverA01–A08
BBehaviourB01–B26
CCountryC01–C16
DDeviceD01–D10
EEmailE01–E30
IIPI01–I12
PPhoneP01–P04
RReuse/BlacklistR01–R03
XCustom/ExtraX01, X02, ...

Custom rules must use the X prefix (e.g., X01.php, X02.php). Core rule prefixes (A–R) are reserved.

Built-in rules

tirreno includes standard detection rules organized by category:

Account takeover (A01-A08)

RuleNameDescription
A01Multiple login failUser failed to login multiple times in a short term
A02Login failed on new deviceUser failed to login with new device
A03New device and new countryUser logged in with new device from new location
A04New device and new subnetUser logged in with new device from new subnet
A05Password change on new deviceUser changed their password on new device
A06Password change in new countryUser changed their password in new country
A07Password change in new subnetUser changed their password in new subnet
A08Browser language changedUser accessed the account with new browser language

Behaviour (B01-B26)

RuleNameDescription
B01Multiple countriesIP addresses are located in diverse countries
B02User has changed a passwordThe user has changed their password
B03User has changed an emailThe user has changed their email
B04Multiple 5xx errorsUser made multiple requests which evoked internal server error
B05Multiple 4xx errorsUser made multiple requests which cannot be fulfilled
B06Potentially vulnerable URLUser made a request to suspicious URL
B07User's full name contains digitsFull name contains digits
B08Dormant account (30 days)Account has been inactive for 30 days
B09Dormant account (90 days)Account has been inactive for 90 days
B10Dormant account (1 year)Account has been inactive for a year
B11New account (1 day)Account has been created today
B12New account (1 week)Account has been created this week
B13New account (1 month)Account has been created this month
B14Aged account (>30 days)Account has been created over 30 days ago
B15Aged account (>90 days)Account has been created over 90 days ago
B16Aged account (>180 days)Account has been created over 180 days ago
B17Single countryIP addresses are located in a single country
B18HEAD requestHTTP request HEAD method is often used by bots
B19Night time requestsUser was active from midnight till 5 a.m.
B20Multiple countries in one sessionUser's country changed in less than 30 minutes
B21Multiple devices in one sessionUser's device changed in less than 30 minutes
B22Multiple IP addresses in one sessionUser's IP changed in less than 30 minutes
B23User's full name contains space or hyphenFull name contains space or hyphen
B24Empty refererUser made a request without a referer
B25Unauthorized requestUser made a successful request without authorization
B26Single event sessionsUser had sessions with only one event

Country (C01-C16)

RuleNameDescription
C01Nigeria IP addressIP address located in Nigeria
C02India IP addressIP address located in India
C03China IP addressIP address located in China
C04Brazil IP addressIP address located in Brazil
C05Pakistan IP addressIP address located in Pakistan
C06Indonesia IP addressIP address located in Indonesia
C07Venezuela IP addressIP address located in Venezuela
C08South Africa IP addressIP address located in South Africa
C09Philippines IP addressIP address located in Philippines
C10Romania IP addressIP address located in Romania
C11Russia IP addressIP address located in Russia
C12European IP addressIP address located in European Union
C13North America IP addressIP address located in Canada or USA
C14Australia IP addressIP address located in Australia
C15UAE IP addressIP address located in United Arab Emirates
C16Japan IP addressIP address located in Japan

Device (D01-D10)

RuleNameDescription
D01Device is unknownUser has manipulated device information
D02Device is LinuxLinux OS, increased risk of crawler bot
D03Device is botUser agent identified as a bot
D04Rare browser deviceUser operates device with uncommon browser
D05Rare OS deviceUser operates device with uncommon OS
D06Multiple devices per userUser accesses account using multiple devices
D07Several desktop devicesUser accesses account using different OS desktop devices
D08Two or more phone devicesUser accesses account using numerous phone devices
D09Old browserUser accesses account using an old browser version
D10Potentially vulnerable User-AgentUser made a request with suspicious User-Agent

Email (E01-E30)

RuleNameDescription
E01Invalid email formatInvalid email format
E02New domain and no breachesEmail belongs to recently created domain with no breach history
E03Suspicious words in emailEmail contains auto-generated mailbox patterns
E04Numeric email nameEmail username consists entirely of numbers
E05Special characters in emailEmail has unusually high number of special characters
E06Consecutive digits in emailEmail includes at least two consecutive digits
E07Long email usernameEmail username exceeds average length
E08Long domain nameEmail domain name is too long
E09Free email providerEmail belongs to free provider
E10The website is unavailableDomain's website seems to be inactive
E11Disposable emailDisposable email addresses are temporary
E12Free email and no breachesEmail belongs to free provider with no breach history
E13New domainDomain name was registered recently
E14No MX recordEmail's domain has no MX record
E15No breaches for emailEmail was not involved in any data breaches
E16Domain appears in spam listsEmail appears in spam lists
E17Free email and spamEmail appears in spam lists and is from free provider
E19Multiple emails changedUser has changed their email
E20Established domain (> 3 year old)Email belongs to domain registered at least 3 years ago
E21No vowels in emailEmail username does not contain any vowels
E22No consonants in emailEmail username does not contain any consonants
E23Educational domain (.edu)Email belongs to educational domain
E24Government domain (.gov)Email belongs to government domain
E25Military domain (.mil)Email belongs to military domain
E26iCloud mailboxEmail belongs to Apple domains (icloud.com, me.com, mac.com)
E27Email breachesEmail appears in data breaches
E28No digits in emailEmail address does not include digits
E29Old breach (>3 years)Earliest data breach appeared more than 3 years ago
E30Domain with average rankEmail domain has Tranco rank between 100,000 and 4,000,000

Note: E18 is reserved for future use.

IP (I01-I12)

RuleNameDescription
I01IP belongs to TORIP assigned to The Onion Router network
I02IP hosting domainHigher risk of crawler bot
I03IP appears in spam listUser may have exhibited unwanted activity before
I04Shared IPMultiple users detected on same IP address
I05IP belongs to commercial VPNUser tries to hide real location
I06IP belongs to datacenterUser is utilizing an ISP datacenter
I07IP belongs to Apple RelayIP belongs to iCloud Private Relay
I08IP belongs to StarlinkIP belongs to SpaceX satellite network
I09Numerous IPsUser accesses account with numerous IP addresses
I10Only residential IPsUser uses only residential IP addresses
I11Single networkIP addresses belong to one network
I12IP belongs to LANIP address belongs to local access network

Phone (P01-P04)

RuleNameDescription
P01Invalid phone formatUser provided incorrect phone number
P02Phone country mismatchPhone number country is not among user's login countries
P03Shared phone numberUser provided a phone number shared with another user
P04Valid phoneUser provided correct phone number

Reuse/blacklist (R01-R03)

RuleNameDescription
R01IP in blacklistThis IP address appears in the blacklist
R02Email in blacklistThis email address appears in the blacklist
R03Phone in blacklistThis phone number appears in the blacklist

Developing custom rules

Custom rules are placed in assets/rules/custom/ with filenames X01.php, X02.php, etc.

Each rule must:

Example rule

See assets/rules/custom/X03.example.php for a complete example:

<?php

namespace Tirreno\Rules\Custom;

class X03 extends \Tirreno\Assets\Rule {
    public const NAME = '1xx user name';
    public const DESCRIPTION = 'Username starts with digit 1.';
    public const ATTRIBUTES = [];

    protected function defineCondition() {
        return $this->rb->logicalAnd(
            $this->rb['extra_one_digit_userid']->equalTo(true),
        );
    }
}

Custom context

For rules that need custom data, create a Context class in assets/rules/custom/Context.php. See Context.example.php:

<?php

declare(strict_types=1);

namespace Tirreno\Rules\Custom;

class Context extends \Tirreno\Assets\Context {
    protected $DB_TABLE_NAME = 'event_account';
    protected $uniqueValues = false;

    public function expandContext(array &$extraData, array &$user): void {
        // Add custom attributes to $user array
        $user['extra_one_digit_userid'] = substr(($extraData['extra_userid'][0][0] ?? ' '), 0, 1) === '1';
    }

    protected function getDetails(array $accountIds, int $apiKey): array {
        [$params, $placeHolders] = $this->getRequestParams($accountIds, $apiKey);

        $query = (
            "SELECT
                event_account.id      AS id,
                event_account.userid  AS extra_userid
            FROM event_account
            WHERE event_account.id IN ({$placeHolders})
              AND event_account.key = :api_key"
        );

        return $this->execQuery($query, $params);
    }
}

Testing rules

  1. Refresh rules: After creating or modifying rules, go to the Rules page and click Refresh at the bottom of the page to apply your changes
  2. Test a rule: Select a rule and click the Play button (▷) to see how many users are triggered by the rule
  3. Match rate: The percentage shown indicates how many users from 1000 match the rule (e.g., "22%" means 22% of 1000 users trigger this rule)

Ruler operators reference

The rules engine uses ruler/ruler for condition evaluation. Available operators in defineCondition():

OperatorDescriptionExample
equalToExact match$this->rb['ea_total_country']->equalTo(1)
notEqualToNot equal$this->rb['eip_tor']->notEqualTo(true)
greaterThanGreater than$this->rb['ea_total_ip']->greaterThan(9)
greaterThanOrEqualToGreater or equal$this->rb['ea_days_since_last_visit']->greaterThanOrEqualTo(30)
lessThanLess than$this->rb['ea_days_since_account_creation']->lessThan(7)
lessThanOrEqualToLess or equal$this->rb['eup_device_count']->lessThanOrEqualTo(1)
stringContainsSubstring match$this->rb['le_email']->stringContains('test')
stringContainsInsensitiveCase-insensitive substring$this->rb['le_domain_part']->stringContainsInsensitive('mail')
startsWithPrefix match$this->rb['event_url_string']->startsWith('/api/')
endsWithSuffix match$this->rb['le_email']->endsWith('.edu')
sameAsVariable comparison$this->rb['lp_country_code']->sameAs($this->rb['eip_country_id'])

Logical operators:

// AND - all conditions must be true
$this->rb->logicalAnd(
    $this->rb['eip_tor']->equalTo(true),
    $this->rb['ea_days_since_account_creation']->lessThan(7)
);

// OR - at least one condition must be true
$this->rb->logicalOr(
    $this->rb['eip_vpn']->equalTo(true),
    $this->rb['eip_tor']->equalTo(true)
);

// NOT - negate a condition
$this->rb->logicalNot(
    $this->rb['le_has_no_data_breaches']->equalTo(true)
);

Rule context attributes

When writing custom rules, the following attributes are available in the defineCondition() method. Access them via $this->rb['attribute_name'].

Event attributes (event_)

From Event context:

AttributeTypeDescription
event_iparrayIP IDs per event
event_url_stringarrayURLs per event
event_empty_refererarrayEmpty referer status per event
event_devicearrayDevice IDs per event
event_typearrayEvent types
event_http_codearrayHTTP response codes
event_http_methodarrayHTTP methods
event_device_createdarrayDevice creation timestamps
event_device_lastseenarrayDevice last seen timestamps

Derived event attributes:

AttributeTypeDescription
event_email_changedboolUser changed email in recent events
event_password_changedboolUser changed password in recent events
event_http_method_headboolHEAD request detected
event_empty_refererboolRequest had empty referer
event_multiple_5xx_httpintCount of 5xx server errors
event_multiple_4xx_httpintCount of 4xx client errors
event_2xx_httpboolSuccessful requests exist
event_vulnerable_urlboolURL matches suspicious patterns

Account attributes (ea_)

Raw account data from User context:

AttributeTypeDescription
ea_useridstringUser identifier
ea_createdstringAccount creation timestamp
ea_lastseenstringLast activity timestamp
ea_total_visitintTotal visits
ea_total_countryintTotal countries
ea_total_ipintTotal IP addresses
ea_total_deviceintTotal devices
ea_firstnamestringFirst name
ea_lastnamestringLast name

Derived account attributes:

AttributeTypeDescription
ea_days_since_account_creationintDays since account was created (-1 if unknown)
ea_days_since_last_visitintDays since user's last activity (-1 if unknown)
ea_fullname_has_numbersboolFull name contains digits
ea_fullname_has_spaces_hyphensboolFull name contains spaces or hyphens

IP attributes (eip_)

From Ip context:

AttributeTypeDescription
eip_cidr_countarrayCount of IPs per CIDR
eip_country_countarrayCount of IPs per country
eip_country_idarrayCountry IDs
eip_data_centerboolIP belongs to datacenter
eip_torboolIP belongs to TOR network
eip_vpnboolIP belongs to commercial VPN
eip_starlinkboolIP belongs to Starlink
eip_blocklistboolIP appears in spam/blocklist
eip_has_fraudboolFraud detected for IP
eip_lanboolIP belongs to LAN
eip_sharedintNumber of users sharing this IP
eip_domains_count_lenintNumber of domains on IP
eip_unique_cidrsintNumber of unique network ranges
eip_only_residentialboolAll IPs are residential (derived)

Device attributes (eup_)

From Device context:

AttributeTypeDescription
eup_devicearrayDevice types (desktop, smartphone, tablet, etc.)
eup_browser_namearrayBrowser names
eup_browser_versionarrayBrowser versions
eup_os_namearrayOperating system names
eup_langarrayBrowser languages
eup_uaarrayRaw user agent strings

Derived device attributes:

AttributeTypeDescription
eup_device_countintNumber of devices used
eup_has_rare_browserboolUser has uncommon browser
eup_has_rare_osboolUser has uncommon OS
eup_vulnerable_uaboolUser-Agent matches suspicious patterns

Session attributes (event_session_)

From Session context:

AttributeTypeDescription
event_session_single_eventboolSession had only one event
event_session_multiple_countryboolCountry changed within 30 min
event_session_multiple_ipboolIP changed within 30 min
event_session_multiple_deviceboolDevice changed within 30 min
event_session_night_timeboolActivity between midnight and 5 AM

Email attributes (Platform Edition only)

Last Email Attributes (le_):

AttributeTypeDescription
le_emailstringEmail address
le_local_partstringEmail username (before @)
le_domain_partstringEmail domain (after @)
le_blockemailsboolEmail is in blocklist
le_data_breachboolKnown data breaches
le_checkedboolEmail has been verified
le_fraud_detectedboolFraud detected for email
le_alert_listboolEmail on alert list

Derived last email attributes:

AttributeTypeDescription
le_existsboolEmail address exists
le_is_invalidboolEmail format is invalid
le_has_suspicious_strboolEmail contains suspicious patterns
le_has_numeric_only_local_partboolEmail username is all numbers
le_email_has_consec_s_charsboolEmail has consecutive special characters
le_email_has_consec_numsboolEmail has consecutive digits
le_email_has_no_digitsboolEmail has no digits
le_email_has_vowelsboolEmail username contains vowels
le_email_has_consonantsboolEmail username contains consonants
le_with_long_local_part_lengthboolEmail username exceeds max length
le_with_long_domain_lengthboolEmail domain exceeds max length
le_email_in_blockemailsboolEmail is in blocklist
le_has_no_data_breachesboolNo known data breaches
le_appears_on_alert_listboolEmail on alert list
le_local_part_lenintLength of email username

Email Attributes (ee_):

AttributeTypeDescription
ee_emailarrayAll email addresses for user
ee_earliest_breacharrayEarliest breach dates per email
ee_days_since_first_breachintDays since earliest known breach (-1 if none)

Domain attributes (Platform Edition only)

Last Domain Attributes (ld_):

AttributeTypeDescription
ld_disposable_domainsboolDomain is disposable email provider
ld_free_email_providerboolDomain is free email provider
ld_blockdomainsboolDomain is in blocklist
ld_mx_recordboolDomain has MX record
ld_disabledboolDomain website is disabled
ld_creation_datestringDomain creation date
ld_tranco_rankintTranco ranking (-1 if not ranked)

Derived last domain attributes:

AttributeTypeDescription
ld_is_disposableboolDomain is disposable email provider
ld_days_since_domain_creationintDays since domain registration
ld_domain_free_email_providerboolDomain is free email provider
ld_from_blockdomainsboolDomain is in blocklist
ld_domain_without_mx_recordboolDomain has no MX record
ld_website_is_disabledboolDomain website is disabled

Phone attributes (Platform Edition only)

From Phone context (ep_):

AttributeTypeDescription
ep_phone_numberarrayPhone numbers
ep_sharedarrayShared status per phone
ep_typearrayPhone types

Last phone from User context (lp_):

AttributeTypeDescription
lp_phone_numberstringLast phone number
lp_country_codestringPhone country code
lp_invalidboolPhone number is invalid
lp_fraud_detectedboolFraud detected for phone
lp_alert_listboolPhone on alert list

Derived phone attributes:

AttributeTypeDescription
lp_invalid_phoneboolPhone number is invalid
ep_shared_phoneboolPhone is shared with other users

Suspicious pattern lists

tirreno maintains lists of suspicious patterns in assets/lists/:

FilePurpose
url.phpURL attack patterns (SQL injection, path traversal, etc.)
user-agent.phpSuspicious user agent strings
email.phpSuspicious email patterns
file-extensions.phpFile extension categories

Each file returns a PHP array:

<?php
return [
    '.env',
    '.git',
    '/wp-admin',
    'phpmyadmin',
    '<script>',
    // ...
];

To add patterns:

  1. Open the appropriate file in assets/lists/
  2. Add your pattern string to the array
  3. Patterns are case-sensitive substring matches

Example patterns by type:

ListExample Patterns
url.php'.env', '../', '/wp-admin', 'phpmyadmin', '<script>'
user-agent.phpBot signatures, scanner identifiers, SQL injection attempts
email.php'spam', 'test', 'dummy', '123', '000'
________________________________________________________________________________