-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathconfig.py
More file actions
324 lines (279 loc) · 11.6 KB
/
Copy pathconfig.py
File metadata and controls
324 lines (279 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
#!/usr/bin/env python3
"""
Configuration management for fraud detection MCP
Handles environment variables, paths, and system settings
"""
from pathlib import Path
from typing import Optional, Dict, Any
from pydantic import ConfigDict, Field, field_validator
from pydantic_settings import BaseSettings
from dotenv import load_dotenv
# Load environment variables from .env file if present
load_dotenv()
class AppConfig(BaseSettings):
"""Application configuration with environment variable support"""
# Application settings
APP_NAME: str = "fraud-detection-mcp"
APP_VERSION: str = "2.3.0"
ENVIRONMENT: str = Field(default="development")
DEBUG: bool = Field(default=False)
# Paths
BASE_DIR: Path = Field(default_factory=lambda: Path.cwd())
MODEL_DIR: Path = Field(default_factory=lambda: Path.cwd() / "models")
DATA_DIR: Path = Field(default_factory=lambda: Path.cwd() / "data")
TEST_DATA_DIR: Path = Field(default_factory=lambda: Path.cwd() / "test_data")
LOG_DIR: Path = Field(default_factory=lambda: Path.cwd() / "logs")
CACHE_DIR: Path = Field(default_factory=lambda: Path.cwd() / "cache")
# Model settings
ISOLATION_FOREST_CONTAMINATION: float = 0.1
ISOLATION_FOREST_N_ESTIMATORS: int = 200
XGBOOST_N_ESTIMATORS: int = 200
XGBOOST_MAX_DEPTH: int = 6
XGBOOST_LEARNING_RATE: float = 0.1
# Risk thresholds
THRESHOLD_HIGH_AMOUNT: float = 10000.0
THRESHOLD_CRITICAL_RISK: float = 0.8
THRESHOLD_HIGH_RISK: float = 0.6
THRESHOLD_MEDIUM_RISK: float = 0.4
# High-risk indicators
HIGH_RISK_LOCATIONS: list = Field(default_factory=lambda: ["unknown"])
HIGH_RISK_PAYMENT_METHODS: list = Field(
default_factory=lambda: ["crypto", "prepaid_card", "money_order"]
)
UNUSUAL_HOURS_START: int = 0
UNUSUAL_HOURS_END: int = 6
# Network analysis settings
NETWORK_HIGH_CONNECTIVITY_THRESHOLD: int = 50
NETWORK_HIGH_BETWEENNESS_THRESHOLD: float = 0.1
NETWORK_HIGH_CLUSTERING_THRESHOLD: float = 0.8
# Performance settings
BATCH_SIZE: int = 32
MAX_WORKERS: int = 4
CACHE_TTL_SECONDS: int = 3600
# Database settings (optional)
DATABASE_URL: Optional[str] = Field(default=None)
REDIS_URL: Optional[str] = Field(default="redis://localhost:6379")
# Security settings
API_KEY_HEADER: str = "X-API-Key"
JWT_SECRET_KEY: Optional[str] = Field(default=None)
JWT_ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
# Rate limiting
RATE_LIMIT_FREE_TIER: str = "10/minute"
RATE_LIMIT_PAID_TIER: str = "1000/minute"
RATE_LIMIT_ENTERPRISE: str = "10000/minute"
# Monitoring
ENABLE_METRICS: bool = Field(default=True)
METRICS_PORT: int = Field(default=9090)
LOG_LEVEL: str = Field(default="INFO")
# MLflow settings
MLFLOW_TRACKING_URI: Optional[str] = Field(default=None)
MLFLOW_EXPERIMENT_NAME: str = "fraud-detection"
# ------------------------------------------------------------------
# Agent Commerce Tier 0 — runtime-tunable thresholds
# ------------------------------------------------------------------
#
# Defaults below come from synthetic-data calibration (see
# ``scripts/calibrate_agent_thresholds.py``). They optimise F1 against
# a labelled synthetic agent-fraud distribution. Production deployments
# SHOULD recalibrate against their own labelled data periodically.
# All values are env-overridable, e.g.:
# ACP_VERIFIED_TRUST_BOOST=0.20 python server.py
#
# Provenance for current defaults:
# - Calibration date: 2026-05-03
# - Dataset: 2000 synthetic agent transactions (50% fraud)
# - Optimisation: grid search, F1 maximisation, n=5 cross-folds
# - See ``docs/calibration/agent_thresholds_2026_05_03.md``
# ------------------------------------------------------------------
# TrafficClassifier confidence adjustments on signature verification
ACP_VERIFIED_CONFIDENCE_BOOST: float = Field(
default=0.15,
description="Confidence boost when RFC 9421 signature verifies (TrafficClassifier).",
)
ACP_FAILED_CONFIDENCE_DROP: float = Field(
default=0.25,
description="Confidence drop when RFC 9421 signature fails (TrafficClassifier).",
)
# AgentIdentityVerifier JWT signal weights (mean of all signals → trust)
ACP_JWT_VERIFIED_SIGNAL: float = Field(
default=0.85,
description="Trust signal contribution for cryptographically-verified JWT.",
)
ACP_JWT_EXP_ONLY_SIGNAL: float = Field(
default=0.70,
description="Trust signal contribution for JWT with valid exp but no signature verify.",
)
ACP_JWT_NO_EXP_SIGNAL: float = Field(
default=0.50,
description="Trust signal contribution for JWT with neither exp nor signature.",
)
ACP_JWT_INVALID_SIGNAL: float = Field(
default=0.10,
description="Trust signal contribution for forged/expired/parse-failed JWT.",
)
ACP_API_KEY_VALID_SIGNAL: float = Field(
default=0.60,
description="Trust signal contribution for API key meeting MIN_KEY_LENGTH.",
)
ACP_API_KEY_INVALID_SIGNAL: float = Field(
default=0.10,
description="Trust signal contribution for malformed API key.",
)
ACP_REGISTRY_NEW_AGENT_SIGNAL: float = Field(
default=0.30,
description="Trust signal for an agent absent from the registry (auto-registered low).",
)
ACP_IDENTITY_VERIFIED_THRESHOLD: float = Field(
default=0.50,
description="Mean trust score required for verified=True (AgentIdentityVerifier).",
)
# analyze_agent_transaction_impl identity_trust adjustments
ACP_PIPELINE_VERIFIED_TRUST_BOOST: float = Field(
default=0.15,
description="identity_trust boost in analyze_agent_transaction_impl on verified signature.",
)
ACP_PIPELINE_FAILED_TRUST_DROP: float = Field(
default=0.30,
description="identity_trust drop in analyze_agent_transaction_impl on failed signature.",
)
# AgentBehavioralFingerprint anomaly threshold
ACP_FINGERPRINT_ANOMALY_THRESHOLD: float = Field(
default=0.60,
description="risk_score above this is flagged as is_anomaly=True in AgentBehavioralFingerprint.",
)
# Trust-score feedback loop. After every analyze_agent_transaction
# call we update the agent's persisted trust_score based on the
# current transaction's risk_score, using EWMA smoothing so a
# single anomalous transaction doesn't tank a long history.
#
# new_trust = (1 - learning_rate) * old_trust + learning_rate * (1 - risk_score)
#
# Pre-fix: trust was set at auto-register and NEVER moved, so the
# 'longitudinal reputation' scoring used a constant trust component
# forever — the whole feedback channel was dead.
ACP_TRUST_LEARNING_RATE: float = Field(
default=0.05,
description="EWMA learning rate for the trust-score feedback loop. 0.05 = each transaction nudges trust by ~5% toward (1-risk_score). Higher = more reactive, lower = more conservative.",
)
ACP_TRUST_LOW_RISK_THRESHOLD: float = Field(
default=0.30,
description="risk_score < this is treated as 'good behaviour' for trust feedback (full reward).",
)
ACP_TRUST_HIGH_RISK_THRESHOLD: float = Field(
default=0.60,
description="risk_score >= this is treated as 'bad behaviour' for trust feedback (penalty mode applies, learning rate doubled).",
)
# NonceCache + IdempotencyStore TTLs (seconds)
ACP_NONCE_TTL_SECONDS: int = Field(
default=480,
description="Visa-TAP nonce replay window (8 min default).",
)
ACP_IDEMPOTENCY_TTL_SECONDS: int = Field(
default=86400,
description="Stripe-ACP idempotency key cache window (24 h default).",
)
ACP_REPLAY_MAX_ENTRIES: int = Field(
default=100_000,
description="Per-cache entry cap before LRU-style eviction (memory safety).",
)
# Backend selection — in_memory (default) | sqlite
ACP_BACKEND: str = Field(
default="in_memory",
description="Backend for NonceCache + IdempotencyStore. 'in_memory' (process-local) or 'sqlite' (multi-process via WAL).",
)
ACP_SQLITE_PATH: Optional[Path] = Field(
default=None,
description="Path to SQLite file when ACP_BACKEND=sqlite. Defaults to DATA_DIR/agent_security.sqlite3.",
)
@field_validator("MODEL_DIR", "DATA_DIR", "TEST_DATA_DIR", "LOG_DIR", "CACHE_DIR")
@classmethod
def create_directories(cls, v):
"""Create directories if they don't exist"""
v = Path(v)
v.mkdir(parents=True, exist_ok=True)
return v
@field_validator("JWT_SECRET_KEY")
@classmethod
def validate_jwt_secret(cls, v, info):
"""Generate JWT secret if not provided in production"""
if v is None and info.data.get("ENVIRONMENT") == "production":
import secrets
return secrets.token_urlsafe(32)
return v
model_config = ConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=True,
)
class ModelConfig:
"""Configuration for ML models"""
def __init__(self, config: AppConfig):
self.config = config
def get_isolation_forest_params(self) -> Dict[str, Any]:
"""Get Isolation Forest hyperparameters"""
return {
"contamination": self.config.ISOLATION_FOREST_CONTAMINATION,
"n_estimators": self.config.ISOLATION_FOREST_N_ESTIMATORS,
"random_state": 42,
"n_jobs": -1,
"max_samples": 256,
}
def get_xgboost_params(self) -> Dict[str, Any]:
"""Get XGBoost hyperparameters"""
return {
"n_estimators": self.config.XGBOOST_N_ESTIMATORS,
"max_depth": self.config.XGBOOST_MAX_DEPTH,
"learning_rate": self.config.XGBOOST_LEARNING_RATE,
"subsample": 0.8,
"colsample_bytree": 0.8,
"random_state": 42,
"n_jobs": -1,
"eval_metric": "aucpr",
}
def get_autoencoder_params(self) -> Dict[str, Any]:
"""Get Autoencoder hyperparameters"""
return {
"hidden_dims": [64, 32, 16, 32, 64],
"learning_rate": 0.001,
"batch_size": 128,
"epochs": 50,
"dropout_rate": 0.2,
}
def get_gnn_params(self) -> Dict[str, Any]:
"""Get Graph Neural Network hyperparameters"""
return {
"hidden_channels": 64,
"num_layers": 3,
"learning_rate": 0.01,
"batch_size": 32,
"epochs": 100,
"dropout": 0.5,
}
# Global configuration instance
config = AppConfig()
model_config = ModelConfig(config)
def get_config() -> AppConfig:
"""Get global configuration instance"""
return config
def get_model_config() -> ModelConfig:
"""Get model configuration instance"""
return model_config
def update_config(**kwargs):
"""Update configuration with new values"""
global config, model_config
for key, value in kwargs.items():
if hasattr(config, key):
setattr(config, key, value)
model_config = ModelConfig(config)
if __name__ == "__main__":
# Print configuration for debugging
print("Fraud Detection MCP Configuration")
print("=" * 60)
print(f"Environment: {config.ENVIRONMENT}")
print(f"Debug Mode: {config.DEBUG}")
print(f"Model Directory: {config.MODEL_DIR}")
print(f"Data Directory: {config.DATA_DIR}")
print(f"Log Level: {config.LOG_LEVEL}")
print(f"Metrics Enabled: {config.ENABLE_METRICS}")
print("=" * 60)