Drift Evaluator Framework
The drift evaluator framework detects temporal changes in entity authority — cases where a workload, identity, or service principal has gained, lost, or shifted authority compared to a known-good baseline. It complements the existing path-level evaluator by operating at the entity level rather than the path level.
Concepts
What is "drift"?
Drift is any change to an entity's authority state that was not explicitly expected. The platform captures entity state on every scan. By comparing current state to the oldest recorded state (the baseline), the evaluators detect three categories of drift:
| Type | Question answered |
|---|---|
| Scope drift | Has this entity gained new roles, grants, credentials, or execution identities since baseline? |
| Ownership drift | Have the owners of this entity been removed or disabled since baseline? |
| Reachability drift | Can this entity now reach destinations or data domains it could not reach at baseline? |
Baseline
The baseline is always the oldest recorded version of the entity in the version history. It represents the first time the platform observed the entity after onboarding. All drift comparisons are made against this snapshot.
Architecture
Rule Interface
Each drift rule implements the FindingRule interface:
interface FindingRule {
name: string;
evaluate(entity: EntityDoc, ctx: EvaluationContext): Promise<RuleFindingCandidate | null>;
}
Rules are pure functions:
- Read-only access to entity state and history via
EvaluationContext - Produce zero or one
RuleFindingCandidateper invocation - Never write to the database
- Deterministic — same inputs always produce the same output
Evaluation Context
EvaluationContext provides three data access methods:
| Method | Purpose |
|---|---|
getEntitiesByIds() | Batch entity lookup by ID |
getEntityVersionHistory() | Full temporal history for an entity (oldest → current) |
getExecutionEvidence() | Execution proof records for evidence completeness checks |
Coexistence with the Path Evaluator
The rule-based drift evaluators run alongside the legacy path-evaluator.ts. The path evaluator handles path-level findings (dormant authority, unknown identity binding, LLM egress, external egress, etc.) in a monolithic function. The new drift rules handle entity-level drift in isolated, independently testable modules. Both systems write to the same FindingDoc collection.
Data Flow
Connector scan
│
▼
Entity stored (EntityDoc + version history entry)
│
▼
Evaluator orchestrator
├── path-evaluator.ts → path-level FindingDocs
└── drift rules (×3) → entity-level FindingDocs
│
▼
FindingDoc persisted in MongoDB
│
▼
Authority Paths API surfaces findings
│
▼
AuthorityPathDetailPage renders risk conditions
│
▼
Remediation Service generates actions
The Three Drift Rules
1. Scope Drift (scope-drift.ts)
Detects: An entity gained new permissions or execution identities since baseline.
Monitored relationship types:
| Relationship | Meaning |
|---|---|
HAS_ROLE | Role assignment added |
GRANTS | Permission grant added |
USES | Credential or resource binding added |
RUNS_AS | Execution identity changed or replaced |
Logic:
- Fetch full version history; identify oldest version as baseline
- Compare current outbound relationships to baseline relationships across the four types
- Detect new additions in each category
- For role additions: resolve role IDs to display names, check if any new role reaches a sensitive domain (confidential, restricted)
- For identity/credential changes: flag if the execution principal was replaced (not just added)
- Fire if any expansion or identity replacement detected
Severity:
critical → finding is exercised AND touches a sensitive domain
high → finding is exercised OR touches a sensitive domain
medium → neither (standing authority, not yet exercised)
Evidence refs: list of added role names, added grant IDs, identity/credential delta, sensitive domains affected, drift categories, baseline version date.
2. Ownership Drift (ownership-drift.ts)
Detects: The entity's owner(s) were removed or became inactive since baseline.
Degradation modes:
| Mode | Condition |
|---|---|
| Owner removed | Owner relationship present at baseline, absent in current state |
| Owner disabled | Owner entity exists but its status changed to: deleted, departed, disabled, disbanded, restructured, or expired |
Logic:
- Only evaluates entities that had at least one owner at baseline
- Identifies owner IDs that were present at baseline
- For each baseline owner: check if still present (removal) and check if still active (disablement)
- Fire if any baseline owner was removed or disabled
Severity:
high → all baseline owners are lost (removed or disabled)
medium → partial owner loss (some owners remain)
Evidence refs: baseline owner count, list of removed owner IDs and display names, list of disabled owner IDs and display names, baseline version date.
3. Reachability Drift (reachability-drift.ts)
Detects: The entity can now reach destinations or business domains it could not reach at baseline.
Comparison: Current execution paths vs. oldest recorded execution paths (derived from version history, not role assignments — actual observed traversal).
Logic:
- Fetch oldest recorded execution paths as the reachability baseline
- Collect current reachable destination IDs and business domains
- Compute delta: new destination IDs and new business domains not present at baseline
- Check if any new destinations touch sensitive data domains
- Fire if any new destinations or domains are reachable
Severity:
critical → new destinations are exercised AND touch a sensitive domain
high → new destinations are exercised OR touch a sensitive domain
medium → new destinations exist but are not yet exercised
Evidence refs: new destination IDs and display names (up to 5; remainder summarized), new business domains, sensitive domains affected, baseline version date.
Remediation Service
The remediation service (src/services/remediation-service.ts) takes an authority path's active findings and generates a prioritized, scored list of actions for the UI.
Interface
export function generatePathRemediation(
path: AuthorityPathDoc,
findings: FindingDoc[]
): PathRemediationGuidance
interface PathRemediationGuidance {
path_id: string;
actions: PathRemediationAction[];
generated_at: string;
}
interface PathRemediationAction {
action: string; // e.g. "Remove role 'FullAccess'"
rationale: string; // e.g. "Reduces authority scope to baseline"
impact_score: number; // 1–10
finding_type: string; // e.g. "scope_drift" or "scope_drift+llm_egress"
}
How It Works
Step 1 — Filter: Only active findings are considered (remediated and suppressed findings are excluded).
Step 2 — Compound detection: The service checks for co-occurring finding pairs in a predefined COMPOUND_PAIRS list. When two findings co-occur, a single coordinated remediation action is generated instead of two separate ones (e.g., "Assign owner and revalidate expanded scope" rather than two independent actions). Compound actions use the maximum severity of the co-occurring findings.
Example compound pairs:
orphaned_ownership + scope_drift→ "Assign owner and revalidate expanded scope"scope_drift + llm_egress→ "Remove role granting LLM endpoint access"
Step 3 — Per-finding actions: For each finding type not handled by a compound rule, 2–4 specific actions are generated from a switch statement covering 14 finding types:
| Finding type | Example action |
|---|---|
scope_drift | "Remove the newly added role" / "Revoke new grants" / "Restrict to exercised authority only" |
ownership_drift | "Assign new owners to replace removed/disabled" / "Revalidate all paths" |
reachability_drift | "Restrict access to newly reachable sensitive domains" / "Remove new destinations" |
orphaned_ownership | "Assign a valid owner to this workload" / "Disable until owned" |
dormant_authority | "Revoke unused paths" / "Rotate credentials" |
llm_egress | "Restrict LLM endpoint access" / "Implement DLP controls" |
Step 4 — Impact scoring:
base_score = SEVERITY_WEIGHT[severity] // critical=4, high=3, medium=2, low=1
sensitivity_mul = SENSITIVITY_MULTIPLIER[sensitivity] // restricted/confidential=2×, high=1.5×, others=1×
resolution_mul = action.resolves ? 2× : 1× // direct fix vs. mitigation
impact_score = min(10, floor(base × sensitivity_mul × resolution_mul))
Step 5 — Output: Actions are deduplicated, sorted by descending impact_score, and capped at 5 (MAX_ACTIONS).
Design Principles
- Deterministic: No ML or probabilistic scoring. Same findings always produce the same actions.
- Read-only: The service generates guidance only. It never modifies source systems.
- Pluggable: Adding a new finding type requires only a new case in the switch statement. New compound pairs can be added to
COMPOUND_PAIRS. - Exposed via API:
GET /api/v1/authority-paths/:id/remediation— see Access Paths for the full API reference.
Adding a New Drift Rule
- Create
src/evaluator/rules/<rule-name>.tsimplementingFindingRule - Add the new finding type to
findingActions()inremediation-service.ts - Add any relevant compound pairs to
COMPOUND_PAIRS - Register the rule in the evaluator orchestrator
- Add a label mapping in the
RiskConditionsStripUI component
Related
- Access Paths — how paths surface drift findings in the API and UI
- Processing Pipeline — where evaluators fit in the overall scan pipeline
- Data Model —
FindingDocschema and entity version history