Skip to content

Commit 84ef8db

Browse files
author
Fahad Alghanim
committed
Fix Python 3.9 compat, sync API docs, fix install instructions, remove placeholder, improve scorer performance
1 parent e4f4931 commit 84ef8db

28 files changed

Lines changed: 614 additions & 136 deletions

README.md

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ Add suspicious-login detection, auth-risk scoring, and step-up decisions to your
44

55
![MIT License](https://img.shields.io/badge/license-MIT-green.svg)
66
![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)
7-
![Tests Passing](https://img.shields.io/badge/tests-32%20passing-brightgreen.svg)
8-
![Pip Installable](https://img.shields.io/badge/pip-installable-orange.svg)
7+
![Tests Passing](https://img.shields.io/badge/tests-35%20passing-brightgreen.svg)
98

109
## Why This Exists
1110
Every fintech and crypto app builds auth-risk scoring internally because generic fraud tooling rarely models authentication flow context well. Teams need to distinguish legitimate users from attackers before full identity verification and before adding friction. This project packages those patterns into an open-source toolkit with synthetic-data-first workflows so you can run everything locally.
@@ -37,26 +36,39 @@ Auth Events
3736
Action: allow | step-up | review | block | revoke
3837
```
3938

40-
## Quickstart
39+
## Install
4140
```bash
42-
pip install identity-risk-engine
41+
git clone https://github.com/KOKOSde/identity-risk-engine.git
42+
cd identity-risk-engine
43+
python3 -m pip install -e .
4344
```
4445

46+
## Quickstart
4547
```python
4648
from identity_risk_engine.simulator_ire import generate_synthetic_auth_events
4749
from identity_risk_engine.policy_engine import PolicyEngine
48-
from identity_risk_engine.risk_engine_ire import score_dataframe
50+
from identity_risk_engine.risk_engine_ire import score_event
4951

5052
events = generate_synthetic_auth_events(num_users=50, num_sessions=1000, attack_ratio=0.2, seed=42)
51-
scored = score_dataframe(events, policy_engine=PolicyEngine())
52-
print(scored[["event_id", "risk_score", "action"]].head())
53+
event = events.iloc[50].to_dict()
54+
history = events.iloc[:50]
55+
result = score_event(event=event, history_df=history, policy_engine=PolicyEngine())
56+
57+
print(result["risk_score"], result["decision"]["action"])
58+
print(result["explanation"]["human_summary"])
5359
```
5460

61+
`PolicyEngine` exposes `decide()` (not `evaluate()`), and per-event explanations are returned by `risk_engine_ire.score_event()` under `result["explanation"]`.
62+
5563
## CLI Quickstart
5664
```bash
57-
identity-risk-engine simulate --users 500 --sessions 20000 --attack-ratio 0.2 --out synthetic.csv
58-
identity-risk-engine score --events synthetic.csv --policy configs/default_policy.yaml --out scored.csv
59-
identity-risk-engine report --events scored.csv --out report.html
65+
python3 -m identity_risk_engine.cli_ire simulate --users 500 --sessions 20000 --attack-ratio 0.2 --out synthetic.csv
66+
python3 -m identity_risk_engine.cli_ire score --events synthetic.csv --policy configs/default_policy.yaml --out scored.csv
67+
python3 -m identity_risk_engine.cli_ire report --events scored.csv --out report.html
68+
# fast demo mode for large files:
69+
python3 -m identity_risk_engine.cli_ire score --events synthetic.csv --policy configs/default_policy.yaml --fast --out scored_fast.csv
70+
# force full scoring mode even on large files:
71+
python3 -m identity_risk_engine.cli_ire score --events synthetic.csv --policy configs/default_policy.yaml --full --out scored_full.csv
6072
```
6173

6274
## FastAPI Quickstart
@@ -114,15 +126,16 @@ Supported actions: `allow`, `allow_with_monitoring`, `step_up_with_passkey`, `st
114126

115127
## Benchmark Results
116128
Run details: `num_users=100`, `num_sessions=2400`, `attack_ratio=0.22`, `seed=21`, time split from `demo_outputs/benchmark_output_ire.txt`.
129+
Reproducible with `seed=42` in model training and deterministic model settings.
117130

118131
| Cohort | AUC | Precision@0.95Recall | Recall@0.95Precision |
119132
|---|---:|---:|---:|
120133
| Global | 1.000 | 1.000 | 1.000 |
121-
| account_takeover | 0.914 | 0.246 | 0.000 |
122-
| bot_behavior | 0.904 | 0.138 | 0.000 |
123-
| credential_stuffing | 0.915 | 0.262 | 0.000 |
124-
| impossible_travel | 0.907 | 0.177 | 0.000 |
125-
| new_account_fraud | 0.907 | 0.177 | 0.000 |
134+
| account_takeover | 0.916 | 0.246 | 0.000 |
135+
| bot_behavior | 0.906 | 0.138 | 0.000 |
136+
| credential_stuffing | 0.908 | 0.262 | 0.000 |
137+
| impossible_travel | 0.910 | 0.177 | 0.000 |
138+
| new_account_fraud | 0.910 | 0.177 | 0.000 |
126139

127140
## Demo Output
128141
CLI report summary snippet (from `/tmp/ire_report.html` generated by CLI):
@@ -161,9 +174,6 @@ FastAPI `/simulate` response snippet (from `demo_outputs/fastapi_simulate_ire.tx
161174
- Fraud analysts
162175
- Security researchers
163176

164-
## Case Study
165-
Coinbase case study notebook placeholder: `notebooks/coinbase_case_study.ipynb` (to be added in a follow-up).
166-
167177
## Related Projects
168178
- [onchain-sybil-detector](../onchain-sybil-detector)
169179
- [LocalMod](../LocalMod)
Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
global {'auc': 1.0, 'precision_at_95_recall': 1.0, 'recall_at_95_precision': 1.0}
22
attack_type,auc,precision_at_95_recall,recall_at_95_precision
3-
account_takeover,0.9137323943661972,0.24615384615384617,0.0
4-
bot_behavior,0.9037800687285223,0.13846153846153847,0.0
5-
credential_stuffing,0.9151943462897526,0.26153846153846155,0.0
6-
impossible_travel,0.9072790294627383,0.17692307692307693,0.0
7-
new_account_fraud,0.9072790294627383,0.17692307692307693,0.0
8-
3+
account_takeover,0.9163732394366197,0.24615384615384617,0.0
4+
bot_behavior,0.9063573883161512,0.13846153846153847,0.0
5+
credential_stuffing,0.9077114944917897,0.26153846153846155,0.0
6+
impossible_travel,0.9098786828422877,0.17692307692307693,0.0
7+
new_account_fraud,0.9098786828422877,0.17692307692307693,0.0
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
run1= {"auc": 1.0, "precision_at_95_recall": 1.0, "recall_at_95_precision": 1.0}
2+
run2= {"auc": 1.0, "precision_at_95_recall": 1.0, "recall_at_95_precision": 1.0}
3+
identical_global= True
4+
identical_by_attack= True
5+
by_attack= [{"attack_type": "account_takeover", "auc": 0.9163732394366197, "precision_at_95_recall": 0.24615384615384617, "recall_at_95_precision": 0.0}, {"attack_type": "bot_behavior", "auc": 0.9063573883161512, "precision_at_95_recall": 0.13846153846153847, "recall_at_95_precision": 0.0}, {"attack_type": "credential_stuffing", "auc": 0.9077114944917897, "precision_at_95_recall": 0.26153846153846155, "recall_at_95_precision": 0.0}, {"attack_type": "impossible_travel", "auc": 0.9098786828422877, "precision_at_95_recall": 0.17692307692307693, "recall_at_95_precision": 0.0}, {"attack_type": "new_account_fraud", "auc": 0.9098786828422877, "precision_at_95_recall": 0.17692307692307693, "recall_at_95_precision": 0.0}]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Report written -> /tmp/ire_report.html
2+
real 1.64
3+
user 2.25
4+
sys 0.26
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Scored 19101 events -> /tmp/ire_scored.csv
2+
Scoring mode: fast-auto (history_window=8)
3+
Elapsed seconds: 9.12
4+
Mean risk score: 0.1036
5+
Action counts:
6+
allow: 12206
7+
allow_with_monitoring: 4416
8+
block: 187
9+
manual_review: 127
10+
require_recovery_review: 217
11+
revoke_session: 110
12+
step_up_with_email_code: 49
13+
step_up_with_passkey: 940
14+
step_up_with_totp: 849
15+
real 11.70
16+
user 12.27
17+
sys 0.34
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Generated 19101 events -> /tmp/ire_test.csv
2+
Attack mix:
3+
account_takeover: 94
4+
bot_behavior: 456
5+
credential_stuffing: 779
6+
impossible_travel: 156
7+
mfa_fatigue: 894
8+
multi_account_sybil: 630
9+
new_account_fraud: 85
10+
normal: 14847
11+
passkey_registration_abuse: 364
12+
recovery_abuse: 632
13+
session_hijack: 164
14+
real 3.35
15+
user 4.15
16+
sys 0.34
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
============================= test session starts ==============================
2+
platform linux -- Python 3.9.20, pytest-8.4.2, pluggy-1.6.0 -- /usr/bin/python3.9
3+
cachedir: .pytest_cache
4+
rootdir: /scratch/fkalghan/circuit_discovery_and_supression/graphs_ai_psych/identity-risk-engine
5+
configfile: pyproject.toml
6+
plugins: cov-7.1.0, anyio-4.12.0
7+
collecting ... collected 35 items
8+
9+
tests/test_behavior_anomaly.py::test_new_user_no_history_returns_uncertainty_score PASSED [ 2%]
10+
tests/test_behavior_anomaly.py::test_insufficient_baseline_still_scores PASSED [ 5%]
11+
tests/test_behavior_anomaly.py::test_clear_behavior_outlier_scores_higher_than_normal PASSED [ 8%]
12+
tests/test_cli_ire.py::test_cli_simulate_score_report_smoke PASSED [ 11%]
13+
tests/test_cli_ire.py::test_cli_score_fast_mode_smoke PASSED [ 14%]
14+
tests/test_composite_scorer.py::test_composite_model_reaches_auc_target_on_synthetic_data PASSED [ 17%]
15+
tests/test_composite_scorer.py::test_predict_proba_shape_and_operating_points PASSED [ 20%]
16+
tests/test_composite_scorer.py::test_composite_model_is_reproducible_with_seed_42 PASSED [ 22%]
17+
tests/test_device_fingerprint.py::test_known_device_gets_low_novelty PASSED [ 25%]
18+
tests/test_device_fingerprint.py::test_new_device_is_more_novel PASSED [ 28%]
19+
tests/test_device_fingerprint.py::test_new_user_without_history_scores_max_novelty PASSED [ 31%]
20+
tests/test_events_ire.py::test_auth_event_validates_required_schema PASSED [ 34%]
21+
tests/test_events_ire.py::test_event_to_row_serializes_enum_to_string PASSED [ 37%]
22+
tests/test_events_ire.py::test_invalid_event_type_raises_validation_error PASSED [ 40%]
23+
tests/test_explainer_ire.py::test_explainer_output_shape_and_content PASSED [ 42%]
24+
tests/test_fastapi_ire.py::test_health_endpoint PASSED [ 45%]
25+
tests/test_fastapi_ire.py::test_simulate_and_dashboard_endpoints PASSED [ 48%]
26+
tests/test_fastapi_ire.py::test_events_endpoint_returns_scored_decision PASSED [ 51%]
27+
tests/test_geo_velocity.py::test_same_location_speed_zero PASSED [ 54%]
28+
tests/test_geo_velocity.py::test_null_coordinates_do_not_crash PASSED [ 57%]
29+
tests/test_geo_velocity.py::test_impossible_travel_detected PASSED [ 60%]
30+
tests/test_policy_engine_ire.py::test_default_policy_mapping PASSED [ 62%]
31+
tests/test_policy_engine_ire.py::test_auth_method_override_applies PASSED [ 65%]
32+
tests/test_policy_engine_ire.py::test_tenant_override_applies PASSED [ 68%]
33+
tests/test_policy_engine_ire.py::test_dry_run_override PASSED [ 71%]
34+
tests/test_risk_engine_ire.py::test_attack_events_get_nonzero_risk_scores PASSED [ 74%]
35+
tests/test_signals_ire.py::test_device_signals_fire_for_new_device PASSED [ 77%]
36+
tests/test_signals_ire.py::test_geo_signals_fire_for_impossible_travel PASSED [ 80%]
37+
tests/test_signals_ire.py::test_behavior_signals_fire_for_failure_burst PASSED [ 82%]
38+
tests/test_signals_ire.py::test_passkey_signals_fire_for_unfamiliar_device PASSED [ 85%]
39+
tests/test_signals_ire.py::test_recovery_signals_fire_for_unfamiliar_env PASSED [ 88%]
40+
tests/test_signals_ire.py::test_simulator_ire_contains_all_attack_types PASSED [ 91%]
41+
tests/test_synthetic_data.py::test_generator_contains_all_attack_types PASSED [ 94%]
42+
tests/test_synthetic_data.py::test_generator_has_required_features PASSED [ 97%]
43+
tests/test_synthetic_data.py::test_new_account_fraud_is_young_account PASSED [100%]
44+
45+
======================== 35 passed in 263.28s (0:04:23) ========================

src/identity_risk_engine/behavior_anomaly.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
from dataclasses import dataclass
6+
from typing import Optional, Union
67

78
import numpy as np
89
import pandas as pd
@@ -30,8 +31,8 @@ class UserBehaviorProfile:
3031
actions_std: float
3132
entropy_mean: float
3233
entropy_std: float
33-
iso_model: IsolationForest | None
34-
last_timestamp: pd.Timestamp | None
34+
iso_model: Optional[IsolationForest]
35+
last_timestamp: Optional[pd.Timestamp]
3536

3637

3738
class BehaviorAnomalyScorer:
@@ -217,7 +218,7 @@ def score_dataframe(self, sessions_df: pd.DataFrame) -> pd.Series:
217218

218219
return self.score(sessions_df)["behavior_anomaly_score"]
219220

220-
def score_session(self, session: dict[str, object] | pd.Series) -> float:
221+
def score_session(self, session: Union[dict[str, object], pd.Series]) -> float:
221222
"""Score a single session payload."""
222223

223224
if isinstance(session, pd.Series):

0 commit comments

Comments
 (0)