Skip to main content

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:

TypeQuestion answered
Scope driftHas this entity gained new roles, grants, credentials, or execution identities since baseline?
Ownership driftHave the owners of this entity been removed or disabled since baseline?
Reachability driftCan 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 RuleFindingCandidate per invocation
  • Never write to the database
  • Deterministic — same inputs always produce the same output

Evaluation Context

EvaluationContext provides three data access methods:

MethodPurpose
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:

RelationshipMeaning
HAS_ROLERole assignment added
GRANTSPermission grant added
USESCredential or resource binding added
RUNS_ASExecution identity changed or replaced

Logic:

  1. Fetch full version history; identify oldest version as baseline
  2. Compare current outbound relationships to baseline relationships across the four types
  3. Detect new additions in each category
  4. For role additions: resolve role IDs to display names, check if any new role reaches a sensitive domain (confidential, restricted)
  5. For identity/credential changes: flag if the execution principal was replaced (not just added)
  6. 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:

ModeCondition
Owner removedOwner relationship present at baseline, absent in current state
Owner disabledOwner entity exists but its status changed to: deleted, departed, disabled, disbanded, restructured, or expired

Logic:

  1. Only evaluates entities that had at least one owner at baseline
  2. Identifies owner IDs that were present at baseline
  3. For each baseline owner: check if still present (removal) and check if still active (disablement)
  4. 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:

  1. Fetch oldest recorded execution paths as the reachability baseline
  2. Collect current reachable destination IDs and business domains
  3. Compute delta: new destination IDs and new business domains not present at baseline
  4. Check if any new destinations touch sensitive data domains
  5. 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 typeExample 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

  1. Create src/evaluator/rules/<rule-name>.ts implementing FindingRule
  2. Add the new finding type to findingActions() in remediation-service.ts
  3. Add any relevant compound pairs to COMPOUND_PAIRS
  4. Register the rule in the evaluator orchestrator
  5. Add a label mapping in the RiskConditionsStrip UI component