Skip to main content

Remediation Content Generation: Implementation Plan

Date: 2026-02-25 Status: Draft v2 (addresses review findings) Depends on: None for backend (new file src/evidence/remediation.ts — no collision). Frontend phases (Steps 8-10) share EvidencePackViewer.tsx and FindingDetail.tsx with Gap 3 (scope drift UX) — remediation frontend should land FIRST, then scope drift adds to the same components. Effort estimate: 1-2 days Owner: TBD Target: Week 1-2 (by Mar 7)


Problem Description

The evidence pack infrastructure defines a RemediationSection type with an actions: string[] array. The buildRemediation() function in src/evidence/sections.ts populates this from a static REMEDIATION_ACTIONS record — 3 generic, one-size-fits-all strings per FindingType.

The static actions are not context-aware. For example, orphaned_ownership always says "Assign a primary owner to this identity" regardless of whether the finding is about a workload named "HR Sync Flow" with 5 execution paths reaching restricted financial data, or a dormant service principal with no paths. The remediation text never mentions entity names, role names, resource names, sensitivity levels, or source systems.

Why This Matters

CISOs need actionable guidance, not boilerplate checklists. The W1 product definition explicitly states that W1 must deliver "evidence-backed artifacts that make the risk defensible and repeatable" and "language and structure that supports a CISO narrative."

Remediation that says "Assign a primary owner" without naming the entity, its source system, what it can reach, or why it matters is not CISO-ready. It fails the "evidence-grade" design constraint because it doesn't reference the evidence chain.

The pilot success criteria require findings to be "explainable with evidence." Generic remediation undermines credibility.

Scope boundary: W1 explicitly excludes "remediation workflows (ticketing, auto-fix, playbooks)." This plan improves advisory text within the existing evidence pack section — deterministic guidance from graph state, not automated enforcement.


Architectural Analysis

Where It Lives

Decision: New file src/evidence/remediation.ts, imported by sections.ts.

The EvidenceBuildContext already contains everything needed: FindingDoc (type, severity, explanation, evidence_refs), EntityDoc (type, display_name, source_system, execution_paths, relationships), owner entities, role entities, and temporal context.

Extracting to a separate file keeps sections.ts clean while the remediation logic grows (13 finding types, each with 3-5 context-aware actions).

Type Extension

Expand RemediationSection to support structured actions with priority and rationale:

export interface RemediationAction {
priority: "immediate" | "short_term" | "ongoing";
action: string;
rationale?: string;
}

export interface RemediationSection {
actions: string[]; // Backward-compatible plain text
structured_actions?: RemediationAction[]; // Prioritized with rationale
summary?: string; // One-sentence executive summary
}

The actions[] field stays for backward compatibility (markdown export, existing UI). The optional structured_actions enables the UI to render prioritized remediation with rationale.

Context Extraction Pattern

function extractContextVars(ctx: EvidenceBuildContext): ContextVars {
const entityName = ctx.entity.properties.display_name ?? ctx.entity.source_id;
const entityType = ctx.entity.entity_type;
const sourceSystem = ctx.entity.source_system;
const pathCount = (ctx.entity.execution_paths ?? []).length;
const roleCount = ctx.entity.relationships.filter(r => r.type === "HAS_ROLE").length;
const sensitivePaths = paths.filter(p =>
["confidential","restricted","high"].includes(p.sensitivity));
const domains = [...new Set(paths.map(p => p.business_domain))];
const ownerNames = ctx.ownerEntities.map(e => e.properties.display_name ?? e.source_id);
// ... etc
}

Dispatcher Pattern

const REMEDIATION_BUILDERS: Record<FindingType,
(ctx: EvidenceBuildContext, vars: ContextVars) => RemediationOutput> = {
orphaned_ownership: remediateOrphanedOwnership,
dormant_authority: remediateDormantAuthority,
scope_drift: remediateScopeDrift,
// ... all 13 types
};

export function buildContextAwareRemediation(ctx: EvidenceBuildContext): RemediationSection {
const vars = extractContextVars(ctx);
const builder = REMEDIATION_BUILDERS[ctx.finding.finding_type];
if (!builder) return { actions: [] };
const output = builder(ctx, vars);
return {
actions: output.structured_actions.map(a => a.action),
structured_actions: output.structured_actions,
summary: output.summary
};
}

Remediation Templates: All 13 Finding Types

Template variables shown in {braces} are populated from EvidenceBuildContext.

1. orphaned_ownership (Critical)

Summary: "{entity_name}" ({entity_type}) in {source_system} has {path_count} execution path(s) but no active owner — assign an accountable individual before reviewing access.

PriorityActionRationale
immediateAssign an individual owner to {entity_type} "{entity_name}" in {source_system}. Contact the last known owner ({last_owner_name}, status: {last_owner_status}) or the {source_system} administrator.Without an active owner, no one is accountable for {path_count} execution path(s) reaching {sensitivity_summary}.
immediateReview whether "{entity_name}" should remain active. If no longer needed, disable it in {source_system}.Orphaned entities with active execution paths are the highest-risk unmanaged surface.
short_termAudit the {path_count} execution path(s) for least-privilege compliance. Current reach includes: {top_resources_summary}.Execution paths may have been assigned under a previous owner's authorization.
ongoingEstablish an ownership review process to detect owner departures before they create orphaned entities.This finding was detected because ownership decayed without governance catching it.

2. ownership_degraded (High)

Summary: Primary owner(s) of "{entity_name}" are non-active. Governance has fallen back to {fallback_count} secondary/inherited owner(s): {fallback_names}.

PriorityActionRationale
immediateAssign a new primary owner to "{entity_name}". The fallback owner(s) ({fallback_names}) may lack context for the {path_count} execution path(s).Secondary/inherited owners are not direct accountability.
short_termVerify that {fallback_names} can confirm the business justification for "{entity_name}" and its {role_count} role assignment(s).Degraded ownership creates a gap between who authorized access and who is responsible.
ongoingConfigure ownership succession rules so primary owner departures trigger immediate reassignment.This degradation was detected reactively; a proactive process would prevent exposure windows.

3. dormant_authority (High)

Summary: "{entity_name}" holds {path_count} execution path(s) but has not executed in {days_dormant} days (threshold: 90 days). Standing authority should be revoked or justified.

PriorityActionRationale
immediateReview whether "{entity_name}" is still required. If not, revoke its role assignments: {role_names_csv}.{days_dormant} days of inactivity with {path_count} active path(s) is standing authority with no operational justification.
immediateIf still needed, rotate its credentials immediately. Dormant identities with standing access are prime targets for credential theft.Stale credentials on dormant authority are often unreviewable.
short_termReduce execution paths to least-privilege. Current reach includes {sensitive_resource_count} sensitive resource(s) across {domain_count} data domain(s).Even if reactivated, its current scope may exceed requirements.
ongoingImplement dormancy detection to flag identities before they exceed the 90-day threshold.Catching dormancy at 30-60 days allows orderly decommission.

4. scope_drift (Medium)

Summary: "{entity_name}" has grown from {baseline_role_count} to {current_role_count} role(s) since first observation. {added_count} role(s) were added without documented justification.

PriorityActionRationale
immediateReview the {added_count} role(s) added since baseline and confirm each has current business justification.Role creep accumulates incrementally and often goes unnoticed until a material exposure is realized.
short_termRemove any unjustified role assignments. Coordinate with the owner ({owner_name}) to confirm operational requirements.Reducing scope to baseline eliminates unnecessary execution paths to {affected_resource_count} resource(s).
ongoingEstablish periodic access review for "{entity_name}" to detect scope expansion before it compounds.Drift was detected by comparing current state to first-observed baseline.

5. privilege_justification_gap (Medium)

Summary: "{entity_name}" has elevated access to {gap_count} sensitive resource(s) where granted permissions do not match observed activity.

PriorityActionRationale
immediateReview the {gap_count} privilege gap(s): {gap_descriptions}. Determine whether granted permissions are still required.Elevated access to {sensitivity_levels} data without matching activity suggests over-provisioning.
short_termReduce permissions to match observed usage. If write/admin access is granted but only read activity is observed, downgrade the role.Least-privilege alignment reduces blast radius in the event of credential compromise.
short_termFor resources with zero observed activity, consider removing the execution path entirely.Zero-activity paths to sensitive resources represent unnecessary exposure.
ongoingDocument business justification for elevated access that must be retained.Documented justification closes the gap between granted authority and business need.

6. unresolved_cross_system_auth (Medium)

Summary: "{entity_name}" ({identity_subtype}) has OAuth credentials that cannot be matched to a governed service principal in {source_system}. This is a shadow workload.

PriorityActionRationale
immediateInvestigate the OAuth application and determine which service principal it should be bound to.Unresolved OAuth credentials represent ungoverned cross-system access.
immediateIf no legitimate binding exists, revoke the OAuth credentials and remove the integration.Shadow OAuth apps authenticate to target systems without appearing in governance reviews.
short_termFor legitimate integrations, create the proper service principal registration and AUTHENTICATES_TO binding.Governed bindings ensure cross-system auth is visible to governance tooling.
ongoingMonitor for new OAuth registrations that lack corresponding service principal bindings.Shadow integrations are often created by dev teams without security review.

7. unproven_execution (High)

Summary: Workload "{entity_name}" has {path_count} execution path(s) but no execution evidence can be deterministically linked to it.

PriorityActionRationale
immediateVerify whether "{entity_name}" is actively executing. Check {source_system} execution logs, trigger history, or runtime monitoring.Without deterministic evidence, the workload's actual behavior cannot be assessed.
short_termIf executing, ensure logs produce records that can be deterministically linked to the workload or its RUNS_AS identity.Execution evidence is required to distinguish active workloads from dormant ones.
short_termIf not executing, evaluate decommissioning to reduce standing authority. It currently reaches {affected_resource_count} resource(s).Standing authority without proven execution is unnecessary exposure.
ongoingEnsure execution evidence collection is configured for all autonomous workloads in {source_system}.This reflects a monitoring gap, not necessarily a security incident.

8. unknown_identity_binding (High)

Summary: Workload "{entity_name}" {binding_detail}. Without deterministic identity binding, its execution context is unknown.

PriorityActionRationale
immediateDetermine which identity "{entity_name}" executes as in {source_system}. Check runtime configuration, service account binding, or managed identity assignment.Without identity binding, execution cannot be attributed and access scope cannot be assessed.
short_termEstablish a RUNS_AS relationship from the workload to a governed identity. This enables the platform to compute execution paths.Identity binding is prerequisite for all downstream exposure assessment.
short_termUntil resolved, restrict the workload's execution paths in {source_system} to prevent unmonitored access.Unbound workloads may execute with elevated privileges invisible to governance.

9. reachable_sensitive_domain (Medium/High)

Summary: "{entity_name}" has {sensitive_path_count} execution path(s) reaching {sensitivity_levels} data across {domain_list}.

PriorityActionRationale
immediateReview whether "{entity_name}" requires access to {sensitivity_levels} data in {domain_list}. Confirm with the owner ({owner_name_or_unknown}).Access to {sensitivity_levels} data creates material exposure in the event of identity compromise.
short_termIf justified, document the business justification and attach to the entity's ownership record.Documented justification ensures future reviewers understand why sensitive access was authorized.
short_termIf not justified, remove the role assignments that grant execution paths to sensitive resources. Currently: {via_roles_csv}.Unnecessary sensitive access amplifies blast radius without operational benefit.
ongoingInclude sensitive domain access in periodic access reviews for "{entity_name}".Structural exposure signals should be re-evaluated whenever access scope changes.

10. llm_egress (High)

Summary: Workload "{entity_name}" has outbound data flow to an LLM endpoint. Data reaching this workload via {path_count} execution path(s) may be transmitted to the LLM.

PriorityActionRationale
immediateAssess what data flows through "{entity_name}" to the LLM endpoint. Review execution paths for sensitive data domains: {domain_summary}.Data sent to LLM endpoints may be retained for training, violating data residency or confidentiality requirements.
immediateVerify that the LLM integration has been approved by security and privacy teams. Confirm endpoint and data handling agreement.Unapproved LLM integrations represent shadow AI that bypasses organizational data governance.
short_termImplement DLP controls on the egress path to prevent sensitive data (especially {max_sensitivity}) from reaching the LLM.Without DLP, any data reachable by "{entity_name}" may be transmitted.
short_termIf approved, register in your AI governance inventory and subject to periodic review.Registered integrations can be tracked for compliance with evolving AI regulations.

11. external_egress (Medium)

Summary: Workload "{entity_name}" has outbound data flow to an external endpoint. Data reachable via {path_count} execution path(s) may leave the enterprise boundary.

PriorityActionRationale
immediateIdentify the external endpoint that "{entity_name}" transmits to. Verify it is a known, authorized integration partner.External egress without governance creates data exfiltration risk.
short_termImplement monitoring on the egress path. Ensure outbound transfers are logged and alertable.Monitoring enables detection of anomalous data volumes or unexpected destination changes.
short_termIf authorized, document the data flow including what data is transmitted and business justification.Documented egress paths can be reviewed during audits and compliance assessments.
ongoingInclude "{entity_name}" in periodic reviews of external integrations.External partnerships change; endpoints may be decommissioned or acquired by untrusted parties.

12. ownership_ambiguous (Medium)

Summary: "{entity_name}" is owned only by group/team owner(s): {owner_names}. No individual has ever been designated as accountable.

PriorityActionRationale
immediateDesignate an individual within {owner_names} as the primary accountable owner.Group/team ownership creates diffusion of responsibility.
short_termVerify the designated individual has context to manage "{entity_name}" — its {path_count} execution path(s) and {role_count} role assignment(s).Accountability without context is nominal.
ongoingEstablish policy requiring individual ownership for entities with execution paths to sensitive resources.Ambiguous ownership compounds over time as team membership changes.

13. ownership_unknown (Medium)

Summary: "{entity_name}" has {path_count} execution path(s) but insufficient metadata to determine who owns it.

PriorityActionRationale
immediateInvestigate {source_system} for ownership metadata not collected by the connector (sys_created_by, owner, managed_by).Absence may reflect a connector gap rather than true unknown.
short_termIf ownership metadata exists, update the connector configuration. If not, assign an owner based on function and scope."{entity_name}" has {path_count} path(s) reaching {domain_count} domain(s) — someone must be accountable.
ongoingImprove metadata collection across connectors to reduce ownership_unknown findings in future scans.Unknown ownership is a data quality issue that degrades all downstream assessments.

Demo Data Requirements

No changes to the seed script are required. Remediation content is generated at evidence-pack-build time from existing entity and finding data. Rerunning the seed or resyncing automatically produces context-aware remediation.

Key demo entities that produce the most compelling remediation:

EntityFinding TypesWhy It's Compelling
wl-hr-syncorphaned_ownership, reachable_sensitive_domainReferences departed owner, restricted data domains
wl-audit-exportdormant_authority, external_egressReferences 90+ day dormancy, external endpoint
wl-invoice-rulescope_driftReferences baseline vs current role count, specific added roles
wl-ai-assistllm_egressReferences LLM endpoint, data flowing through workload
wl-crm-syncunknown_identity_bindingReferences missing RUNS_AS, restricted data

Optional: Add display_name properties to role/resource entities in the seed if any are missing, so remediation text references human-readable names.


UI Changes

Phase 1: RemediationPanel Component

New file: ui/src/components/findings/RemediationPanel.tsx

  • If structured_actions present: render prioritized action cards with priority badge (red=immediate, amber=short_term, blue=ongoing), action text (bold), rationale (secondary).
  • If only actions present: render as simple bulleted list.
  • If summary present: render as highlighted header above actions.
  • Default to open (not collapsed) in EvidencePackViewer since remediation is the most actionable section.

Phase 2: Wire into EvidencePackViewer

File: ui/src/components/findings/EvidencePackViewer.tsx

Add dedicated remediation rendering path in SectionContent:

if (sectionKey === "remediation" && typeof data === "object") {
return <RemediationPanel remediation={data} />;
}

Currently this section renders via the generic KeyValueTable which outputs JSON.stringify(actions).

Phase 3: Promote Remediation on Finding Detail

File: ui/src/pages/FindingDetail.tsx

Add a standalone "Recommended Actions" card between the Explanation and Evidence Completeness sections. This card:

  • Extracts remediation from the evidence pack
  • Shows the summary and top 2 immediate actions inline
  • Links to the full evidence pack remediation section for detail

This gives CISOs key remediation guidance without navigating into the evidence pack.

UI Type Updates

File: ui/src/api/api-types.ts

export interface RemediationAction {
priority: "immediate" | "short_term" | "ongoing";
action: string;
rationale?: string;
}

export interface RemediationSection {
actions: string[];
structured_actions?: RemediationAction[];
summary?: string;
}

Implementation Steps

StepFile(s)DescriptionEffort
1src/domain/evidence-packs/types.tsAdd RemediationAction interface, expand RemediationSection10 min
2src/evidence/remediation.ts (NEW)Implement extractContextVars + 13 finding-type builders + dispatcher3-4 hr
3src/evidence/sections.tsReplace static buildRemediation with import from remediation.ts; remove REMEDIATION_ACTIONS10 min
4src/evidence/markdown.tsEnhance remediation section rendering (summary + structured actions by priority)30 min
5test/evidence/remediation.test.ts (NEW)Test each of 13 types: entity name appears, priorities correct, summary populated2 hr
6test/evidence/sections.test.tsUpdate buildRemediation test to verify context-aware content30 min
7ui/src/api/api-types.tsAdd RemediationAction type, update evidence pack typing10 min
8ui/src/components/findings/RemediationPanel.tsx (NEW)Prioritized action cards with badges and rationale1-2 hr
9ui/src/components/findings/EvidencePackViewer.tsxAdd dedicated remediation rendering path30 min
10ui/src/pages/FindingDetail.tsxAdd "Recommended Actions" card above evidence pack1 hr
11VerifyRun tests, typecheck, lint, reseed, verify in UI1 hr

Steps 2 and 8 are the bulk of the work and can run in parallel (backend + frontend).


Risks

RiskMitigation
Evidence pack integrity hash changes on every rebuildExpected behavior. Pack chaining via previous_pack_id preserves history. Hash recalculation is by design.
Missing context data (no owners, no roles, etc.)Each template function checks data presence and falls back to generic phrasing. extractContextVars handles all null/empty cases.
Long remediation text in markdown exportLimit each action to ~150 characters. Summary to one sentence.
Schema version bumpNot required — RemediationSection already includes actions: string[] and new fields are optional.