keyhog-core 0.5.43

keyhog-core: shared data model and detector specifications for the KeyHog secret scanner
Documentation
<div class="report-container">
  <!-- Header -->
  <header>
    <div class="logo-section">
      <div class="logo-lockup">
        <div class="logo-word"><span class="logo-name">keyhog</span><span class="logo-sub">secret scan report</span></div>
      </div>
    </div>
    <div class="theme-selector" aria-label="Report theme">
      <button class="theme-btn active" onclick="setTheme('keyhog')" aria-label="Use KeyHog theme" aria-pressed="true">KEYHOG</button>
      <button class="theme-btn" onclick="setTheme('light')" aria-label="Use Light theme" aria-pressed="false">LIGHT</button>
      <button class="theme-btn" onclick="setTheme('matrix')" aria-label="Use Matrix theme" aria-pressed="false">MATRIX</button>
    </div>
  </header>

  <!-- Risk posture hero, one-glance verdict for the whole scan -->
  <div class="risk-hero" id="risk-hero">
    <div class="risk-hero-verdict" id="risk-verdict">: </div>
    <div class="risk-hero-sub" id="risk-sub" aria-live="polite">analyzing findings…</div>
  </div>

  <div class="scan-metadata" id="scan-metadata" style="display: none;">
    <div class="metadata-item">
      <span class="metadata-label">Target</span>
      <span class="metadata-value" id="meta-targets">: </span>
    </div>
    <div class="metadata-item">
      <span class="metadata-label">Generated</span>
      <span class="metadata-value" id="meta-generated">: </span>
    </div>
    <div class="metadata-item">
      <span class="metadata-label">Duration</span>
      <span class="metadata-value" id="meta-duration">: </span>
    </div>
    <div class="metadata-item">
      <span class="metadata-label">Scan ID</span>
      <span class="metadata-value" id="meta-scan-id">: </span>
    </div>
    <div class="metadata-item">
      <span class="metadata-label">Scan Status</span>
      <span class="metadata-value" id="meta-scan-status">: </span>
    </div>
    <div class="metadata-item">
      <span class="metadata-label">Backend recovery</span>
      <span class="metadata-value" id="meta-backend-recovery">: </span>
    </div>
    <div class="metadata-item">
      <span class="metadata-label">Source Chunks</span>
      <span class="metadata-value" id="meta-source-chunks">: </span>
    </div>
    <div class="metadata-item">
      <span class="metadata-label">Source Bytes</span>
      <span class="metadata-value" id="meta-source-bytes">: </span>
    </div>
    <div class="metadata-item">
      <span class="metadata-label">Detectors</span>
      <span class="metadata-value" id="meta-detectors">: </span>
    </div>
    <div class="metadata-item">
      <span class="metadata-label">Version</span>
      <span class="metadata-value" id="meta-version">: </span>
    </div>
  </div>

  <!-- Content Grid -->
  <div class="dashboard-grid">
    <!-- Sidebar Controls & Metrics -->
    <div class="sidebar">
      <!-- Metrics Summary -->
      <div class="card">
        <h2>Summary</h2>
        <div class="metric-row">
          <div class="stat-box">
            <div class="stat-val" id="cnt-total">0</div>
            <div class="stat-lbl">Total</div>
          </div>
          <div class="stat-box">
            <div class="stat-val" id="cnt-live" style="color: var(--color-live);">0</div>
            <div class="stat-lbl">Live</div>
          </div>
          <div class="stat-box">
            <div class="stat-val" id="cnt-not-checked" style="color: var(--color-skipped);">0</div>
            <div class="stat-lbl">Not checked</div>
          </div>
        </div>
        <div class="metric-row">
          <div class="stat-box">
            <div class="stat-val" id="cnt-critical" style="color: var(--color-critical);">0</div>
            <div class="stat-lbl">Critical</div>
          </div>
          <div class="stat-box">
            <div class="stat-val" id="cnt-high" style="color: var(--color-high);">0</div>
            <div class="stat-lbl">High</div>
          </div>
        </div>
      </div>

      <!-- Severity Chart -->
      <div class="card">
        <h2>Severity Breakdown</h2>
        <div class="chart-container">
          <svg class="chart-svg" viewBox="0 0 42 42" role="img" aria-label="Severity breakdown donut chart">
            <defs>
              <filter id="seg-glow" x="-50%" y="-50%" width="200%" height="200%">
                <feGaussianBlur stdDeviation="0.9" result="b"/>
                <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
              </filter>
            </defs>
            <circle class="chart-track" cx="21" cy="21" r="15.91549430918954"></circle>
            <circle class="chart-hole" cx="21" cy="21" r="15.91549430918954"></circle>
            <g filter="url(#seg-glow)">
              <circle class="chart-segment" id="seg-critical" cx="21" cy="21" r="15.91549430918954" stroke="var(--color-critical)"></circle>
              <circle class="chart-segment" id="seg-high" cx="21" cy="21" r="15.91549430918954" stroke="var(--color-high)"></circle>
              <circle class="chart-segment" id="seg-medium" cx="21" cy="21" r="15.91549430918954" stroke="var(--color-medium)"></circle>
              <circle class="chart-segment" id="seg-low" cx="21" cy="21" r="15.91549430918954" stroke="var(--color-low)"></circle>
              <circle class="chart-segment" id="seg-info" cx="21" cy="21" r="15.91549430918954" stroke="var(--color-info)"></circle>
              <circle class="chart-segment" id="seg-client" cx="21" cy="21" r="15.91549430918954" stroke="var(--color-client-safe)"></circle>
            </g>
            <text class="chart-center-num" id="chart-total" x="21" y="20.4" text-anchor="middle" dominant-baseline="central">0</text>
            <text class="chart-center-lbl" x="21" y="25.6" text-anchor="middle" dominant-baseline="central">findings</text>
          </svg>
        </div>
        <div class="chart-legend" id="severity-legend" aria-hidden="true"></div>
      </div>

      <!-- Top Services Chart -->
      <div class="card">
        <h2>Top Services</h2>
        <div class="chart-bar-container" id="service-bars">
          <!-- Populated dynamically via JS -->
        </div>
      </div>

      <!-- Filters Panel -->
      <div class="card">
        <h2>Filter Severity</h2>
        <div class="filter-group">
          <label><input type="checkbox" id="fil-critical" checked onchange="applyFilters()"> Critical</label>
          <label><input type="checkbox" id="fil-high" checked onchange="applyFilters()"> High</label>
          <label><input type="checkbox" id="fil-medium" checked onchange="applyFilters()"> Medium</label>
          <label><input type="checkbox" id="fil-low" checked onchange="applyFilters()"> Low</label>
          <label><input type="checkbox" id="fil-info" checked onchange="applyFilters()"> Info</label>
          <label><input type="checkbox" id="fil-client-safe" checked onchange="applyFilters()"> Client Safe</label>
        </div>
      </div>
    </div>

    <!-- Main column: the coverage banner and the findings table, stacked so the
         table owns the full content width instead of the narrow sidebar track. -->
    <div class="main-content">
    <!-- Scan coverage, honest account of what was NOT fully scanned, so "N
         findings" is never read as a clean bill of health when files went
         unscanned (unreadable, over-size, truncated archives, …). -->
    <div class="coverage-panel" id="coverage-panel" style="display: none;">
      <div class="card">
        <h2><span class="coverage-dot" id="coverage-dot"></span> Scan Coverage</h2>
        <div class="coverage-note" id="coverage-note"></div>
        <ul class="coverage-list" id="coverage-list"></ul>
      </div>
    </div>

    <!-- Findings Listing -->
    <div class="findings-panel">
      <div class="card">
        <h2>Findings</h2>
        
        <!-- Search bar -->
        <input type="text" class="search-input" id="search-box" placeholder="Search by file path, detector ID, service..." aria-label="Search findings by file path, detector, or service" aria-describedby="result-count" oninput="applyFiltersDebounced()">
        <div class="result-count" id="result-count" aria-live="polite" aria-atomic="true">Showing 0 findings.</div>

        <!-- Status Filter Tabs -->
        <div class="tabs" role="tablist" aria-label="Verification status filter">
          <button class="tab-btn active" id="tab-all" role="tab" aria-selected="true" onclick="setStatusTab('all')">ALL</button>
          <button class="tab-btn" id="tab-live" role="tab" aria-selected="false" onclick="setStatusTab('live')">LIVE</button>
          <button class="tab-btn" id="tab-revoked" role="tab" aria-selected="false" onclick="setStatusTab('revoked')">REVOKED</button>
          <button class="tab-btn" id="tab-unverifiable" role="tab" aria-selected="false" onclick="setStatusTab('unverifiable')">UNVERIFIABLE</button>
        </div>

        <!-- Table view -->
        <div class="table-wrapper">
          <table>
            <thead>
              <tr>
                <th scope="col">Detector</th>
                <th scope="col">Service</th>
                <th scope="col">File Path & Line</th>
                <th scope="col">Severity</th>
                <th scope="col">Verification</th>
              </tr>
            </thead>
            <tbody id="findings-table-body">
              <!-- Populated dynamically via JS -->
            </tbody>
          </table>
        </div>

        <!-- Empty state helper -->
        <div class="empty-state" id="empty-view" style="display: none;">
          No findings match the active search and filter criteria.
        </div>
      </div>
    </div>
    </div>
  </div>
</div>