| `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 |
Sessions expire after 24 hours. Session tokens arestored in `httpOnly` cookies.
**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: `admin` → `Admin`, `editor` → `Standard_User`, `viewer` → `Read_Only`. Unrecognized or NULL roles default to `Read_Only`.
---
@@ -249,11 +273,11 @@ The home page is the primary CVE research and tracking tool.
- Color-coded severity badges: Critical (red), High (amber), Medium (sky blue), Low (green)
- Paginated list view
**CVE Operations (editor/admin)**
**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
- 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**
@@ -265,7 +289,7 @@ The home page is the primary CVE research and tracking tool.
**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 (editor/admin): fetch updated metadata for all CVEs in the database in one operation
- 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
@@ -285,7 +309,7 @@ The Reporting page is the core operational view for remediation tracking. It int
#### Syncing Data
Click **Sync** (top right) to pull the latest findings from Ivanti. The sync:
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.5–9.9 VRR)
2. Fetches the closed finding count separately
3. Sweeps closed findings to capture FP workflow states (including Approved FPs now closed)
@@ -324,19 +348,19 @@ Each row represents a single Ivanti host finding.
**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.
**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.
**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`.
**Export:** Click **Export** to download the current filtered view as CSV or XLSX.
**Export:** Click **Export** to download the current filtered view as CSV or XLSX. 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.
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")
@@ -364,7 +388,7 @@ The Compliance page tracks NTS-AEO team posture against the AEO compliance frame
#### Upload Workflow
Editors and admins can upload a new compliance report via the **Upload Report** button:
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 report is parsed server-side and a **diff preview** is shown — new violations, resolved items, and recurring items since the last upload
@@ -391,7 +415,7 @@ A slide-out panel for a selected device showing:
- 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** — timestamped notes per metric with a multi-metric selector if multiple metrics are failing. Requires Admin or Standard_User group.
Notes persist across uploads and are keyed to the device hostname and metric ID.
@@ -405,11 +429,12 @@ Only **STEAM** and **ACCESS-ENG** teams are tracked. The team selector at the to
A document library for internal reference material — policies, runbooks, vendor advisories, and process guides.
- Upload documents with a title, optional description, and category
- View documents inline in the browser (PDFs render in an iframe; Markdown files render as HTML)
- 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
-Editors and admins can upload and delete; all authenticated users can view
-Admin can delete any article; Standard_User can delete articles they created
Bulk export tools for reports and data extracts. Available to Admin, Standard_User, and Leadership groups. Read_Only users cannot access the Exports page.
---
@@ -430,15 +455,18 @@ Track Archer exception tickets (EXC numbers) linked to specific CVE/vendor pairs
- 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)
---
### User Management (Admin)
- Create users with a role assignment
- Change username, email, password, role, or active status
- Create users with a group assignment (Admin, Standard_User, Leadership, Read_Only)
- Change username, email, password, group, or active status
- Group changes require confirmation; downgrading an Admin shows an additional warning
- Deactivating a user immediately invalidates all their active sessions
- Admins cannot demote themselves or deactivate their own account
- All group changes are audit-logged with previous and new group values
All endpoints are prefixed with `/api`. All endpoints except `/api/auth/login` and `/api/auth/logout` require a valid session cookie.
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 | Auth | Description |
| Method | Path | Group | Description |
|---|---|---|---|
| POST | `/api/auth/login` | Public | Log in, receive session cookie |
| 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` | Session | Get current user info |
| GET | `/api/cves` | viewer+ | List CVEs; query params: `search`, `vendor`, `severity`, `status` |
| POST | `/api/cves` | editor+ | Create a new CVE entry |
| PUT | `/api/cves/:id` | editor+ | Update a CVE entry by row ID |
| PATCH | `/api/cves/:cveId/status` | editor+ | Update status for all vendor rows matching a CVE ID |
| DELETE | `/api/cves/:id` | editor+ | Delete a single CVE vendor entry |
| DELETE | `/api/cves/by-cve-id/:cveId` | editor+ | Delete all vendor entries for a CVE ID |
| GET | `/api/cves/check/:cveId` | viewer+ | Quick check: existence and status of a CVE |
| GET | `/api/cves/distinct-ids` | viewer+ | All distinct CVE IDs (used by NVD sync) |
| GET | `/api/cves/:cveId/vendors` | viewer+ | All vendor entries for a specific CVE ID |
| 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 |
### Documents
| Method | Path | Role | Description |
| Method | Path | Group | Description |
|---|---|---|---|
| GET | `/api/cves/:cveId/documents` | viewer+ | List documents for a CVE; optional `?vendor=` filter |
| POST | `/api/cves/:cveId/documents` | editor+ | Upload a document for a CVE/vendor pair |
| DELETE | `/api/documents/:id` | admin | Delete a document and its file from disk |
| 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 | Role | Description |
| Method | Path | Group | Description |
|---|---|---|---|
| GET | `/api/nvd/lookup/:cveId` | viewer+ | Look up a single CVE in the NVD 2.0 API |
| POST | `/api/cves/nvd-sync` | editor+ | Bulk update CVE metadata from NVD |
| 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 | Role | Description |
| Method | Path | Group | Description |
|---|---|---|---|
| GET | `/api/ivanti/findings` | viewer+ | Get cached findings with notes and overrides merged in |
| POST | `/api/ivanti/findings/sync` | viewer+ | Trigger an immediate findings sync from Ivanti |
| GET | `/api/ivanti/findings/counts` | viewer+ | Open vs closed finding totals |
| GET | `/api/ivanti/findings/fp-workflow-counts` | viewer+ | FP workflow state breakdown |
| PUT | `/api/ivanti/findings/:findingId/override` | editor+ | Override `hostName` or `dns`; empty value clears the override |
| PUT | `/api/ivanti/findings/:findingId/note` | viewer+ | Save or update a finding note (max 255 chars) |
| 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 | Role | Description |
| Method | Path | Group | Description |
|---|---|---|---|
| GET | `/api/ivanti/workflows` | viewer+ | Get cached workflow data |
| POST | `/api/ivanti/workflows/sync` | viewer+ | Trigger an immediate workflow sync |
| GET | `/api/ivanti/workflows` | Any | Get cached workflow data |
| POST | `/api/ivanti/workflows/sync` | Admin, Standard_User | Trigger an immediate workflow sync |
### Ivanti Queue
### Ivanti — Todo Queue
| Method | Path | Role | Description |
| Method | Path | Group | Description |
|---|---|---|---|
| GET | `/api/ivanti/queue` | viewer+ | Get all queue items for the current user |
| POST | `/api/ivanti/queue` | editor+ | Add a finding to the queue |
**`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.
@@ -752,18 +796,47 @@ cve-dashboard/
## 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:
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,}$/`
@@ -771,14 +844,10 @@ cve-dashboard/
- 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
### Error handling
- 500 responses never expose internal error messages to the client
- Full errors are logged server-side only
- Descriptive 400/409 responses contain only application-authored validation messages
`httpOnly: true`, `sameSite: lax`, `secure: true` in production (`NODE_ENV=production`).
---
## Migrations
Migrations are standalone Node.js scripts. Run them in the listed order on a fresh install. All use `CREATE TABLE IF NOT EXISTS` or `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` and are safe to re-run.
Migrations are standalone Node.js scripts. Run them in the listed order on a fresh install. All are idempotent and safe to re-run.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.