ADR-005: Platform-Only Finding Generation
Status
Accepted (2026-02-10)
Context
The Entra-ServiceNow connector had two detection modules — CrossServiceDetector and ExecutionChainDetector — totaling ~2,400 lines of detection logic. These produced findings directly in CLI reports and web dashboard output.
The platform evaluator (src/evaluator/) independently evaluates ingested entities against rules (orphaned_ownership, dormant_authority, scope_drift, privilege_justification_gap) to produce evidence-grade findings with immutable evidence packs.
This created duplicate detection: the same issue (e.g., orphaned ownership) was detected both by the connector's CrossServiceDetector and the platform's orphaned_ownership rule. The two implementations had different logic, different thresholds, and different output formats.
Problems with connector-side detection
- Duplication: Ownership, dormancy, and scope drift detected in two places with divergent logic
- No evidence packs: Connector detections were plain text strings — not evidence-grade, not immutable, not versioned
- No temporal tracking: Connector detections were point-in-time; platform evaluator tracks how findings change across sync versions
- Maintenance burden: Every new detection rule needed implementation in both the connector detector and the platform evaluator
Decision
All finding generation happens in the platform evaluator. Connectors are pure discovery + classification pipelines.
What connectors produce
- Entity nodes with classification properties (egress_category, origin, ownership_status, risk_group, identity_binding_status)
- Relationship edges with provenance (RUNS_AS, AUTHENTICATES_TO, OWNED_BY, TRIGGERS_ON, etc.)
- Execution evidence nodes (sign-in data, flow execution counts)
What connectors do NOT produce
- Findings (orphaned ownership, dormant authority, etc.)
- Health classifications (critical, at_risk, needs_attention)
- Detection reports with recommendations
What the platform evaluator handles
- All finding types: orphaned_ownership, ownership_degraded, dormant_authority, scope_drift, privilege_justification_gap, unresolved_cross_system_auth
- Evidence pack generation with immutable, timestamped evidence
- Temporal tracking across sync versions
- Severity classification based on entity properties and execution paths
CLI behavior change
- CLI exit code 2 (critical issues found) is removed — detection is not the CLI's job
- CLI reports are inventory-only: entities discovered, relationships mapped, properties classified
- CLI exit codes: 0 (success), 1 (errors during scan)
Rationale
Why platform-only
- Single source of truth: One implementation per detection rule, one set of thresholds, one output format
- Evidence-grade: Platform findings have immutable evidence packs, temporal tracking, and version history
- Testability: Detection rules are tested against the platform's entity model, not connector-specific dataclasses
- Separation of concerns: Connectors discover and classify; the platform evaluates and explains
Gate criteria (verified before removal)
Before removing connector detectors, the following were verified:
| Connector Detector | Platform Rule | Status |
|---|---|---|
| CrossServiceDetector: NO_OWNER / DISABLED_OWNER | orphaned_ownership | Verified — CREATED_BY fallback added |
| OwnershipDecayDetector | ownership_degraded | Verified — ownership_level property naming fixed |
| ExecutionChainDetector: dormant SP | dormant_authority | Verified — RUNS_AS traversal in path materializer |
| ScopeDriftDetector | scope_drift | Verified — HAS_ROLE version comparison |
| Shadow automation (unmatched client_id) | unresolved_cross_system_auth | New rule implemented with subtype guard |
Accepted gap
- INACTIVE_OWNER sign-in-based detection: The platform evaluator checks entity
status(disabled, suspended) but not sign-in recency. An owner with status "active" but no sign-in for 6+ months is not detected. Documented as a known limitation for future improvement.
Consequences
- Positive: ~2,400 lines of detection code removed from connector (
detectors.pydeleted entirely) - Positive: No more divergent detection logic between connector and platform
- Positive: CLI pipeline is simpler — discovery, classification, transform, submit
- Negative: CLI standalone mode loses issue detection capability (requires platform for findings)
- Neutral: app.py (Flask dashboard) remains functional for inventory browsing but no longer shows detected issues