Menu

Change language

GitHub repo

Security Incident Timeline

blocksecurity

Security Operations Center (SOC) incident response timeline and post-mortem tracker with telemetry KPIs, chronological audit trail, log snippets, and corrective action items.

Installation

$npx uipkge-ng@latest add security-incident-timeline
Manifest:https://uipkge.dev/r/angular/security-incident-timeline.jsonInstalls to:src/app/components/blocks/security-incident-timeline/

Variants

Loading interactive previews…

Files installed (2)

  • src/app/components/blocks/security-incident-timeline/security-incident-timeline.component.ts46.5 kB
    // Security Operations Center (SOC) incident response timeline and post-mortem
    // tracker: telemetry KPIs, chronological audit trail with log snippets, and
    // corrective action items. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, signal } from '@angular/core'
    import {
      Check,
      CheckCircle2,
      Clock,
      Copy,
      FileCheck,
      FileDown,
      GitPullRequest,
      KeyRound,
      Lock,
      LucideAngularModule,
      Plus,
      Radio,
      Server,
      Shield,
      ShieldAlert,
      ShieldCheck,
      Terminal,
      Users,
      Zap,
    } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiAvatarComponent, UiAvatarFallbackComponent, UiAvatarImageComponent } from '@/ui/avatar/avatar.component'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import {
      UiCardComponent,
      UiCardContentComponent,
      UiCardDescriptionComponent,
      UiCardHeaderComponent,
      UiCardTitleComponent,
    } from '@/ui/card/card.component'
    import { UiCheckboxComponent } from '@/ui/checkbox/checkbox.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    
    const logSnippets: Record<string, string> = {
      detection: `[2026-08-21T14:12:08.104Z] GUARDDUTY_ALERT: UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS
    PrincipalId: AROA4491X0921:github-worker-ephemeral-tok_88f9x
    RequestedRole: arn:aws:iam::102938475612:role/SuperAdminPolicy
    SourceIP: 198.51.100.42 (Tor Exit Node AS13335) | UserAgent: aws-cli/2.15.30 Python/3.11.8
    RiskScore: 98/100 | FindingId: gd-find-8891-c0a1-992a | Action: STS:AssumeRoleWithWebIdentity`,
    
      triage: `[2026-08-21T14:15:30.412Z] INCIDENT_WAR_ROOM_ESTABLISHED: bridge_id=bridge_sec_0842
    IncidentCommander: [email protected] | Responders: [m.vance, d.okoye, j.chen]
    VectorAnalysis: Ephemeral token tok_88f9x generated in GH Actions workflow run #1049281.
    ScopeAssessment: Ingress proxy read cache queried; zero write or database mutation detected.
    EscalationLevel: SEV-1 (Paging IAM + Platform on-call)`,
    
      containment: `[2026-08-21T14:22:15.890Z] CLOUDFLARE_WAF_ENFORCED: rule_id=sec_block_tor_asn_13335 action=BLOCK
    GatewaysSynced: [apigw-iad-prod-01, apigw-iad-prod-02, apigw-iad-prod-03] (3/3 nodes OK)
    ActiveConnectionsDropped: 14 concurrent TCP streams | RateLimit: 0 req/sec allowed from source
    EgressQuarantine: ALL external routes from adversary subnet blackholed.`,
    
      revocation: `[2026-08-21T14:30:53.002Z] VAULT_REVOCATION_BROADCAST: lease_prefix=auth/token/tok_88f9x leases_revoked=38
    KMS_KEY_ROTATED: arn:aws:kms:us-east-1:102938475612:key/cmk-gateway-signing-02 -> key_version_v3
    RedisClusterBroadcast: AUTH_CACHE_PURGE_SUCCESS (12/12 shards synchronized in 84ms)
    TokenVerificationState: REVOCATION_COMPLETE (0 surviving tokens)`,
    
      postmortem: `[2026-08-21T14:48:00.651Z] SOC_AUDIT_SWEEP: duration=15m00s | unauthorized_calls=0 | perimeter_status=NOMINAL
    IncidentStatusTransition: SEV-1_ACTIVE -> MITIGATED_POST_MORTEM
    ForensicsArchive: s3://sec-incident-vault-us-east-1/incidents/2026-0842/evidence_bundle.tar.gz.enc
    EvidenceSignOff: [m.vance, e.rostova] | Hash: sha256:7f83b1657ff1fc53b92dc18148a1d65`,
    }
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-security-incident-timeline, [ui-security-incident-timeline]',
      standalone: true,
      // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiAvatarComponent,
        UiAvatarFallbackComponent,
        UiAvatarImageComponent,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiCheckboxComponent,
        UiSeparatorComponent,
      ],
      template: `
        <div data-slot="security-incident-timeline" [class]="rootClass">
          <!-- 1. Incident Header Card -->
          <div ui-card class="border-border shadow-xs">
            <div ui-card-header class="space-y-4">
              <div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
                <div class="space-y-2">
                  <!-- Badge Metadata Bar -->
                  <div class="flex flex-wrap items-center gap-2">
                    <span ui-badge variant="outline" class="font-mono text-xs font-semibold tracking-wider">
                      {{ incidentId }}
                    </span>
    
                    <!-- Severity Badge with Pulsing Red Dot -->
                    <span ui-badge variant="destructive" class="gap-1.5 font-mono text-xs font-semibold uppercase">
                      <span class="relative flex size-2 shrink-0">
                        <span class="bg-destructive absolute inline-flex h-full w-full rounded-full opacity-75"></span>
                        <span class="relative inline-flex size-2 rounded-full bg-white"></span>
                      </span>
                      {{ severity }}
                    </span>
    
                    <!-- Status Badge -->
                    @if (!isResolved()) {
                      <span ui-badge variant="warning" class="gap-1.5 font-mono text-xs font-semibold">
                        <span class="bg-warning size-2 rounded-full"></span>
                        {{ status }}
                      </span>
                    } @else {
                      <span ui-badge variant="success" class="gap-1.5 font-mono text-xs font-semibold">
                        <span class="bg-success size-2 rounded-full"></span>
                        Resolved · Closed
                      </span>
                    }
    
                    <span class="text-muted-foreground text-xs font-medium">
                      Region: <span class="text-foreground font-mono">us-east-1 (N. Virginia)</span>
                    </span>
                  </div>
    
                  <!-- Title & Description -->
                  <div class="space-y-1">
                    <h1 class="text-foreground text-2xl font-bold tracking-tight sm:text-3xl">
                      {{ incidentTitle }}
                    </h1>
                    <p class="text-muted-foreground text-sm">
                      Adversarial AWS STS role assumption anomaly flagged across external ingress gateways. Compromised
                      ephemeral CI/CD workflow token quarantined and revoked in 18 minutes.
                    </p>
                  </div>
                </div>
    
                <!-- Action Buttons -->
                <div class="flex flex-wrap items-center gap-2">
                  <button
                    ui-button
                    aria-label="Dismiss notification"
                    variant="outline"
                    size="sm"
                    class="h-9 gap-1.5 text-xs"
                    (click)="handleExportPdf()"
                  >
                    @if (exportNotification()) {
                      <lucide-icon [img]="Check" class="text-success size-3.5" aria-hidden="true" />
                    } @else {
                      <lucide-icon [img]="FileDown" class="size-3.5" aria-hidden="true" />
                    }
                    <span>{{ exportNotification() ? 'Exporting PDF...' : 'Export Incident Report PDF' }}</span>
                  </button>
    
                  <button ui-button variant="outline" size="sm" class="h-9 gap-1.5 text-xs" (click)="handleAddUpdate()">
                    @if (updateAddedNotification()) {
                      <lucide-icon [img]="Check" class="text-success size-3.5" aria-hidden="true" />
                    } @else {
                      <lucide-icon [img]="Plus" class="size-3.5" aria-hidden="true" />
                    }
                    <span>{{ updateAddedNotification() ? 'Draft Created' : 'Add Timeline Update' }}</span>
                  </button>
    
                  <button
                    ui-button
                    [variant]="isResolved() ? 'outline' : 'default'"
                    size="sm"
                    class="h-9 gap-1.5 text-xs font-medium"
                    (click)="handleResolve()"
                  >
                    <lucide-icon [img]="ShieldCheck" class="size-3.5" aria-hidden="true" />
                    <span>{{ isResolved() ? 'Re-open Incident' : 'Resolve Incident' }}</span>
                  </button>
                </div>
              </div>
    
              <ui-separator />
    
              <!-- Commander & Forensics Meta Strip -->
              <div class="grid grid-cols-1 gap-4 text-xs sm:grid-cols-2 lg:grid-cols-4">
                <div class="flex items-center gap-3">
                  <ui-avatar class="size-9 border">
                    <ui-avatar-image src="https://i.pravatar.cc/72?img=60" alt="Marcus Vance" />
                    <ui-avatar-fallback class="bg-primary/10 text-primary font-semibold">MV</ui-avatar-fallback>
                  </ui-avatar>
                  <div class="min-w-0">
                    <p class="text-muted-foreground text-xs font-medium">Incident Commander</p>
                    <p class="text-foreground truncate text-sm font-semibold">{{ commanderName }}</p>
                    <p class="text-muted-foreground truncate text-xs">{{ commanderRole }}</p>
                  </div>
                </div>
    
                <div class="space-y-1">
                  <p class="text-muted-foreground text-xs font-medium">Detection Trigger</p>
                  <p class="text-foreground font-mono text-xs font-semibold">GuardDuty · IAM.Anomaly.STS</p>
                  <p class="text-muted-foreground text-xs">2026-08-21 14:12:08 UTC</p>
                </div>
    
                <div class="space-y-1">
                  <p class="text-muted-foreground text-xs font-medium">Threat Vector & Origin</p>
                  <p class="text-foreground font-mono text-xs font-semibold">198.51.100.42 (Tor Exit Node)</p>
                  <p class="text-muted-foreground text-xs">AS13335 · Ephemeral OIDC Leak</p>
                </div>
    
                <div class="space-y-1">
                  <p class="text-muted-foreground text-xs font-medium">Incident War Room</p>
                  <div class="flex items-center gap-2">
                    <span class="relative flex size-2 shrink-0">
                      <span class="bg-success absolute inline-flex h-full w-full rounded-full opacity-75"></span>
                      <span class="bg-success relative inline-flex size-2 rounded-full"></span>
                    </span>
                    <p class="text-foreground font-mono text-xs font-semibold">#bridge-sec-0842</p>
                    <span ui-badge variant="outline" class="text-xs">4 Responders</span>
                  </div>
                  <p class="text-muted-foreground text-xs">Bridge active · PagerDuty Sync</p>
                </div>
              </div>
            </div>
          </div>
    
          <!-- 2. 4 Incident KPI Telemetry Cards -->
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
            <!-- Card 1: Time to Detect -->
            <div ui-card class="border-border shadow-xs">
              <div ui-card-content class="p-4">
                <div class="flex items-center justify-between">
                  <p class="text-muted-foreground text-xs font-medium">Time to Detect (TTD)</p>
                  <div
                    class="border-warning/20 bg-warning/10 text-warning flex size-8 items-center justify-center rounded-lg border"
                  >
                    <lucide-icon [img]="Clock" class="size-4" aria-hidden="true" />
                  </div>
                </div>
                <div class="mt-2">
                  <div class="text-foreground font-mono text-2xl font-bold tracking-tight">4m 12s</div>
                  <p class="text-muted-foreground mt-1 flex items-center gap-1 text-xs">
                    <lucide-icon [img]="CheckCircle2" class="text-success size-3 shrink-0" aria-hidden="true" />
                    <span>Automated GuardDuty alert fired</span>
                  </p>
                </div>
              </div>
            </div>
    
            <!-- Card 2: Time to Mitigate -->
            <div ui-card class="border-border shadow-xs">
              <div ui-card-content class="p-4">
                <div class="flex items-center justify-between">
                  <p class="text-muted-foreground text-xs font-medium">Time to Mitigate (TTM)</p>
                  <div
                    class="border-destructive/20 bg-destructive/10 text-destructive flex size-8 items-center justify-center rounded-lg border"
                  >
                    <lucide-icon [img]="Zap" class="size-4" aria-hidden="true" />
                  </div>
                </div>
                <div class="mt-2">
                  <div class="text-foreground font-mono text-2xl font-bold tracking-tight">18m 45s</div>
                  <p class="text-muted-foreground mt-1 flex items-center gap-1 text-xs">
                    <lucide-icon [img]="CheckCircle2" class="text-success size-3 shrink-0" aria-hidden="true" />
                    <span>Full perimeter isolation & rotation</span>
                  </p>
                </div>
              </div>
            </div>
    
            <!-- Card 3: Affected Resources -->
            <div ui-card class="border-border shadow-xs">
              <div ui-card-content class="p-4">
                <div class="flex items-center justify-between">
                  <p class="text-muted-foreground text-xs font-medium">Affected Resources</p>
                  <div
                    class="border-info/20 bg-info/10 text-info flex size-8 items-center justify-center rounded-lg border"
                  >
                    <lucide-icon [img]="Server" class="size-4" aria-hidden="true" />
                  </div>
                </div>
                <div class="mt-2">
                  <div class="text-foreground font-mono text-2xl font-bold tracking-tight">3 Gateways</div>
                  <p class="text-muted-foreground mt-1 flex items-center gap-1 font-mono text-xs">
                    <span>apigw-iad-prod-[01..03]</span>
                  </p>
                </div>
              </div>
            </div>
    
            <!-- Card 4: Impacted User Accounts -->
            <div ui-card class="border-border shadow-xs">
              <div ui-card-content class="p-4">
                <div class="flex items-center justify-between">
                  <p class="text-muted-foreground text-xs font-medium">Impacted User Accounts</p>
                  <div
                    class="border-success/20 bg-success/10 text-success flex size-8 items-center justify-center rounded-lg border"
                  >
                    <lucide-icon [img]="Users" class="size-4" aria-hidden="true" />
                  </div>
                </div>
                <div class="mt-2">
                  <div class="text-success font-mono text-2xl font-bold tracking-tight">0 Compromised</div>
                  <p class="text-muted-foreground mt-1 flex items-center gap-1 text-xs">
                    <lucide-icon [img]="ShieldCheck" class="text-success size-3 shrink-0" aria-hidden="true" />
                    <span>0 compromise confirmed · Token revoked</span>
                  </p>
                </div>
              </div>
            </div>
          </div>
    
          <!-- 3. Incident Timeline (Vertical Connected Stream) -->
          <div ui-card class="border-border shadow-xs">
            <div ui-card-header>
              <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                <div>
                  <div ui-card-title class="text-lg font-bold">Incident Response Timeline</div>
                  <div ui-card-description class="text-xs">
                    Chronological audit trail of SOC telemetry, containment actions, and mitigation milestones.
                  </div>
                </div>
                <div class="flex items-center gap-2">
                  <span ui-badge variant="outline" class="font-mono text-xs"> 5 Timeline Events · UTC Synchronized </span>
                </div>
              </div>
            </div>
    
            <div ui-card-content class="pt-2">
              <div
                class="before:bg-border relative space-y-8 pl-6 before:absolute before:top-3 before:bottom-3 before:left-3 before:w-px sm:pl-8 sm:before:left-4"
              >
                <!-- Timeline Entry 1: Detection -->
                <div class="relative space-y-3">
                  <div
                    class="bg-card ring-background border-destructive/40 absolute -left-6 flex size-7 items-center justify-center rounded-full border shadow-xs ring-4 sm:-left-8 sm:size-8"
                  >
                    <lucide-icon [img]="ShieldAlert" class="text-destructive size-3.5 sm:size-4" aria-hidden="true" />
                  </div>
    
                  <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                    <div class="flex flex-wrap items-center gap-2">
                      <span class="text-foreground font-mono text-xs font-bold">2026-08-21T14:12:08Z</span>
                      <span ui-badge variant="destructive" class="font-mono text-xs uppercase"> 1. Detection </span>
                      <span ui-badge variant="outline" class="font-mono text-xs"> T+00:00 </span>
                      <h3 class="text-foreground text-sm font-semibold">Automated Detection & Anomaly Ingestion</h3>
                    </div>
    
                    <div class="bg-muted/60 flex items-center gap-2 rounded-md border px-2.5 py-1 text-xs">
                      <ui-avatar class="size-5">
                        <ui-avatar-image src="https://i.pravatar.cc/60?img=60" alt="Marcus Vance" />
                        <ui-avatar-fallback class="text-xs">MV</ui-avatar-fallback>
                      </ui-avatar>
                      <span class="text-foreground font-medium">Marcus Vance</span>
                      <span class="text-muted-foreground text-xs">(SecOps Lead)</span>
                    </div>
                  </div>
    
                  <div class="space-y-1 text-xs">
                    <p class="text-foreground font-medium">Action Taken:</p>
                    <p class="text-muted-foreground leading-relaxed">
                      AWS GuardDuty anomaly engine flagged an unauthorized STS AssumeRole policy elevation request targeting
                      <code class="text-foreground bg-muted rounded px-1.5 py-0.5 font-mono text-xs"
                        >role/SuperAdminPolicy</code
                      >
                      from an unapproved Tor exit node. Ingestion webhook routed alert to PagerDuty SecOps on-call.
                    </p>
                  </div>
    
                  <div class="overflow-hidden rounded-md border bg-zinc-950 font-mono text-xs text-zinc-100 shadow-inner">
                    <div class="flex items-center justify-between border-b border-zinc-800 bg-zinc-900/90 px-3 py-1.5">
                      <div class="flex items-center gap-2">
                        <lucide-icon [img]="Terminal" class="text-muted-foreground size-3.5" aria-hidden="true" />
                        <span class="text-muted-foreground text-xs">guardduty_alert_stream.log</span>
                      </div>
                      <button
                        ui-button
                        variant="ghost"
                        size="xs"
                        class="h-6 gap-1 px-2 text-xs text-zinc-300 hover:bg-zinc-800 hover:text-zinc-100"
                        (click)="copySnippet('detection')"
                      >
                        @if (copiedSnippetId() === 'detection') {
                          <lucide-icon [img]="Check" class="text-success size-3" aria-hidden="true" />
                        } @else {
                          <lucide-icon [img]="Copy" class="size-3" aria-hidden="true" />
                        }
                        <span>{{ copiedSnippetId() === 'detection' ? 'Copied' : 'Copy Snippet' }}</span>
                      </button>
                    </div>
                    <pre
                      class="text-destructive/90 overflow-x-auto p-3 text-xs leading-relaxed select-text"
                    ><code>{{ logSnippets.detection }}</code></pre>
                  </div>
    
                  <div class="bg-muted/40 rounded-lg border p-3 text-xs">
                    <span class="text-foreground font-semibold">Mitigation Notes: </span>
                    <span class="text-muted-foreground">
                      Perimeter rate-limiting threshold exceeded (52 req/s). SIEM correlated 4 duplicate elevation
                      signatures across edge ingress gateways.
                    </span>
                  </div>
                </div>
    
                <!-- Timeline Entry 2: Triage -->
                <div class="relative space-y-3">
                  <div
                    class="bg-card ring-background border-warning/40 absolute -left-6 flex size-7 items-center justify-center rounded-full border shadow-xs ring-4 sm:-left-8 sm:size-8"
                  >
                    <lucide-icon [img]="Radio" class="text-warning size-3.5 sm:size-4" aria-hidden="true" />
                  </div>
    
                  <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                    <div class="flex flex-wrap items-center gap-2">
                      <span class="text-foreground font-mono text-xs font-bold">2026-08-21T14:15:30Z</span>
                      <span ui-badge variant="warning" class="font-mono text-xs uppercase"> 2. Triage </span>
                      <span ui-badge variant="outline" class="font-mono text-xs"> T+03:22 </span>
                      <h3 class="text-foreground text-sm font-semibold">SOC Triage & Incident War Room Assembly</h3>
                    </div>
    
                    <div class="bg-muted/60 flex items-center gap-2 rounded-md border px-2.5 py-1 text-xs">
                      <ui-avatar class="size-5">
                        <ui-avatar-image src="https://i.pravatar.cc/60?img=47" alt="Elena Rostova" />
                        <ui-avatar-fallback class="text-xs">ER</ui-avatar-fallback>
                      </ui-avatar>
                      <span class="text-foreground font-medium">Elena Rostova</span>
                      <span class="text-muted-foreground text-xs">(Incident Commander)</span>
                    </div>
                  </div>
    
                  <div class="space-y-1 text-xs">
                    <p class="text-foreground font-medium">Action Taken:</p>
                    <p class="text-muted-foreground leading-relaxed">
                      PagerDuty SEV-1 broadcast dispatched. Assembled SecOps, IAM, and Core Infrastructure leads in primary
                      war room. Traced origin token
                      <code class="text-foreground bg-muted rounded px-1.5 py-0.5 font-mono text-xs">tok_88f9x</code> to an
                      ephemeral CI/CD GitHub Actions runner.
                    </p>
                  </div>
    
                  <div class="overflow-hidden rounded-md border bg-zinc-950 font-mono text-xs text-zinc-100 shadow-inner">
                    <div class="flex items-center justify-between border-b border-zinc-800 bg-zinc-900/90 px-3 py-1.5">
                      <div class="flex items-center gap-2">
                        <lucide-icon [img]="Terminal" class="text-muted-foreground size-3.5" aria-hidden="true" />
                        <span class="text-muted-foreground text-xs">war_room_triage_audit.log</span>
                      </div>
                      <button
                        ui-button
                        variant="ghost"
                        size="xs"
                        class="h-6 gap-1 px-2 text-xs text-zinc-300 hover:bg-zinc-800 hover:text-zinc-100"
                        (click)="copySnippet('triage')"
                      >
                        @if (copiedSnippetId() === 'triage') {
                          <lucide-icon [img]="Check" class="text-success size-3" aria-hidden="true" />
                        } @else {
                          <lucide-icon [img]="Copy" class="size-3" aria-hidden="true" />
                        }
                        <span>{{ copiedSnippetId() === 'triage' ? 'Copied' : 'Copy Snippet' }}</span>
                      </button>
                    </div>
                    <pre
                      class="text-warning/90 overflow-x-auto p-3 text-xs leading-relaxed select-text"
                    ><code>{{ logSnippets.triage }}</code></pre>
                  </div>
    
                  <div class="bg-muted/40 rounded-lg border p-3 text-xs">
                    <span class="text-foreground font-semibold">Mitigation Notes: </span>
                    <span class="text-muted-foreground">
                      Confirmed unauthorized token attempt was using leaked CI/CD ephemeral workflow credential tok_88f9x.
                      Scope bounded strictly to read-only API gateway routing.
                    </span>
                  </div>
                </div>
    
                <!-- Timeline Entry 3: Containment -->
                <div class="relative space-y-3">
                  <div
                    class="bg-card ring-background border-warning/40 absolute -left-6 flex size-7 items-center justify-center rounded-full border shadow-xs ring-4 sm:-left-8 sm:size-8"
                  >
                    <lucide-icon [img]="Lock" class="text-warning size-3.5 sm:size-4" aria-hidden="true" />
                  </div>
    
                  <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                    <div class="flex flex-wrap items-center gap-2">
                      <span class="text-foreground font-mono text-xs font-bold">2026-08-21T14:22:15Z</span>
                      <span ui-badge variant="warning" class="font-mono text-xs uppercase"> 3. Containment </span>
                      <span ui-badge variant="outline" class="font-mono text-xs"> T+10:07 </span>
                      <h3 class="text-foreground text-sm font-semibold">Perimeter Quarantine & Traffic Blackholing</h3>
                    </div>
    
                    <div class="bg-muted/60 flex items-center gap-2 rounded-md border px-2.5 py-1 text-xs">
                      <ui-avatar class="size-5">
                        <ui-avatar-image src="https://i.pravatar.cc/60?img=33" alt="David Okoye" />
                        <ui-avatar-fallback class="text-xs">DO</ui-avatar-fallback>
                      </ui-avatar>
                      <span class="text-foreground font-medium">David Okoye</span>
                      <span class="text-muted-foreground text-xs">(Network Security)</span>
                    </div>
                  </div>
    
                  <div class="space-y-1 text-xs">
                    <p class="text-foreground font-medium">Action Taken:</p>
                    <p class="text-muted-foreground leading-relaxed">
                      Cloudflare WAF dynamic block rule enforced for egress IP ASN block (AS13335). Automated session kill
                      command dispatched across all 3 production ingress proxy gateway clusters.
                    </p>
                  </div>
    
                  <div class="overflow-hidden rounded-md border bg-zinc-950 font-mono text-xs text-zinc-100 shadow-inner">
                    <div class="flex items-center justify-between border-b border-zinc-800 bg-zinc-900/90 px-3 py-1.5">
                      <div class="flex items-center gap-2">
                        <lucide-icon [img]="Terminal" class="text-muted-foreground size-3.5" aria-hidden="true" />
                        <span class="text-muted-foreground text-xs">perimeter_waf_quarantine.log</span>
                      </div>
                      <button
                        ui-button
                        variant="ghost"
                        size="xs"
                        class="h-6 gap-1 px-2 text-xs text-zinc-300 hover:bg-zinc-800 hover:text-zinc-100"
                        (click)="copySnippet('containment')"
                      >
                        @if (copiedSnippetId() === 'containment') {
                          <lucide-icon [img]="Check" class="text-success size-3" aria-hidden="true" />
                        } @else {
                          <lucide-icon [img]="Copy" class="size-3" aria-hidden="true" />
                        }
                        <span>{{ copiedSnippetId() === 'containment' ? 'Copied' : 'Copy Snippet' }}</span>
                      </button>
                    </div>
                    <pre
                      class="text-warning/90 overflow-x-auto p-3 text-xs leading-relaxed select-text"
                    ><code>{{ logSnippets.containment }}</code></pre>
                  </div>
    
                  <div class="bg-muted/40 rounded-lg border p-3 text-xs">
                    <span class="text-foreground font-semibold">Mitigation Notes: </span>
                    <span class="text-muted-foreground">
                      Ingress gateways returned HTTP 403 Forbidden. External traffic from adversarial IP ranges suppressed
                      with 0 packet leak into VPC internal mesh.
                    </span>
                  </div>
                </div>
    
                <!-- Timeline Entry 4: Token Revocation -->
                <div class="relative space-y-3">
                  <div
                    class="bg-card ring-background border-info/40 absolute -left-6 flex size-7 items-center justify-center rounded-full border shadow-xs ring-4 sm:-left-8 sm:size-8"
                  >
                    <lucide-icon [img]="KeyRound" class="text-info size-3.5 sm:size-4" aria-hidden="true" />
                  </div>
    
                  <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                    <div class="flex flex-wrap items-center gap-2">
                      <span class="text-foreground font-mono text-xs font-bold">2026-08-21T14:30:53Z</span>
                      <span ui-badge variant="info" class="font-mono text-xs uppercase"> 4. Token Revocation </span>
                      <span ui-badge variant="outline" class="font-mono text-xs"> T+18:45 </span>
                      <h3 class="text-foreground text-sm font-semibold">
                        Cryptographic Credential Invalidation & Key Rotation
                      </h3>
                    </div>
    
                    <div class="bg-muted/60 flex items-center gap-2 rounded-md border px-2.5 py-1 text-xs">
                      <ui-avatar class="size-5">
                        <ui-avatar-image src="https://i.pravatar.cc/60?img=32" alt="Jessica Chen" />
                        <ui-avatar-fallback class="text-xs">JC</ui-avatar-fallback>
                      </ui-avatar>
                      <span class="text-foreground font-medium">Jessica Chen</span>
                      <span class="text-muted-foreground text-xs">(IAM Engineering)</span>
                    </div>
                  </div>
    
                  <div class="space-y-1 text-xs">
                    <p class="text-foreground font-medium">Action Taken:</p>
                    <p class="text-muted-foreground leading-relaxed">
                      Revoked compromised GitHub Actions OIDC token series in AWS IAM & HashiCorp Vault. Rotated parent
                      cluster KMS gateway signing keys and flushed authentication verification caches across Redis mesh.
                    </p>
                  </div>
    
                  <div class="overflow-hidden rounded-md border bg-zinc-950 font-mono text-xs text-zinc-100 shadow-inner">
                    <div class="flex items-center justify-between border-b border-zinc-800 bg-zinc-900/90 px-3 py-1.5">
                      <div class="flex items-center gap-2">
                        <lucide-icon [img]="Terminal" class="text-muted-foreground size-3.5" aria-hidden="true" />
                        <span class="text-muted-foreground text-xs">vault_revocation_kms_rotation.log</span>
                      </div>
                      <button
                        ui-button
                        variant="ghost"
                        size="xs"
                        class="h-6 gap-1 px-2 text-xs text-zinc-300 hover:bg-zinc-800 hover:text-zinc-100"
                        (click)="copySnippet('revocation')"
                      >
                        @if (copiedSnippetId() === 'revocation') {
                          <lucide-icon [img]="Check" class="text-success size-3" aria-hidden="true" />
                        } @else {
                          <lucide-icon [img]="Copy" class="size-3" aria-hidden="true" />
                        }
                        <span>{{ copiedSnippetId() === 'revocation' ? 'Copied' : 'Copy Snippet' }}</span>
                      </button>
                    </div>
                    <pre
                      class="text-info/90 overflow-x-auto p-3 text-xs leading-relaxed select-text"
                    ><code>{{ logSnippets.revocation }}</code></pre>
                  </div>
    
                  <div class="bg-muted/40 rounded-lg border p-3 text-xs">
                    <span class="text-foreground font-semibold">Mitigation Notes: </span>
                    <span class="text-muted-foreground">
                      All downstream JWT verification caches invalidated via Redis Pub/Sub broadcast in 84ms. Zero active
                      token survivability confirmed.
                    </span>
                  </div>
                </div>
    
                <!-- Timeline Entry 5: Post-Mortem Action Items -->
                <div class="relative space-y-3">
                  <div
                    class="bg-card ring-background border-success/40 absolute -left-6 flex size-7 items-center justify-center rounded-full border shadow-xs ring-4 sm:-left-8 sm:size-8"
                  >
                    <lucide-icon [img]="ShieldCheck" class="text-success size-3.5 sm:size-4" aria-hidden="true" />
                  </div>
    
                  <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                    <div class="flex flex-wrap items-center gap-2">
                      <span class="text-foreground font-mono text-xs font-bold">2026-08-21T14:48:00Z</span>
                      <span ui-badge variant="success" class="font-mono text-xs uppercase">
                        5. Post-Mortem Action Items
                      </span>
                      <span ui-badge variant="outline" class="font-mono text-xs"> T+35:52 </span>
                      <h3 class="text-foreground text-sm font-semibold">
                        Threat Vector Neutralized & Post-Mortem Transition
                      </h3>
                    </div>
    
                    <div class="bg-muted/60 flex items-center gap-2 rounded-md border px-2.5 py-1 text-xs">
                      <ui-avatar class="size-5">
                        <ui-avatar-image src="https://i.pravatar.cc/60?img=60" alt="Marcus Vance" />
                        <ui-avatar-fallback class="text-xs">MV</ui-avatar-fallback>
                      </ui-avatar>
                      <span class="text-foreground font-medium">Marcus Vance</span>
                      <span class="text-muted-foreground text-xs">(SecOps Lead)</span>
                    </div>
                  </div>
    
                  <div class="space-y-1 text-xs">
                    <p class="text-foreground font-medium">Action Taken:</p>
                    <p class="text-muted-foreground leading-relaxed">
                      Completed 15-minute clean traffic audit sweep with 0 anomalous requests. Incident commander officially
                      downgraded active SEV-1 to Mitigated status and initiated formal post-mortem action items.
                    </p>
                  </div>
    
                  <div class="overflow-hidden rounded-md border bg-zinc-950 font-mono text-xs text-zinc-100 shadow-inner">
                    <div class="flex items-center justify-between border-b border-zinc-800 bg-zinc-900/90 px-3 py-1.5">
                      <div class="flex items-center gap-2">
                        <lucide-icon [img]="Terminal" class="text-muted-foreground size-3.5" aria-hidden="true" />
                        <span class="text-muted-foreground text-xs">post_incident_audit_sweep.log</span>
                      </div>
                      <button
                        ui-button
                        variant="ghost"
                        size="xs"
                        class="h-6 gap-1 px-2 text-xs text-zinc-300 hover:bg-zinc-800 hover:text-zinc-100"
                        (click)="copySnippet('postmortem')"
                      >
                        @if (copiedSnippetId() === 'postmortem') {
                          <lucide-icon [img]="Check" class="text-success size-3" aria-hidden="true" />
                        } @else {
                          <lucide-icon [img]="Copy" class="size-3" aria-hidden="true" />
                        }
                        <span>{{ copiedSnippetId() === 'postmortem' ? 'Copied' : 'Copy Snippet' }}</span>
                      </button>
                    </div>
                    <pre
                      class="text-success/90 overflow-x-auto p-3 text-xs leading-relaxed select-text"
                    ><code>{{ logSnippets.postmortem }}</code></pre>
                  </div>
    
                  <div class="bg-muted/40 rounded-lg border p-3 text-xs">
                    <span class="text-foreground font-semibold">Mitigation Notes: </span>
                    <span class="text-muted-foreground">
                      Post-incident evidence bundle archived to secure S3 vault bucket
                      <code class="text-foreground bg-muted rounded px-1 font-mono text-xs"
                        >s3://sec-incidents-2026/0842-audit.tar.gz.enc</code
                      >. Remediation tasks assigned.
                    </span>
                  </div>
                </div>
              </div>
            </div>
          </div>
    
          <!-- 4. Root Cause & Corrective Action Items Card -->
          <div ui-card class="border-border shadow-xs">
            <div ui-card-header>
              <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
                <div class="space-y-1">
                  <div class="flex items-center gap-2">
                    <lucide-icon [img]="FileCheck" class="text-primary size-5" aria-hidden="true" />
                    <div ui-card-title class="text-lg font-bold">Root Cause Analysis & Corrective Action Items</div>
                  </div>
                  <div ui-card-description class="text-xs">
                    3 actionable remediation tasks tracked across Jira and GitHub to prevent recurring authorization bypass
                    vectors.
                  </div>
                </div>
                <span ui-badge variant="outline" class="w-fit font-mono text-xs"> 3 Post-Mortem Tasks </span>
              </div>
            </div>
    
            <div ui-card-content class="space-y-4">
              <!-- Root Cause Summary Box -->
              <div class="bg-muted/30 border-border space-y-1.5 rounded-lg border p-3.5 text-xs">
                <div class="text-foreground flex items-center gap-2 font-semibold">
                  <lucide-icon [img]="Shield" class="text-warning size-4" aria-hidden="true" />
                  <span>Root Cause Summary</span>
                </div>
                <p class="text-muted-foreground leading-relaxed">
                  A third-party fork pull request executed via a workflow using
                  <code class="text-foreground bg-muted rounded px-1 py-0.5 font-mono">pull_request_target</code> allowed
                  unauthorized extraction of the short-lived CI OIDC token. AWS STS role trust policy lacked strict
                  <code class="text-foreground bg-muted rounded px-1 py-0.5 font-mono">repository_owner</code> and
                  <code class="text-foreground bg-muted rounded px-1 py-0.5 font-mono">ref</code> claim constraints.
                </p>
              </div>
    
              <!-- Task List Items -->
              <div class="border-border divide-border divide-y rounded-lg border">
                <!-- Task 1 -->
                <div
                  class="hover:bg-muted/20 flex flex-col gap-3 p-3.5 transition-colors sm:flex-row sm:items-center sm:justify-between"
                >
                  <div class="flex items-start gap-3">
                    <div class="pt-0.5">
                      <ui-checkbox
                        id="task-sec-4412"
                        [checked]="actionItem1()"
                        (checkedChange)="actionItem1.set(!!$event)"
                      />
                    </div>
                    <div class="space-y-1">
                      <div class="flex flex-wrap items-center gap-2">
                        <label for="task-sec-4412" [class]="taskLabelClass(actionItem1())"> SEC-4412 </label>
                        <span ui-badge variant="destructive" class="font-mono text-xs">P0 Blocker</span>
                        <span ui-badge variant="warning" class="font-mono text-xs">In Review</span>
                      </div>
                      <p [class]="taskDescClass(actionItem1())">
                        Enforce strict OIDC branch-to-role binding with repository claim validation on CI runner STS
                        policies
                      </p>
                      <p class="text-muted-foreground text-xs">
                        Require <code class="font-mono text-xs">repository_owner=uipkge</code> and
                        <code class="font-mono text-xs">ref=refs/heads/main</code> on all assumed cloud roles.
                      </p>
                    </div>
                  </div>
    
                  <div class="flex shrink-0 flex-wrap items-center gap-3 pl-7 sm:justify-end sm:pl-0">
                    <div class="bg-muted/60 flex items-center gap-1.5 rounded-md border px-2 py-1 text-xs">
                      <ui-avatar class="size-4">
                        <ui-avatar-image src="https://i.pravatar.cc/50?img=32" alt="Jessica Chen" />
                        <ui-avatar-fallback class="text-xs">JC</ui-avatar-fallback>
                      </ui-avatar>
                      <span class="text-foreground font-medium">Jessica Chen</span>
                    </div>
                    <span ui-badge variant="outline" class="gap-1 font-mono text-xs">
                      <lucide-icon [img]="GitPullRequest" class="size-3" aria-hidden="true" />
                      PR #882
                    </span>
                  </div>
                </div>
    
                <!-- Task 2 -->
                <div
                  class="hover:bg-muted/20 flex flex-col gap-3 p-3.5 transition-colors sm:flex-row sm:items-center sm:justify-between"
                >
                  <div class="flex items-start gap-3">
                    <div class="pt-0.5">
                      <ui-checkbox
                        id="task-sec-4413"
                        [checked]="actionItem2()"
                        (checkedChange)="actionItem2.set(!!$event)"
                      />
                    </div>
                    <div class="space-y-1">
                      <div class="flex flex-wrap items-center gap-2">
                        <label for="task-sec-4413" [class]="taskLabelClass(actionItem2())"> SEC-4413 </label>
                        <span ui-badge variant="warning" class="font-mono text-xs">P1 High</span>
                        <span ui-badge variant="success" class="font-mono text-xs">Completed</span>
                      </div>
                      <p [class]="taskDescClass(actionItem2())">
                        Reduce default ephemeral CI token TTL from 3600s to 900s across all GitHub Actions deployment
                        workflows
                      </p>
                      <p class="text-muted-foreground text-xs">
                        Update HashiCorp Vault AWS secrets engine configuration and OIDC provider default duration.
                      </p>
                    </div>
                  </div>
    
                  <div class="flex shrink-0 flex-wrap items-center gap-3 pl-7 sm:justify-end sm:pl-0">
                    <div class="bg-muted/60 flex items-center gap-1.5 rounded-md border px-2 py-1 text-xs">
                      <ui-avatar class="size-4">
                        <ui-avatar-image src="https://i.pravatar.cc/50?img=33" alt="David Okoye" />
                        <ui-avatar-fallback class="text-xs">DO</ui-avatar-fallback>
                      </ui-avatar>
                      <span class="text-foreground font-medium">David Okoye</span>
                    </div>
                    <span ui-badge variant="outline" class="gap-1 font-mono text-xs"> Deployed v4.18.2 </span>
                  </div>
                </div>
    
                <!-- Task 3 -->
                <div
                  class="hover:bg-muted/20 flex flex-col gap-3 p-3.5 transition-colors sm:flex-row sm:items-center sm:justify-between"
                >
                  <div class="flex items-start gap-3">
                    <div class="pt-0.5">
                      <ui-checkbox
                        id="task-sec-4414"
                        [checked]="actionItem3()"
                        (checkedChange)="actionItem3.set(!!$event)"
                      />
                    </div>
                    <div class="space-y-1">
                      <div class="flex flex-wrap items-center gap-2">
                        <label for="task-sec-4414" [class]="taskLabelClass(actionItem3())"> SEC-4414 </label>
                        <span ui-badge variant="warning" class="font-mono text-xs">P1 High</span>
                        <span ui-badge variant="info" class="font-mono text-xs">In Progress</span>
                      </div>
                      <p [class]="taskDescClass(actionItem3())">
                        Implement automated SIEM quarantine webhook to auto-revoke Vault tokens upon GuardDuty high-severity
                        finding
                      </p>
                      <p class="text-muted-foreground text-xs">
                        Deploy AWS EventBridge rule to pipe GuardDuty high-severity IAM alerts directly to Lambda quarantine
                        webhook.
                      </p>
                    </div>
                  </div>
    
                  <div class="flex shrink-0 flex-wrap items-center gap-3 pl-7 sm:justify-end sm:pl-0">
                    <div class="bg-muted/60 flex items-center gap-1.5 rounded-md border px-2 py-1 text-xs">
                      <ui-avatar class="size-4">
                        <ui-avatar-image src="https://i.pravatar.cc/50?img=60" alt="Marcus Vance" />
                        <ui-avatar-fallback class="text-xs">MV</ui-avatar-fallback>
                      </ui-avatar>
                      <span class="text-foreground font-medium">Marcus Vance</span>
                    </div>
                    <span ui-badge variant="outline" class="gap-1 font-mono text-xs"> Sprint 34 · Aug 28 </span>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      `,
    })
    export class UiSecurityIncidentTimelineComponent {
      protected readonly Check = Check
      protected readonly CheckCircle2 = CheckCircle2
      protected readonly Clock = Clock
      protected readonly Copy = Copy
      protected readonly FileCheck = FileCheck
      protected readonly FileDown = FileDown
      protected readonly GitPullRequest = GitPullRequest
      protected readonly KeyRound = KeyRound
      protected readonly Lock = Lock
      protected readonly Plus = Plus
      protected readonly Radio = Radio
      protected readonly Server = Server
      protected readonly Shield = Shield
      protected readonly ShieldAlert = ShieldAlert
      protected readonly ShieldCheck = ShieldCheck
      protected readonly Terminal = Terminal
      protected readonly Users = Users
      protected readonly Zap = Zap
    
      protected readonly logSnippets = logSnippets
    
      @Input() incidentId = '#SEC-INC-2026-0842'
      @Input() incidentTitle = 'Unauthorized API Token Elevation Attempt'
      @Input() severity = 'SEV-1 Critical'
      @Input() status = 'Mitigated / In Post-Mortem'
      @Input() commanderName = 'Marcus Vance'
      @Input() commanderRole = 'Principal SecOps · Incident Commander'
      @Input('class') className?: string
    
      readonly isResolved = signal(false)
      readonly exportNotification = signal(false)
      readonly copiedSnippetId = signal<string | null>(null)
      readonly updateAddedNotification = signal(false)
    
      readonly actionItem1 = signal(true)
      readonly actionItem2 = signal(true)
      readonly actionItem3 = signal(false)
    
      get rootClass(): string {
        return cn('w-full space-y-6', this.className)
      }
    
      taskLabelClass(done: boolean): string {
        return cn(
          'cursor-pointer font-mono text-xs font-bold',
          done ? 'text-muted-foreground line-through' : 'text-foreground',
        )
      }
    
      taskDescClass(done: boolean): string {
        return cn('text-xs font-medium', done ? 'text-muted-foreground line-through' : 'text-foreground')
      }
    
      copySnippet(id: string): void {
        const text = logSnippets[id]
        if (!text) return
        try {
          void navigator.clipboard.writeText(text)
        } catch {
          // Clipboard unavailable: still flip the label.
        }
        this.copiedSnippetId.set(id)
        setTimeout(() => {
          if (this.copiedSnippetId() === id) this.copiedSnippetId.set(null)
        }, 2000)
      }
    
      handleExportPdf(): void {
        this.exportNotification.set(true)
        setTimeout(() => this.exportNotification.set(false), 2500)
      }
    
      handleAddUpdate(): void {
        this.updateAddedNotification.set(true)
        setTimeout(() => this.updateAddedNotification.set(false), 2500)
      }
    
      handleResolve(): void {
        this.isResolved.update((v) => !v)
      }
    }
    
  • src/app/components/blocks/security-incident-timeline/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/security-incident-timeline.json