root 6ee68f5521 Add sync anomaly detection, BU drift monitoring, and findings count investigation
- Add BU drift checker that classifies archived findings as BU reassignment,
  severity drift, closure, or decommission via unfiltered Ivanti API queries
- Add post-sync anomaly summary with significance threshold and classification
  breakdown stored in ivanti_sync_anomaly_log table
- Add per-finding BU tracking that detects BU changes across syncs and records
  them in ivanti_finding_bu_history table
- Add drift guard that skips trend history writes when total drops more than 50%
- Add CLOSED_GONE archive state for findings that vanish from the closed set
- Add anomaly banner UI on Vulnerability Triage page for significant sync changes
- Add API endpoints for anomaly latest/history and BU change tracking
- Add diagnostic scripts for drift checking and BU reassignment verification
- Add investigation document and xlsx export for the April 2026 BU reassignment
  incident where 109 findings were moved to SDIT-CSD-ITLS-PIES
- Migrations required: add_closed_gone_state.js, add_sync_anomaly_tables.js
2026-04-24 20:34:34 +00:00

STEAM Security Dashboard

A self-hosted vulnerability management dashboard for the NTS-AEO-STEAM and NTS-AEO-ACCESS-ENG business units. Centralises CVE tracking, Ivanti host finding triage, AEO compliance posture, FP and Archer exception workflows, and internal documentation in a single interface.


Table of Contents


Overview

The STEAM Security Dashboard answers a common problem in vulnerability management: tracking which CVEs have been addressed, whether supporting vendor documentation exists, where each finding is in the remediation or exception workflow, and how the team's overall AEO compliance posture is trending week over week.

The application provides:

  • A searchable, filterable CVE list with per-vendor tracking and document storage
  • NVD API integration to auto-populate CVE metadata
  • Ivanti/RiskSense integration — sync open host findings with live FP workflow tracking
  • Reporting page with donut charts, advanced per-column filtering, inline editing, Ivanti Queue, and CSV/XLSX export
  • Ivanti Queue — personal staging list for batch-processing FP, Archer, and CARD workflows
  • AEO Compliance page — weekly xlsx upload, diff preview, per-team metric health cards, device-level violation tracking with notes history
  • Archer risk acceptance ticket tracking (EXC numbers) linked to CVE/vendor pairs
  • A knowledge base for internal documentation and policies
  • Group-based access control (Admin, Standard_User, Leadership, Read_Only) with a full audit trail

Tech Stack

Layer Technology
Backend Node.js 18+, Express 5
Database SQLite3
File uploads Multer 2
Auth bcryptjs, cookie-based sessions, express-rate-limit
Frontend React 19, lucide-react, recharts, xlsx, react-markdown, rehype-sanitize, mermaid
Compliance xlsx parsing Python 3, pandas, openpyxl

Prerequisites

  • Node.js 18 or later
  • npm
  • Python 3 with python3-pandas and python3-openpyxl apt packages (required for compliance xlsx parsing)

Installation

1. Clone the repository

git clone <repo-url>
cd cve-dashboard

2. Install backend dependencies

npm install

3. Install frontend dependencies

cd frontend
npm install

4. Install Python dependencies

Install via apt — this is the correct approach on Ubuntu/Debian and mirrors the dev server setup:

apt install -y python3-pandas python3-openpyxl

If apt packages are unavailable or you need a specific version, see docs/python-venv-setup.md for the venv fallback approach.

A bulk notes import script (import_notes_from_csv.py) is also available in backend/scripts/ for maintenance tasks like backfilling notes from a CSV. It uses only Python stdlib.

5. Configure environment variables

Create backend/.env — the server will refuse to start without SESSION_SECRET:

cd backend
cp .env.example .env
# Edit .env and set SESSION_SECRET to a random string:
#   openssl rand -base64 32

See Configuration for all available options.

6. Initialize the database

Run once from the backend/ directory to create the SQLite database, all tables, indexes, and a default admin user:

cd backend
node setup.js

This creates backend/cve_database.db and generates a random admin password printed to stdout. Save the password — it is only shown once.

7. Run database migrations

Apply all feature migrations in order:

cd backend
node migrations/add_knowledge_base_table.js
node migrations/add_archer_tickets_table.js
node migrations/add_ivanti_sync_table.js
node migrations/add_ivanti_findings_tables.js
node migrations/add_ivanti_todo_queue_table.js
node migrations/add_card_workflow_type.js
node migrations/add_todo_queue_ip_address.js
node migrations/add_todo_queue_hostname.js
node migrations/add_compliance_tables.js
node migrations/add_finding_archive_tables.js
node migrations/add_archer_tickets_timestamps.js
node migrations/add_ivanti_counts_history_table.js
node migrations/add_fp_submissions_table.js
node migrations/add_user_groups.js
node migrations/add_created_by_columns.js
node migrations/add_fp_submission_editing.js
node migrations/add_granite_workflow_type.js
node migrations/add_compliance_notes_group_id.js

8. Build the frontend

cd frontend
npm run build

Or use npm start for the development server (see Running the Application).


Configuration

The application is configured via .env files. These files are gitignored and must be created manually per environment.

Backend: backend/.env

PORT=3001
API_HOST=localhost
CORS_ORIGINS=http://YOUR_IP:3000
SESSION_SECRET=<generate with: openssl rand -base64 32>
# NODE_ENV=production  — see note below

# Optional: NVD API key for higher rate limits (50 req/30s vs 5 req/30s)
# Register at https://nvd.nist.gov/developers/request-an-api-key
NVD_API_KEY=your-key-here

# Ivanti / RiskSense integration (required for Reporting page sync)
IVANTI_API_KEY=your-ivanti-api-key
IVANTI_CLIENT_ID=1550
# Optional: filter workflows to a specific person's submissions
IVANTI_FIRST_NAME=
IVANTI_LAST_NAME=
# Set to 'true' if your network has SSL inspection / self-signed certs
IVANTI_SKIP_TLS=false

SESSION_SECRET is required. The server will exit on startup if it is not set. Generate one with openssl rand -base64 32.

NODE_ENV and the Secure cookie flag: When NODE_ENV=production, session cookies are set with the Secure flag, which means the browser will only send them over HTTPS connections. If you are running the application over plain HTTP (no TLS/SSL), you must leave NODE_ENV unset or set it to development — otherwise login will succeed but every subsequent API request will return 401 because the browser silently drops the cookie. Only set NODE_ENV=production when the application is served behind HTTPS (e.g., via a reverse proxy with TLS termination).

Frontend: frontend/.env

REACT_APP_API_BASE=http://YOUR_IP:3001/api
REACT_APP_API_HOST=http://YOUR_IP:3001

Replace YOUR_IP with the machine's IP address or hostname. Use localhost for local-only access.

Important: React caches environment variables at build/start time. After changing frontend/.env, fully restart the frontend process — a browser refresh alone is not sufficient.


Running the Application

From the project root:

./start-servers.sh   # Starts backend and frontend in the background
./stop-servers.sh    # Stops all servers

The start script saves PIDs to backend.pid and frontend.pid. Logs are written to backend/backend.log and frontend/frontend.log.

Running manually

# Terminal 1 — backend
cd backend
node server.js

# Terminal 2 — frontend (development server)
cd frontend
npm start

Default ports

Service URL
Frontend http://localhost:3000
Backend API http://localhost:3001

Features

Authentication and User Groups

All routes require authentication. Four user groups are supported:

Group Permissions
Admin Full CRUD on all resources, user management, audit log access, export all data, delete any resource regardless of ownership
Standard_User View all data, create and edit resources, delete own resources (with state and compliance restrictions), basic export (CSV/XLSX)
Leadership View all data, export reports/compliance/visualizations, no create/edit/delete
Read_Only View all data only — no create, edit, delete, or export

Standard User delete restrictions:

  • Can only delete resources they created (created_by ownership check)
  • Cannot delete findings marked as resolved or closed
  • Cannot delete tickets linked to compliance reports
  • CVE deletion triggers a cascade impact check — if any associated Archer or JIRA ticket is compliance-linked, deletion is blocked and requires Admin intervention

Sessions expire after 24 hours. Session tokens are stored in httpOnly cookies. Login is rate-limited to 20 attempts per 15-minute window.

Migration from legacy roles: The add_user_groups.js migration automatically maps existing users: adminAdmin, editorStandard_User, viewerRead_Only. Unrecognized or NULL roles default to Read_Only.


Home — CVE Management

The home page is the primary CVE research and tracking tool.

CVE List

  • Search CVEs by keyword (matches CVE ID, vendor, description)
  • Filter by vendor, severity (Critical / High / Medium / Low), and status
  • Color-coded severity badges: Critical (red), High (amber), Medium (sky blue), Low (green)
  • Paginated list view

CVE Operations (Admin/Standard_User)

  • Add a new CVE entry — NVD auto-fill populates description, severity, and published date automatically
  • Edit any field on an existing CVE entry
  • Update status for all vendor rows matching a CVE ID in one click
  • Delete a single vendor entry or all vendor entries for a CVE ID (ownership restrictions apply for Standard_User)
  • The same CVE ID can be tracked across multiple vendors independently

Document Management

  • Upload documents attached to a CVE/vendor pair
  • Supported document types: advisory, email, screenshot, patch, other
  • Allowed file extensions: PDF, images (PNG, JPG, GIF, BMP, TIFF), Office documents (DOC, DOCX, XLS, XLSX, PPT, PPTX), text files (TXT, MD, CSV, LOG), email files (MSG, EML), and others (RTF, HTML, XML, JSON, YAML, ODF variants, ZIP, GZ, TAR, 7Z)
  • File size limit: 10 MB per upload
  • Admins can delete documents

NVD Integration

  • Auto-fill CVE description, severity, and published date from the NIST NVD API 2.0 when adding a new CVE
  • Bulk NVD Sync (Admin/Standard_User): fetch updated metadata for all CVEs in the database in one operation
  • CVSS severity cascade: v3.1 preferred, then v3.0, then v2.0
  • Rate-limit aware: respects NVD's 5 req/30s unauthenticated limit; with NVD_API_KEY the limit increases to 50 req/30s

Archer Ticket Quick Navigation

  • Archer EXC numbers shown on CVE rows
  • Clicking an EXC badge navigates to the Reporting page pre-filtered to findings with that EXC number

Calendar Widget

  • Shows current month with red dot indicators on dates where Ivanti findings are due
  • Click a date to navigate to the Reporting page filtered to that due date

Reporting — Host Findings

The Reporting page is the core operational view for remediation tracking. It integrates with Ivanti/RiskSense to show all host findings for the configured business units.

Syncing Data

Click Sync (top right) to pull the latest findings from Ivanti. Sync requires Admin or Standard_User group. The sync:

  1. Fetches all open host findings matching your BU filters and severity range (8.59.9 VRR)
  2. Fetches the closed finding count separately
  3. Sweeps closed findings to capture FP workflow states (including Approved FPs now closed)
  4. Stores everything in the local SQLite cache

Findings are also auto-synced on a 24-hour schedule. The last sync timestamp is shown at the top of the page.

IVANTI_API_KEY must be set in backend/.env for sync to work.

Metric Charts

Chart What it shows
Open vs Closed Total open vs closed host findings direct from the Ivanti API
Action Coverage Findings by action taken: FP Request · Archer Exception · Pending. Click a segment to filter the table.
FP Finding Status How many findings are in each FP workflow state (Actionable, Requested, Reworked, Approved, Rejected, Expired)
FP Workflow Status How many unique FP# ticket IDs are in each state — one ticket can cover many findings

Findings Table

Each row represents a single Ivanti host finding.

Column Description
Finding ID Ivanti finding identifier
Severity Numerical VRR score with group label (CRITICAL / HIGH)
Title Vulnerability title
CVEs Associated CVE IDs — up to 2 shown, remainder as "+N" badge
Host Hostname — inline editable
IP Address Host IP address
DNS DNS/FQDN — inline editable
Due Date Remediation due date; red if overdue, amber if within 30 days
SLA SLA status: OVERDUE / AT_RISK / WITHIN_SLA
BU Business unit
Workflow FP# ticket ID and state badge — colour-coded by urgency
Last Found Last detection date from Ivanti
Notes Free-form notes — inline editable, persists across syncs

Inline editing: Click a Host or DNS cell to override the Ivanti value. An amber dot (●) marks overridden cells; use the revert button (↻) to restore the original. Overrides survive re-syncs. Requires Admin or Standard_User group.

CVE Tooltips: Hover over any CVE badge in the table to see a tooltip with the CVE description and severity (if the CVE exists in the local database). Tooltips appear after a 300ms delay, are cached in memory for the session, and auto-position to stay within the viewport.

Filtering: Click ⊙ on any column header for multi-select filtering. The — empty — option filters to findings with no value in that column. Multiple filters are ANDed. The Action Coverage chart also acts as a filter.

Column management: Toggle visibility and drag to reorder via the Columns button. Order and visibility persist to localStorage.

Row visibility: Hide individual rows by clicking the EyeOff icon on any row, or select multiple rows via checkboxes and click Hide Selected in the bulk action toolbar. Hidden rows are excluded from the table, the Action Coverage chart, and exports. Use the Hidden (N) button in the toolbar to view and restore hidden rows individually or all at once. Hidden row state persists to localStorage across sessions. Row hiding is a personal view preference available to all user groups.

Export: Click Export to download the current filtered view as CSV or XLSX. Hidden rows and filtered rows are both excluded from exports. Requires Admin, Standard_User, or Leadership group.


Ivanti Queue

A personal staging list for batch-processing FP, Archer, and CARD workflows without context-switching into Ivanti mid-review. Requires Admin or Standard_User group.

Adding items: Check the checkbox at the far left of any finding row. A popover appears:

  • For FP and Archer items: enter the Vendor / Platform (e.g., "Juniper MX", "Cisco IOS-XE")
  • For CARD items: no vendor entry required — the IP address is captured automatically
  • Select the workflow type: FP, Archer, or CARD
  • Click Add to Queue — the row checkbox turns solid blue

Queue panel: Click the Queue button (top right of Reporting page) to open the slide-out panel:

  • CARD items appear at the top in their own section with the IP address displayed
  • FP and Archer items are grouped alphabetically by vendor below
  • Badges show workflow type: amber = FP, sky = Archer, green = CARD

Working the queue:

  • Check the green checkbox on an item to mark it complete (strikethrough at reduced opacity)
  • Delete individual items with the trash icon, or select multiple and use Delete (N)
  • Clear Completed removes all marked-complete items at once
  • Create FP Workflow — select pending FP items and click to open the FP Workflow modal, which submits a False Positive workflow batch directly to the Ivanti API with form fields, file attachments, and scope override. Attachments can be local file uploads or documents selected from the CVE document library — library documents are read from disk and sent to Ivanti identically to local uploads. Successful submission marks the queue items as complete and records the submission locally.

Redirecting completed items:

  • Completed items show a redirect button (↱) next to the delete icon
  • Click redirect to open a modal where you select the target workflow type (FP, Archer, or CARD) and vendor (required for FP/Archer)
  • Redirecting creates a new pending queue item with the same finding data under the new workflow type — the original completed item is preserved
  • This is useful when a CARD inventory fix is done but the finding still needs an FP or Archer workflow, or when an item was assigned to the wrong workflow initially
  • Not every completed item needs a redirect — it's an optional action for items that require further processing

Queue items are stored in the database, are personal to your login, and persist across sessions and page refreshes.


Compliance — AEO Posture

The Compliance page tracks NTS-AEO team posture against the AEO compliance framework using weekly xlsx reports exported from the NTS_AEO reporting system.

Upload Workflow

Admin and Standard_User groups can upload a new compliance report via the Upload Report button:

  1. Drop or browse for the NTS_AEO_YYYY_MM_DD.xlsx file
  2. The backend extracts the xlsx schema and runs a drift check against the parser configuration (compliance_config.json). If structural drift is detected, a drift review phase is shown before the diff preview:
    • Breaking findings (red) — missing core columns or detail sheets — block the upload until the config is updated
    • Silent-miss findings (amber) — unknown metrics or sheets that will be miscategorised — warn but allow proceeding
    • Cosmetic findings (muted) — new columns or stale config entries — informational only
    • Admins can click Reconcile Config to auto-patch the parser configuration and re-run the check
  3. If no breaking drift exists, the diff preview is shown — new violations, resolved items, and recurring items since the last upload
  4. Click Confirm Upload to commit. The upload is recorded and the device table updates immediately.

The report date is extracted automatically from the filename.

Upload rollback: Admins can roll back the most recent upload via POST /api/compliance/rollback/:uploadId. Rolling back deletes new items introduced by that upload, re-activates items it resolved, and decrements seen counts on recurring items.

Metric Health Cards

Each AEO metric (e.g., 2.3.4i, 5.2.4) is shown as a health card displaying:

  • Compliance percentage vs target
  • Status: Meets/Exceeds Target · Within 15% of Target · Below 15% of Target

Click a card to filter the device table to only devices failing that metric.

Device Table

Shows all devices currently failing one or more metrics (Active tab) or previously resolved (Resolved tab). Columns: Hostname, IP Address, Type, Failing Metrics, Times Seen. Click a row to open the detail panel.

Detail Panel

A slide-out panel for a selected device showing:

  • Failing Metrics — each metric with surfaced extra fields (CVEs, SLA status, due date, OS, EoL, Splunk last seen, MFA software)
  • For 2.3.x vulnerability metrics: the Ivanti_Vulnerability_ID is displayed with a View in Reporting → button that navigates directly to the Reporting page
  • Resolved Metrics — previously failing metrics now back in compliance
  • History — how many times the device has appeared on the report and since when
  • Notes — timestamped notes per metric with a multi-metric selector if multiple metrics are failing. Notes can be deleted by the author or an Admin — deleting a multi-metric note removes it from all linked metrics. Requires Admin or Standard_User group.

Notes persist across uploads and are keyed to the device hostname and metric ID.

Teams

Only STEAM and ACCESS-ENG teams are tracked. The team selector at the top of the page switches context between them.


Knowledge Base

A document library for internal reference material — policies, runbooks, vendor advisories, and process guides.

  • Upload documents with a title, optional description, and category (Admin/Standard_User)
  • View documents inline in the browser (PDFs render in a sandboxed iframe; Markdown files render as sanitized HTML)
  • Download any document
  • Filter and browse by category
  • Admin can delete any article; Standard_User can delete articles they created
  • All authenticated users can view

Allowed file types: PDF, Markdown, TXT, Office documents (DOC, DOCX, XLS, XLSX, PPT, PPTX), HTML, JSON, YAML, and images (PNG, JPG, GIF).


Exports

Bulk export tools for reports and data extracts. Available to Admin, Standard_User, and Leadership groups. Read_Only users cannot access the Exports page.


Archer Risk Acceptance Tickets

Track Archer exception tickets (EXC numbers) linked to specific CVE/vendor pairs.

  • EXC number format: EXC-NNNNN
  • Statuses: Draft, Open, Under Review, Accepted
  • Optional Archer URL field for deep-linking to the Archer record
  • Filter tickets by CVE ID, vendor, or status
  • Clicking an EXC badge on the Home page navigates to the Reporting page pre-filtered to findings with that EXC number in their notes
  • Admin/Standard_User can create, edit, and delete tickets (Standard_User delete subject to ownership and compliance linkage checks)

Admin Panel

The Admin Panel is a full-page, tabbed interface accessible only to Admin-group users. It replaces the previous inline modal rendering and follows the dashboard's dark tactical intelligence theme. Three tabs provide consolidated access to administrative functions:

User Management — the default tab. Displays a themed user table with group badges (Admin in red, Standard_User in accent blue, Leadership in amber, Read_Only in muted grey). Admins can create, edit, and delete users, change group assignments, and toggle active status — all through inline forms styled to match the dashboard. Admins cannot demote themselves or deactivate their own account. Deactivating a user immediately invalidates all their active sessions. All group changes are audit-logged with previous and new group values.

Audit Log — a paginated, filterable log table showing every state-changing action with timestamp, username, action type, entity type, entity ID, details, and IP address. Action types are colour-coded: login in green, delete in red, create in accent blue, update in amber. Filter by username, action type, entity type, and date range. Results are paginated at 25 per page.

System Info — stat cards showing total user count, active user count, total audit log entries, and users who logged in within the last 7 days. A "Recent Activity" section lists the 10 most recent audit log entries.

The UserMenu quick-access links ("Manage Users", "Audit Log") continue to open the existing modal components for fast access without navigating to the admin page.


Scripts

backend/scripts/parse_compliance_xlsx.py

Called automatically by the compliance upload flow. Parses the NTS_AEO xlsx report and outputs structured JSON to stdout for consumption by the Node compliance route. Reads metric categories, core columns, and skip sheets from compliance_config.json (shared with the drift checker).

  • Reads all detail sheets; skips sheets listed in skip_sheets
  • Filters to rows where Compliant == False
  • Extracts hostname, IP, device type, team, and metric ID per row
  • Captures all non-core columns in extra_json (CVEs, SLA status, OS, EoL, Splunk, MFA, Ivanti_Vulnerability_ID, etc.)
  • Parses Summary sheet for per-team metric health (compliance_pct, target, status)
  • Extracts report date from the filename (NTS_AEO_YYYY_MM_DD.xlsx)

Dependencies: pandas>=2.0.0, openpyxl>=3.0.0

backend/scripts/extract_xlsx_schema.py

Called by the preview endpoint before parsing. Extracts the structural schema of an xlsx file as JSON — sheet names, first-row column headers per sheet, and unique metric values from the Summary sheet. The Node.js drift checker compares this schema against compliance_config.json to detect breaking, silent-miss, and cosmetic drift.

Dependencies: openpyxl>=3.0.0

backend/scripts/compliance_config.json

Shared parser configuration file — the single source of truth for metric_categories (metric ID → category mapping), core_cols (columns that become main item fields), and skip_sheets (sheets excluded from parsing). Read by both parse_compliance_xlsx.py and the Node.js driftChecker.js module. Admins can auto-patch this file via the Reconcile Config button in the upload modal.


API Reference

All endpoints are prefixed with /api. All endpoints except /api/auth/login and /api/auth/logout require a valid session cookie. Group requirements are listed per endpoint.

Auth

Method Path Group Description
POST /api/auth/login Public Log in, receive session cookie (rate-limited: 20/15min)
POST /api/auth/logout Public Invalidate session
GET /api/auth/me Any Get current user info (returns group field)
POST /api/auth/cleanup-sessions Admin Delete expired sessions

CVEs

Method Path Group Description
GET /api/cves Any List CVEs; query params: search, vendor, severity, status
POST /api/cves Admin, Standard_User Create a new CVE entry
PUT /api/cves/:id Admin, Standard_User Update a CVE entry by row ID
PATCH /api/cves/:cveId/status Admin, Standard_User Update status for all vendor rows matching a CVE ID
DELETE /api/cves/:id Admin, Standard_User Delete a single CVE vendor entry (ownership + cascade check for Standard_User)
DELETE /api/cves/by-cve-id/:cveId Admin, Standard_User Delete all vendor entries for a CVE ID (ownership + cascade check for Standard_User)
GET /api/cves/check/:cveId Any Quick check: existence and status of a CVE
GET /api/cves/distinct-ids Any All distinct CVE IDs (used by NVD sync)
GET /api/cves/:cveId/vendors Any All vendor entries for a specific CVE ID
GET /api/cves/compliance Any Document compliance status view
GET /api/cves/:cveId/tooltip Any Get CVE description and severity for tooltip display (truncated to 300 chars)

Documents

Method Path Group Description
GET /api/cves/:cveId/documents Any List documents for a CVE; optional ?vendor= filter
POST /api/cves/:cveId/documents Admin, Standard_User Upload a document for a CVE/vendor pair
DELETE /api/documents/:id Admin Delete a document and its file from disk

NVD

Method Path Group Description
GET /api/nvd/lookup/:cveId Any Look up a single CVE in the NVD 2.0 API
POST /api/cves/nvd-sync Admin, Standard_User Bulk update CVE metadata from NVD

JIRA Tickets

Method Path Group Description
GET /api/jira-tickets Any List tickets; optional filters: cve_id, vendor, status
POST /api/jira-tickets Admin, Standard_User Create a JIRA ticket
PUT /api/jira-tickets/:id Admin, Standard_User Update a JIRA ticket
DELETE /api/jira-tickets/:id Admin, Standard_User Delete a JIRA ticket (ownership + compliance check for Standard_User)

Ivanti — Host Findings

Method Path Group Description
GET /api/ivanti/findings Any Get cached findings with notes and overrides merged in
POST /api/ivanti/findings/sync Admin, Standard_User Trigger an immediate findings sync from Ivanti
GET /api/ivanti/findings/counts Any Open vs closed finding totals
GET /api/ivanti/findings/fp-workflow-counts Any FP workflow state breakdown
PUT /api/ivanti/findings/:findingId/override Admin, Standard_User Override hostName or dns; empty value clears the override
PUT /api/ivanti/findings/:findingId/note Admin, Standard_User Save or update a finding note (max 255 chars)

Ivanti — Workflows

Method Path Group Description
GET /api/ivanti/workflows Any Get cached workflow data
POST /api/ivanti/workflows/sync Admin, Standard_User Trigger an immediate workflow sync

Ivanti — FP Workflow Submission

Method Path Group Description
GET /api/ivanti/fp-workflow/documents/search Any Search the CVE document library by name, CVE ID, or vendor; returns up to 50 matches
POST /api/ivanti/fp-workflow Admin, Standard_User Submit an FP workflow batch to Ivanti API (multipart/form-data with local attachments and/or libraryDocIds)
GET /api/ivanti/fp-workflow/submissions Any List FP submissions for the current user
PUT /api/ivanti/fp-workflow/submissions/:id Admin, Standard_User Update an FP submission (edit form fields)
POST /api/ivanti/fp-workflow/submissions/:id/findings Admin, Standard_User Add or remove findings on an existing submission
POST /api/ivanti/fp-workflow/submissions/:id/attachments Admin, Standard_User Upload additional attachments (local files and/or libraryDocIds) to an existing submission
PATCH /api/ivanti/fp-workflow/submissions/:id/status Admin, Standard_User Update submission lifecycle status

Ivanti — Todo Queue

Method Path Group Description
GET /api/ivanti/todo-queue Any Get all queue items for the current user
POST /api/ivanti/todo-queue Admin, Standard_User Add a finding to the queue
POST /api/ivanti/todo-queue/batch Admin, Standard_User Batch-add multiple findings to the queue
PUT /api/ivanti/todo-queue/:id Admin, Standard_User Update a queue item (mark complete, edit vendor/type)
POST /api/ivanti/todo-queue/:id/redirect Admin, Standard_User Redirect a completed item to a different workflow type
DELETE /api/ivanti/todo-queue/:id Admin, Standard_User Delete a single queue item
DELETE /api/ivanti/todo-queue/completed Admin, Standard_User Delete all completed queue items

Ivanti — Archive

Method Path Group Description
GET /api/ivanti/archive Any Get finding archive data for severity score drift tracking

Compliance

Method Path Group Description
POST /api/compliance/preview Admin, Standard_User Parse an xlsx upload, run drift check, and return drift report + diff + temp file path
POST /api/compliance/commit Admin, Standard_User Commit a previewed upload to the database
POST /api/compliance/reconcile-config Admin Auto-patch compliance_config.json to resolve breaking and silent-miss drift findings
POST /api/compliance/rollback/:uploadId Admin Roll back the most recent upload (deletes new items, re-activates resolved items)
GET /api/compliance/uploads Any List all compliance upload records
GET /api/compliance/summary Any Metric health summary; ?team=STEAM
GET /api/compliance/items Any Device list; ?team=STEAM&status=active
GET /api/compliance/items/:hostname Any Full detail for a device (metrics + notes)
GET /api/compliance/notes/:hostname/:metricId Any Notes for a specific hostname/metric
POST /api/compliance/notes Admin, Standard_User Add a note for a hostname/metric; accepts metric_ids array for multi-metric notes
DELETE /api/compliance/notes/:id Admin, Standard_User Delete a note by ID; ?group=true deletes all notes sharing the same group_id. Author or Admin only.

Knowledge Base

Method Path Group Description
POST /api/knowledge-base/upload Admin, Standard_User Upload a new knowledge base document
GET /api/knowledge-base Any List all articles
GET /api/knowledge-base/:id Any Get article metadata
GET /api/knowledge-base/:id/content Any Get file content for inline display
GET /api/knowledge-base/:id/download Any Download the file
DELETE /api/knowledge-base/:id Admin, Standard_User Delete article and file (Standard_User: own articles only)

Archer Tickets

Method Path Group Description
GET /api/archer-tickets Any List tickets; optional filters: cve_id, vendor, status
GET /api/archer-tickets/status-trend Any Ticket counts by date and status for pipeline chart
POST /api/archer-tickets Admin, Standard_User Create a new Archer ticket
PUT /api/archer-tickets/:id Admin, Standard_User Update an Archer ticket
DELETE /api/archer-tickets/:id Admin, Standard_User Delete an Archer ticket (ownership + compliance check for Standard_User)

Users (Admin only)

Method Path Group Description
GET /api/users Admin List all users
GET /api/users/:id Admin Get a single user
POST /api/users Admin Create a user
PATCH /api/users/:id Admin Update a user
DELETE /api/users/:id Admin Delete a user

Audit Logs (Admin only)

Method Path Group Description
GET /api/audit-logs Admin Paginated audit log; filters: user, action, entityType, startDate, endDate
GET /api/audit-logs/actions Admin List distinct action types for filter dropdowns

Utility

Method Path Group Description
GET /api/vendors Any List all distinct vendor names
GET /api/stats Any Dashboard statistics

Architecture

cve-dashboard/
├── start-servers.sh          # Start backend + frontend in background
├── stop-servers.sh           # Stop all servers
├── package.json              # Root package.json (backend dependencies)
│
├── backend/
│   ├── server.js             # Express app — routes, middleware, security headers
│   ├── setup.js              # One-time DB initialization and default admin creation
│   ├── cve_database.db       # SQLite database (gitignored)
│   ├── uploads/              # File storage root (gitignored)
│   │   ├── <CVE-ID>/<vendor>/    # CVE documents
│   │   ├── knowledge_base/       # Knowledge base documents
│   │   └── temp/                 # Temporary upload staging
│   ├── routes/
│   │   ├── auth.js               # Login, logout, session check, rate limiting
│   │   ├── users.js              # User CRUD (admin)
│   │   ├── auditLog.js           # Audit log viewer (admin)
│   │   ├── nvdLookup.js          # NVD API proxy
│   │   ├── knowledgeBase.js      # Knowledge base document management
│   │   ├── archerTickets.js      # Archer EXC ticket CRUD
│   │   ├── ivantiWorkflows.js    # Ivanti workflow batch sync and cache
│   │   ├── ivantiFindings.js     # Ivanti host findings sync, notes, overrides, FP counts
│   │   ├── ivantiTodoQueue.js    # Ivanti Queue — personal FP/Archer/CARD staging list
│   │   ├── ivantiArchive.js      # Finding archive for severity score drift
│   │   └── compliance.js         # AEO compliance upload, diff, device tracking, notes
│   ├── middleware/
│   │   └── auth.js               # requireAuth and requireGroup middleware
│   ├── helpers/
│   │   ├── auditLog.js           # logAudit helper (fire-and-forget)
│   │   ├── driftChecker.js       # Schema drift detection: compareSchemaToDrift(), loadConfig(), reconcileConfig()
│   │   └── ivantiApi.js          # Ivanti API HTTP helpers (multipart, JSON, form POST)
│   ├── migrations/               # Sequential migration scripts (run manually with node)
│   └── scripts/
│       ├── compliance_config.json        # Shared parser config (metric_categories, core_cols, skip_sheets)
│       ├── extract_xlsx_schema.py        # Extracts xlsx structure as JSON for drift checking
│       ├── parse_compliance_xlsx.py      # Parses NTS_AEO xlsx compliance reports
│       ├── import_notes_from_csv.py      # Bulk-import finding notes from CSV
│       └── requirements.txt              # pandas, openpyxl
│
└── frontend/
    └── src/
        ├── App.js                    # Home dashboard — CVE list, filters, modals, calendar
        ├── App.css                   # Global styles and CSS variables
        ├── contexts/
        │   └── AuthContext.js        # Auth state provider (login, logout, group helpers)
        └── components/
            ├── LoginForm.js              # Login page
            ├── NavDrawer.js              # Side navigation drawer (Admin Panel link for Admin group)
            ├── UserMenu.js               # User dropdown in header (shows group badge)
            ├── CalendarWidget.js         # Due-date calendar with Ivanti finding indicators
            ├── UserManagement.js         # Admin user management modal (quick-access from UserMenu)
            ├── AuditLog.js               # Admin audit log modal (quick-access from UserMenu)
            ├── NvdSyncModal.js           # Bulk NVD sync dialog
            ├── KnowledgeBaseModal.js     # Knowledge base upload/list modal
            ├── KnowledgeBaseViewer.js    # Inline document viewer (sandboxed iframe, sanitized markdown)
            ├── ConfirmModal.js           # Themed confirmation dialog (replaces window.confirm)
            ├── CveTooltip.js             # Hover tooltip for CVE badges (portal-rendered, cached)
            ├── RedirectModal.js          # Queue item redirect modal (workflow type + vendor selection)
            └── pages/
                ├── AdminPage.js              # Admin panel: user management, audit log, system info
                ├── ReportingPage.js          # Host findings: charts, table, queue, export
                ├── CompliancePage.js         # AEO compliance: metric cards, device table
                ├── ComplianceUploadModal.js  # xlsx upload with diff preview
                ├── ComplianceDetailPanel.js  # Per-device metrics, history, notes
                ├── ComplianceChartsPanel.js  # Compliance trend charts
                ├── IvantiCountsChart.js      # Ivanti counts history chart
                ├── ArchiveSummaryBar.js      # Finding archive summary
                ├── KnowledgeBasePage.js      # Knowledge base page
                └── ExportsPage.js            # Exports page (group-gated)

Database Schema

Core tables (created by setup.js)

cves — One row per CVE/vendor pair. UNIQUE(cve_id, vendor). Includes created_by column for ownership tracking.

documents — Files attached to a CVE/vendor pair. Foreign key to cves(cve_id) with ON DELETE CASCADE.

required_documents — Vendor-specific document requirements.

users — Accounts with group-based access control. user_group column with values: Admin, Standard_User, Leadership, Read_Only. Enforced by INSERT/UPDATE triggers. Legacy role column retained for rollback safety.

sessions — Active sessions with 24-hour expiry.

audit_logs — Append-only log of all state-changing actions.

Feature tables (added by migrations)

knowledge_base — Document library entries with title, slug, category, description, file metadata, and created_by.

archer_tickets — Archer EXC exception tickets linked to CVE/vendor pairs. UNIQUE(exc_number). Includes created_by for ownership tracking. Foreign key to cves(cve_id, vendor) with ON DELETE CASCADE.

jira_tickets — JIRA tickets linked to CVE/vendor pairs. Includes created_by. Foreign key to cves(cve_id, vendor) with ON DELETE CASCADE.

ivanti_sync_state — Single-row cache for Ivanti workflow batch data.

ivanti_findings_cache — Single-row cache for Ivanti host findings.

ivanti_finding_notes — Persistent per-finding notes keyed by finding ID. Survives cache refreshes. UNIQUE(finding_id).

ivanti_counts_cache — Single-row cache for finding metrics: open/closed counts, FP workflow state breakdowns by finding and by unique ticket ID.

ivanti_finding_overrides — Editor-applied overrides for hostName and dns fields. UNIQUE(finding_id, field).

ivanti_todo_queue — Personal per-user queue of findings staged for FP, Archer, or CARD processing. Keyed by (user_id, finding_id). Completed items can be redirected to a different workflow type via POST /:id/redirect, which creates a new pending item preserving the original finding data.

ivanti_fp_submissions — Record of FP workflow submissions to the Ivanti API. Tracks user, workflow batch ID, form fields, finding IDs, queue item IDs, attachment results, and submission status (success/partial/failed).

compliance_uploads — Record of each compliance xlsx upload: filename, report date, uploader, timestamp, and new/resolved/recurring counts.

compliance_items — One row per device/metric violation. Tracks hostname, IP, device type, team, metric ID, category, extra_json (all non-core xlsx columns), status (active/resolved), first seen upload, and times seen. Identity key: (hostname, metric_id).

compliance_notes — Timestamped notes per hostname/metric. Multiple notes per combination are supported. group_id column links notes created in the same multi-metric submission. Foreign-key linked to compliance items.

View

cve_document_status — Aggregates document counts per CVE/vendor and derives a compliance_status (Complete when an advisory is present, otherwise Missing Required Docs).


Security Model

Authentication

  • Cookie-based sessions with httpOnly: true, sameSite: lax, secure: true (in production)
  • Sessions expire after 24 hours
  • Login rate-limited to 20 attempts per 15-minute window via express-rate-limit
  • SESSION_SECRET is required — server refuses to start without it

Group-based access control

Four groups with distinct permission boundaries enforced server-side via requireGroup middleware:

Capability Admin Standard_User Leadership Read_Only
View all data
Create/edit resources
Delete own resources ✓ (restricted)
Delete any resource
Export (CSV/XLSX)
Admin panel / user management

Standard_User delete restrictions are enforced at the API level: ownership check, finding state check, compliance linkage check, and cascade impact check for CVEs.

File upload security

  • Extension allowlist enforced by Multer; executables (.exe, .js, .sh, .py, .bat, etc.) are blocked
  • MIME type prefix validation in addition to extension checking
  • 10 MB per-file size limit
  • Filenames are sanitized: path separators, .. sequences, null bytes, and non-alphanumeric characters are removed
  • Content-Disposition headers sanitize filenames to prevent header injection

Path traversal prevention

  • sanitizePathSegment() strips /, \, .., and null bytes from any value used in path.join()
  • isPathWithinUploads() verifies resolved paths stay within the uploads root before any file operation

Content security

  • Knowledge base PDF iframe uses sandbox="allow-same-origin" to prevent script execution
  • Markdown rendering uses rehype-sanitize to strip dangerous HTML
  • CSP frame-ancestors header derived from CORS_ORIGINS environment variable

Input validation

  • CVE ID must match /^CVE-\d{4}-\d{4,}$/
  • Severity must be one of: Critical, High, Medium, Low
  • Status must be one of: Open, Addressed, In Progress, Resolved
  • Archer EXC numbers must match /^EXC-\d+$/
  • Finding override field must be one of: hostName, dns
  • User group validated against: Admin, Standard_User, Leadership, Read_Only (enforced by DB triggers and app-level validation)
  • Hostname format validated with /^[a-zA-Z0-9._-]+$/ in compliance notes
  • All database operations use prepared statements — no string interpolation in SQL

Security headers

Applied to all responses:

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: SAMEORIGIN
  • X-XSS-Protection: 1; mode=block
  • Referrer-Policy: strict-origin-when-cross-origin
  • Permissions-Policy: camera=(), microphone=(), geolocation=()

Upgrading an Existing Deployment

This procedure updates the application code and schema while preserving all existing data. The database file (backend/cve_database.db) is never overwritten by git pull — it is gitignored.

# 1. Stop the running servers
cd /home/cve-dashboard
./stop-servers.sh

# 2. Pull latest code
git pull origin master

# 3. Install backend dependencies (picks up any new packages)
npm install

# 4. Install frontend dependencies
cd frontend
npm install
cd ..

# 5. Ensure SESSION_SECRET is set in backend/.env
#    If missing:
#    echo "SESSION_SECRET=$(openssl rand -base64 32)" >> backend/.env

# 6. Run all migrations (idempotent — safe to re-run, skips already-applied changes)
cd backend
node migrations/add_knowledge_base_table.js
node migrations/add_archer_tickets_table.js
node migrations/add_ivanti_sync_table.js
node migrations/add_ivanti_findings_tables.js
node migrations/add_ivanti_todo_queue_table.js
node migrations/add_card_workflow_type.js
node migrations/add_todo_queue_ip_address.js
node migrations/add_todo_queue_hostname.js
node migrations/add_compliance_tables.js
node migrations/add_finding_archive_tables.js
node migrations/add_archer_tickets_timestamps.js
node migrations/add_ivanti_counts_history_table.js
node migrations/add_fp_submissions_table.js
node migrations/add_user_groups.js
node migrations/add_created_by_columns.js
node migrations/add_fp_submission_editing.js
node migrations/add_granite_workflow_type.js
node migrations/add_compliance_notes_group_id.js
cd ..

# 7. Rebuild the frontend
cd frontend
npm run build
cd ..

# 8. Start servers
./start-servers.sh

After upgrading, clear your browser cookies and log in fresh — session format changes between versions will invalidate old sessions.

Do not re-run node setup.js on an existing deployment. It is only for first-time initialization. Re-running it will not destroy data (it checks for existing tables/users), but it is unnecessary and may create a duplicate admin account.

NODE_ENV reminder: If you are running over plain HTTP (no TLS), make sure NODE_ENV is not set to production in backend/.env. See Troubleshooting for details.


Migrations

Migrations are standalone Node.js scripts. Run them in the listed order on a fresh install. All are idempotent and safe to re-run.

cd backend
node migrations/add_knowledge_base_table.js
node migrations/add_archer_tickets_table.js
node migrations/add_ivanti_sync_table.js
node migrations/add_ivanti_findings_tables.js
node migrations/add_ivanti_todo_queue_table.js
node migrations/add_card_workflow_type.js
node migrations/add_todo_queue_ip_address.js
node migrations/add_todo_queue_hostname.js
node migrations/add_compliance_tables.js
node migrations/add_finding_archive_tables.js
node migrations/add_archer_tickets_timestamps.js
node migrations/add_ivanti_counts_history_table.js
node migrations/add_fp_submissions_table.js
node migrations/add_user_groups.js
node migrations/add_created_by_columns.js
node migrations/add_fp_submission_editing.js
node migrations/add_granite_workflow_type.js
node migrations/add_compliance_notes_group_id.js

For deployments upgrading from an older schema, the following legacy migration scripts are also available in backend/:

  • migrate_multivendor.js — Adds multi-vendor support to an older single-vendor schema
  • migrate-audit-log.js — Adds the audit_logs table to pre-auth deployments
  • migrate-to-1.1.js — General 1.0 → 1.1 schema update

Several columns (fp_workflow_counts_json, fp_id_counts_json, seen_count, summary_json) are added automatically via idempotent ALTER TABLE statements each time the server starts. No manual re-run is needed.


Troubleshooting

Login succeeds but all pages show "Error Loading" / 401 Unauthorized

Symptom: You can log in successfully, but the dashboard shows "Error Loading CVEs", "Failed to fetch", and the browser console shows 401 on every API call.

Cause: The session cookie has the Secure flag set (because NODE_ENV=production in backend/.env), but the application is being accessed over plain HTTP. Browsers silently refuse to send Secure cookies over non-HTTPS connections, so every request after login arrives without a session cookie.

Fix: Either:

  1. Remove NODE_ENV=production from backend/.env (or set it to development) and restart the backend, or
  2. Set up HTTPS (e.g., via nginx reverse proxy with TLS termination) and access the app over https://

Login fails with "Too many login attempts"

Cause: The login endpoint is rate-limited to 20 attempts per 15-minute window. Wait 15 minutes or restart the backend to reset the counter.

Server refuses to start: "SESSION_SECRET environment variable must be set"

Fix: Add a SESSION_SECRET to backend/.env:

echo "SESSION_SECRET=$(openssl rand -base64 32)" >> backend/.env

After upgrading: "user_group" errors or missing group data

Fix: Run the group migration:

cd backend
node migrations/add_user_groups.js
node migrations/add_created_by_columns.js

This maps existing roles to groups automatically (admin→Admin, editor→Standard_User, viewer→Read_Only).

Description
No description provided
Readme 20 MiB
Languages
JavaScript 98.5%
CSS 0.6%
Python 0.5%
Shell 0.3%
HTML 0.1%