Skip to main content

Implementation Plan: Overview Page + Authority Exposure Brief UX Changes

Date: 2026-03-07 Source: Sergey's feedback — UX Details: Overview page, Clarity: so what? Status: Draft — not yet reviewed by CISO, CEO, or architect


Feedback Validation (Analyst Review)

Before planning implementation, each feedback item is assessed for validity, feasibility, and potential concerns.

1. Risk Badge: P0/P1/P2 → Critical/High/Moderate/Low

Verdict: Valid and correct. Implement as-is.

Current P0/P1/P2 labels are opaque internal shorthand. A CISO reading "P0" must mentally decode it. The proposed labels (Critical/High/Moderate/Low) are self-explanatory and align with how security teams already think.

The proposed logic is also sound and maps almost exactly to what we already compute:

Current LogicProposed LabelMatch?
critical severity + execution > 0 → P0Critical: Sensitive domain + active execution + governance failure~Yes — cluster definitions already require governance failure (orphaned/unbound) for critical severity
critical severity (no exec) OR high + execution → P1High: Sensitive domain + active execution~Yes
everything else → P2Moderate: Governance failure but limited/no execution~Yes (dormant + governance failure)
(no current equivalent)Low: Dormant authorityPartially new — currently no cluster purely for dormant-without-egress

Concerns:

  • The current P0/P1/P2 logic uses only severity + execution_30d > 0. Sergey's proposed logic introduces a third axis: governance failure presence. This is more precise but requires the backend to check whether the cluster's finding_types include a governance condition (orphaned_ownership, unknown_identity_binding, scope_drift). This is feasible — the finding_types array on each cluster already contains this information.
  • The "Low" tier has no current cluster mapping. Today, dormant_external is the closest but it's rated "high" because it includes external egress. Pure dormant-without-egress paths don't form a cluster. Recommendation: Keep the four-tier labeling but only apply "Low" to dormant_external when execution_30d === 0. Don't create a new cluster for dormant-only — that's a separate product decision.

2. Cluster Names: Attribute-based → Functional Labels

Verdict: Valid. Implement with one addition.

Current names like "Orphaned + Sensitive" read as filter intersections, not threat descriptions. "Unowned Sensitive Access" communicates the risk immediately. This is a pure label change — no logic impact.

CurrentProposedAssessment
Orphaned + SensitiveUnowned Sensitive AccessGood
Orphaned + Sensitive + LLMUnowned Sensitive Access with LLMGood
Unbound + SensitiveUnbound Sensitive AccessGood
LLM EgressLLM Data EgressGood — adds "Data" to clarify what's egressing
Orphaned + External EgressUnowned External EgressGood
Dormant + ExternalDormant External AccessGood

Concern: Missing cluster. The current system has 7 clusters but Sergey's mapping only covers 6. scope_drift_sensitive ("Scope Drift + Sensitive") is not in the proposed mapping table. This is likely an oversight rather than intentional removal.

Recommendation: Add a 7th row: Scope Drift + Sensitive"Drifted Sensitive Access" or "Scope-Drifted Sensitive Access". Flag to Sergey for confirmation.

3. Verdict Sentence: New Template

Verdict: Valid with one structural concern.

Current format:

"3 autonomous paths exercised restricted/confidential-scoped authority and invoked endpoints 1,245 times in the last 30 days — all under orphaned ownership."

Proposed format:

"13 autonomous identities accessed sensitive systems 681 times in the last 30 days — none have an assigned owner."

The proposed version is significantly clearer. Key improvements:

  • "identities" instead of "paths" — more intuitive for CISOs (they think in identities, not graph paths)
  • "accessed sensitive systems" instead of "exercised restricted/confidential-scoped authority" — plain language
  • "none have an assigned owner" instead of "all under orphaned ownership" — explains the condition, doesn't use jargon

Concern: Data model mismatch. The current system counts paths as the primary unit. identity_count is available on the cluster response but is a distinct-count derived field. The sentence says <N> autonomous identities — this number must come from cluster.identity_count, NOT cluster.path_count. Currently identity_count is computed correctly in RiskClusterService (unique identity entities across matched paths), so the data is available.

Concern: "accessed " is vague. The examples use phrases like "accessed sensitive systems", "sent sensitive data to an LLM", "sent data outside the organization". These are cluster-specific — they can't come from a single template. Each cluster needs its own verb phrase. This means we need a verdict_template or action_phrase per cluster definition, not a generic builder function.

Recommendation: Define per-cluster action phrases in RISK_CLUSTER_DEFS:

ClusterAction Phrase
orphaned_sensitive"accessed sensitive systems"
orphaned_sensitive_llm"sent sensitive data to an LLM"
unbound_sensitive"accessed sensitive systems"
llm_egress"sent data to LLM endpoints"
orphaned_external"sent data outside the organization"
dormant_external"retain external access but have not executed"
scope_drift_sensitive"accessed sensitive systems with expanded scope"

Concern: Governance clause mapping. The "— governance condition" suffix varies per cluster:

  • orphaned → "none have an assigned owner"
  • unbound → "without a bound automation"
  • dormant → (no suffix — the verb phrase itself covers it)
  • scope_drift → needs a phrase like "with permissions beyond baseline"

This also needs to be per-cluster, not derived generically.

4. Authority Exposure Brief (New Page, Replaces Cluster Detail)

Verdict: Valid direction. Phased implementation recommended.

The current RiskClusterDetailPage is indeed a "header + table" — the header has stats, then immediately dumps a 9-column table. Sergey's proposed structure (Section A-D narrative → then table) is a significant UX improvement for CISO-level consumption.

Section-by-section assessment:

Section A — What Happened: Requires generating a natural-language narrative from cluster data. We have all the inputs (identity_count, execution_30d, sensitive_domains, egress_category). The challenge is making this sound natural, not templated. Feasible — we can use per-cluster narrative templates.

Section B — Am I Exposed? This is structured data we already have:

  • Paths: path_count
  • Executions (30d): total_execution_30d
  • Domains exercised: sensitive_domains[]
  • Endpoint type: partially available (egress_category gives external/llm/internal, but not specific endpoint types)
  • Active vs dormant: derivable from execution_30d > 0 per path, but not currently aggregated at cluster level

Concern: "Endpoint type" is ambiguous. We track egress_category (external/llm/internal) but not the specific endpoint protocol (REST, SOAP, webhook). If Sergey means egress category, we're fine. If he means the actual endpoint URL type, we'd need connector changes. Recommendation: Clarify with Sergey. For now, treat this as egress category.

Section C — Governance Condition: We need to surface which governance failures apply to this cluster. Currently, finding_types on the cluster tell us this (orphaned_ownership → "Orphaned", scope_drift → "Drift", etc.). We need:

  • Orphaned? → finding_types.includes("orphaned_ownership")
  • Drift? → finding_types.includes("scope_drift")
  • Identity reuse? → Not currently tracked. We'd need a new finding type or derivation (same identity_id across multiple paths). This is feasible but requires evaluator work.
  • Long-lived? → Not currently tracked as a finding. Long-lived tokens/credentials aren't surfaced as findings yet. This would need a new evaluator rule checking credential age.

Concern: Two of four governance conditions in Section C don't exist in the system yet:

  1. Identity reuse — derivable (count paths per identity, flag if > 1), but no finding type exists
  2. Long-lived tokens — requires credential age data from connectors, which we may not have

Recommendation: Implement Section C with the two conditions we have (orphaned, drift), show "identity reuse" if identity_count < path_count (simple heuristic that's still deterministic), and mark "long-lived" as "Not assessed" until connector data supports it.

Section D — How Do I Fix It? Remediation guidance needs to be per-cluster. This is purely a content/copy exercise — no backend logic needed. We define 1-3 bullets per cluster_key.

Section Table: "View Authority Paths (N)" button → reveals the existing table. This is a straightforward UI change — wrap the table in a collapsible/expandable section, default collapsed.


Implementation Plan

Phase 1: Overview Page Updates (Low risk, high impact)

Scope: Risk badges, cluster names, verdict sentences on the Overview page and Clusters list.

1.1 — Rename Cluster Labels

Files:

  • sv0-platform/src/services/risk-cluster-service.ts — Update RISK_CLUSTER_DEFS[].label

Changes:

orphaned_sensitive:     "Orphaned + Sensitive"        → "Unowned Sensitive Access"
orphaned_sensitive_llm: "Orphaned + Sensitive + LLM" → "Unowned Sensitive Access with LLM"
unbound_sensitive: "Unbound + Sensitive" → "Unbound Sensitive Access"
llm_egress: "LLM Egress" → "LLM Data Egress"
orphaned_external: "Orphaned + External Egress" → "Unowned External Egress"
dormant_external: "Dormant + External" → "Dormant External Access"
scope_drift_sensitive: "Scope Drift + Sensitive" → "Drifted Sensitive Access" (TBD — confirm with Sergey)

Also update description fields to use functional language instead of attribute-style.

Risk: Low — pure label change, no logic impact. API consumers see the new labels immediately.

Tests: Update any snapshot/unit tests that assert on cluster labels.

1.2 — Replace P0/P1/P2 with Deterministic Risk Labels

Files:

  • sv0-platform/src/services/risk-cluster-service.ts — Change RiskClusterPriority type and computation
  • sv0-platform/ui/src/api/api-types.ts — Update RiskClusterPriority type
  • sv0-platform/ui/src/components/PathRiskClusterCard.tsx — Update PRIORITY_STYLES

Backend changes:

Replace:

export type RiskClusterPriority = "P0" | "P1" | "P2";

With:

export type RiskClusterPriority = "critical" | "high" | "moderate" | "low";

Update priority computation (lines 296-300):

// Governance failure = cluster requires orphaned_ownership, unknown_identity_binding, or scope_drift
const hasGovernanceFailure = def.finding_types.some(ft =>
["orphaned_ownership", "unknown_identity_binding", "scope_drift"].includes(ft)
);

const priority: RiskClusterPriority =
def.severity === "critical" && total_execution_30d > 0 && hasGovernanceFailure ? "critical" :
(def.severity === "critical" || def.severity === "high") && total_execution_30d > 0 ? "high" :
hasGovernanceFailure && total_execution_30d === 0 ? "moderate" :
"low";

Frontend changes:

Replace P0/P1/P2 styles with:

const PRIORITY_STYLES: Record<RiskClusterPriority, { label: string; className: string }> = {
critical: { label: "Critical", className: "bg-red-100 text-red-700" },
high: { label: "High", className: "bg-orange-100 text-orange-700" },
moderate: { label: "Moderate", className: "bg-amber-100 text-amber-700" },
low: { label: "Low", className: "bg-blue-100 text-blue-700" },
};

Risk: Low-medium — changes the API contract for priority field. Any external consumers (none currently) would break. Internal UI is updated simultaneously.

Tests: Update priority computation tests. Add test cases for the governance-failure axis.

1.3 — Rewrite Verdict Sentence Builder

Files:

  • sv0-platform/src/services/risk-cluster-service.ts — Add action_phrase and governance_clause to cluster defs
  • sv0-platform/ui/src/components/PathRiskClusterCard.tsx — Rewrite buildVerdictSentence()

Backend changes:

Extend RiskClusterDef with new fields:

export interface RiskClusterDef {
cluster_key: string;
label: string;
description: string;
severity: string;
finding_types: FindingType[];
action_phrase: string; // NEW: "accessed sensitive systems"
governance_clause: string; // NEW: "none have an assigned owner"
dormant_verb?: boolean; // NEW: true for dormant clusters (different sentence structure)
}

Add to each cluster definition:

{
cluster_key: "orphaned_sensitive",
label: "Unowned Sensitive Access",
// ...
action_phrase: "accessed sensitive systems",
governance_clause: "none have an assigned owner",
},
{
cluster_key: "dormant_external",
label: "Dormant External Access",
// ...
action_phrase: "retain external access but have not executed",
governance_clause: "",
dormant_verb: true,
},
// ... etc for all 7 clusters

Serve action_phrase, governance_clause, and dormant_verb in the API response so the frontend can build the sentence.

Frontend changes:

Replace buildVerdictSentence():

function buildVerdictSentence(cluster: PathRiskCluster): string {
const count = cluster.identity_count ?? cluster.path_count;
const noun = count === 1 ? "autonomous identity" : "autonomous identities";

if (cluster.dormant_verb) {
return `${count} ${noun} ${cluster.action_phrase} in the last 30 days.`;
}

const exec = cluster.total_execution_30d.toLocaleString();
const base = `${count} ${noun} ${cluster.action_phrase} ${exec} times in the last 30 days`;
const suffix = cluster.governance_clause ? `${cluster.governance_clause}` : "";
return `${base}${suffix}.`;
}

Risk: Medium — changes the primary text users read. Must be reviewed for copy quality.

Tests: Unit test buildVerdictSentence() with mock cluster data for each cluster type.

1.4 — Update Cluster Sorting (Priority Order)

Files:

  • sv0-platform/ui/src/pages/ClustersListPage.tsx — Update sort comparator
  • sv0-platform/ui/src/pages/OverviewPage.tsx — Update sort if applicable

Currently sorts by P0 → P1 → P2. Update to sort by critical → high → moderate → low.

Risk: Low.


Phase 2: Authority Exposure Brief (Medium risk, high impact)

Scope: Replace RiskClusterDetailPage with the new narrative-first layout.

2.1 — Section A: What Happened Narrative

Files:

  • sv0-platform/ui/src/pages/RiskClusterDetailPage.tsx — Replace header section

Replace the current stats line (N paths | X executions/30d | Y lack valid ownership) with a full narrative paragraph. Use the same action_phrase + governance_clause from the cluster definition, but expand into a richer format:

What Happened: 13 autonomous identities accessed sensitive systems (restricted, confidential) 681 times in the last 30 days. None have an assigned owner. Authority was exercised via REST API endpoints against ServiceNow data domains.

Data sources:

  • identity_count → count of identities
  • action_phrase → verb
  • sensitive_domains[] → domain names
  • total_execution_30d → execution count
  • governance_clause → ownership/governance condition
  • Endpoint types → derive from paths' egress_category (aggregate)
  • Source systems → derive from paths' source_system (aggregate)

Concern: Source system and endpoint type aggregation aren't in the current cluster API response. We need to add egress_categories: string[] and source_systems: string[] to RiskClusterResult.

Backend changes:

  • risk-cluster-service.ts — Add egress_categories and source_systems aggregation to getRiskClusters() and the detail endpoint meta

Risk: Medium — new API fields, new narrative copy.

2.2 — Section B: Am I Exposed?

Files:

  • sv0-platform/ui/src/pages/RiskClusterDetailPage.tsx — Add structured data section

Display as a compact grid/card layout:

MetricSourceAvailable?
Authority Pathspath_count
Executions (30d)total_execution_30d
Domains Exercisedsensitive_domains[]
Endpoint Typeegress_category aggregation✓ (after 2.1 backend change)
Active vs DormantDerive: paths with execution_30d > 0 vs = 0Needs new aggregation

Backend changes:

  • Add active_path_count and dormant_path_count to cluster detail response

Risk: Low — structured data display, straightforward.

2.3 — Section C: Governance Condition

Files:

  • sv0-platform/ui/src/pages/RiskClusterDetailPage.tsx — Add governance checklist

Display as a checklist with status indicators:

ConditionDetectionAvailable?
Orphaned?finding_types.includes("orphaned_ownership")
Drift?finding_types.includes("scope_drift")
Identity reuse?identity_count < path_count (same identity used across multiple paths)✓ (derivable)
Long-lived?Credential age > thresholdNot yet — needs connector data

For each condition, show:

  • ✓ green check if not present (healthy)
  • ⚠ amber warning if present
  • — gray dash if "Not assessed"

Risk: Low for available conditions. "Long-lived" will show "Not assessed" until connector support lands.

2.4 — Section D: Remediation Guidance

Files:

  • sv0-platform/ui/src/pages/RiskClusterDetailPage.tsx — Add remediation section
  • sv0-platform/src/services/risk-cluster-service.ts — Add remediation_guidance: string[] to cluster defs

Per-cluster remediation bullets (advisory only):

ClusterGuidance
Unowned Sensitive Access1. Assign an active owner and revalidate approval. 2. Review whether all sensitive domain access is still required. 3. Restrict scope to exercised domains only.
Unowned Sensitive Access with LLM1. Assign an active owner immediately. 2. Audit LLM endpoint data flows for sensitive content. 3. Restrict or remove LLM egress until ownership is confirmed.
Unbound Sensitive Access1. Bind automation to a deterministic identity. 2. Review sensitive domain access scope. 3. Enable runtime logging for unbound execution paths.
LLM Data Egress1. Verify LLM endpoint authorization and data classification. 2. Restrict outbound payloads to non-sensitive data. 3. Enable content inspection logging.
Unowned External Egress1. Assign an active owner. 2. Restrict external endpoint invocation to approved destinations. 3. Enable egress logging.
Dormant External Access1. Revoke external access for dormant authority. 2. Convert long-lived tokens to just-in-time access. 3. If access is still needed, assign an owner and reactivate.
Drifted Sensitive Access1. Review scope expansion and remove unnecessary roles. 2. Re-baseline authority to current exercised scope. 3. Investigate root cause of drift.

Risk: Low — static content, no logic. Copy should be reviewed for accuracy.

2.5 — Table Behind "View Authority Paths" Button

Files:

  • sv0-platform/ui/src/pages/RiskClusterDetailPage.tsx — Wrap existing table in collapsible section

Replace the immediate table render with:

[Button: "View Authority Paths (N)"]
↓ (click to expand)
[Existing table]

Default state: collapsed. This prevents cognitive overload on first load.

Risk: Low — pure UI change, wraps existing table component.


Phase 3: Path Detail Refinement (Low effort)

Scope: Minor changes to the Authority Path Detail page.

3.1 — Observed vs Standing Authority Separation

Files:

  • sv0-platform/ui/src/pages/AuthorityPathDetailPage.tsx — Add collapsed panel

Current view shows all authority in one view. Add:

  • Default view: observed authority only (paths with execution_30d > 0)
  • Collapsed panel: "Additional Standing Authority (Not Exercised)" — paths with execution_30d === 0

Risk: Low — UI layout change only.


Sequencing

OrderItemEffortDependencies
11.1 Cluster label rename~1 hourNone
21.2 Risk badge replacement~2 hoursNone
31.3 Verdict sentence rewrite~3 hours1.1 (needs new labels for context)
41.4 Sort order update~30 min1.2 (needs new priority type)
52.5 Table behind button~1 hourNone
62.2 Section B (Am I Exposed?)~2 hoursBackend aggregation
72.3 Section C (Governance)~2 hoursNone
82.4 Section D (Remediation)~2 hoursNone
92.1 Section A (Narrative)~3 hours1.3, 2.2 backend fields
103.1 Observed vs Standing~2 hoursNone

Total estimated scope: ~18-20 hours of implementation across frontend and backend.

Phase 1 (items 1-4) can ship independently and provides immediate value. Phase 2 (items 5-9) should ship together as the Authority Exposure Brief. Phase 3 (item 10) can ship independently.


Open Questions for Sergey

  1. Scope Drift + Sensitive cluster — Missing from the renaming table. Proposed: "Drifted Sensitive Access". Confirm or provide preferred name.
  2. "Endpoint type" in Section B — Does this mean egress category (external/llm/internal) or the actual protocol/URL type (REST, webhook, etc.)?
  3. Identity reuse — Is the simple derivation (identity_count < path_count) sufficient, or does this need a formal finding type with its own evaluator rule?
  4. Long-lived tokens — This governance condition isn't tracked yet. Is it a Phase 1 requirement or can it be deferred?
  5. Verdict sentence for clusters with 0 identities — If identity_count is 0 (edge case with unbound paths), should we fall back to path count?

Files Changed (Summary)

Backend (sv0-platform/src/)

FileChange
services/risk-cluster-service.tsCluster labels, priority logic, new fields (action_phrase, governance_clause, remediation_guidance), aggregation additions
api/routes/posture.tsServe new fields in response
api/routes/risk-clusters.tsServe new fields in detail response
domain/authority-paths/types.tsNo changes needed

Frontend (sv0-platform/ui/src/)

FileChange
api/api-types.tsUpdate RiskClusterPriority, add new fields to PathRiskCluster
components/PathRiskClusterCard.tsxPriority styles, verdict sentence rewrite
pages/OverviewPage.tsxSort order update
pages/ClustersListPage.tsxSort order update
pages/RiskClusterDetailPage.tsxFull restructure → Authority Exposure Brief layout
pages/AuthorityPathDetailPage.tsxObserved vs standing separation

Tests (sv0-platform/test/)

FileChange
Tests for risk-cluster-serviceUpdate label assertions, add priority logic tests
UI component tests (if any)Update snapshot/assertion tests