DetectionResult¶
DetectionResult is the dataclass returned by detect_penetration_attempt() and detect_penetration_patterns(). Replaces the legacy tuple[bool, str] return so callers can read per-category metadata alongside the boolean verdict.
from dataclasses import dataclass, field
@dataclass
class DetectionResult:
is_threat: bool
trigger_info: str
threat_categories: list[str] = field(default_factory=list)
threat_scores: dict[str, float] = field(default_factory=dict)
Fields¶
| Field | Type | Description |
|---|---|---|
is_threat |
bool |
True when at least one regex or semantic pattern matched. |
trigger_info |
str |
Human-readable description of the first hit (e.g. "Query param 'q': Value matched pattern '...'"). Empty when is_threat=False. |
threat_categories |
list[str] |
Ordered list of categories that contributed at least one match ("sqli", "xss", "custom", ...). Deduplicated; preserves first-seen order. |
threat_scores |
dict[str, float] |
Maximum score per category. Regex matches contribute 1.0; semantic matches contribute their probability or threat score. |
When to inspect each field¶
is_threat is the gate. Most callers short-circuit on it and return early.
threat_categories is what you read when the next decision depends on which kind of attack you saw. Per-category ban policy uses it (SecurityConfig.threat_ban_config); dashboards use it to render the right glyph. The list is empty when only legacy semantic hits occurred without category labels.
threat_scores is what you read when you want a numeric handle for ranking, alerting, or dashboard severity. The values are not normalized across regex and semantic — regex always reports 1.0, semantic reports its model probability.
Example¶
from guard_core.utils import detect_penetration_attempt
async def handle_submission(request, logger):
result = await detect_penetration_attempt(request)
if not result.is_threat:
return {"ok": True}
logger.warning(f"Detection: {result.trigger_info}")
for category in result.threat_categories:
score = result.threat_scores.get(category, 0.0)
logger.warning(f" category={category} score={score:.2f}")
return {"error": "Suspicious activity detected"}
Migration from tuple[bool, str]¶
detected, trigger = await detect_penetration_attempt(request)
result = await detect_penetration_attempt(request)
detected, trigger = result.is_threat, result.trigger_info
The legacy 2-tuple is no longer returned. Callers that destructured the old tuple must migrate. See Migration guide for the full breaking-change list.