docs: update README for group-based access control, security hardening, and current architecture
- Replace role-based docs with group-based (Admin, Standard_User, Leadership, Read_Only) - Update API reference with correct group requirements and new endpoints (JIRA tickets, archive, todo-queue) - Remove hardcoded default credentials from installation instructions - Document SESSION_SECRET as required with generation instructions - Add new migrations to install sequence (archive, timestamps, counts history, user_groups, created_by) - Update architecture tree with new files (ivantiArchive, ComplianceChartsPanel, etc.) - Update security model with rate limiting, sandbox iframe, rehype-sanitize, Content-Disposition sanitization - Update database schema docs with created_by columns, user_group triggers, cascade deletes - Fix middleware reference from requireRole to requireGroup - Remove stale admin123 references throughout
This commit is contained in:
356
README.md
356
README.md
@@ -13,7 +13,7 @@ A self-hosted vulnerability management dashboard for the NTS-AEO-STEAM and NTS-A
|
||||
- [Configuration](#configuration)
|
||||
- [Running the Application](#running-the-application)
|
||||
- [Features](#features)
|
||||
- [Authentication and User Roles](#authentication-and-user-roles)
|
||||
- [Authentication and User Groups](#authentication-and-user-groups)
|
||||
- [Home — CVE Management](#home--cve-management)
|
||||
- [Reporting — Host Findings](#reporting--host-findings)
|
||||
- [Ivanti Queue](#ivanti-queue)
|
||||
@@ -46,7 +46,7 @@ The application provides:
|
||||
- **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
|
||||
- Role-based access control with a full audit trail
|
||||
- Group-based access control (Admin, Standard_User, Leadership, Read_Only) with a full audit trail
|
||||
|
||||
---
|
||||
|
||||
@@ -54,11 +54,11 @@ The application provides:
|
||||
|
||||
| Layer | Technology |
|
||||
|---|---|
|
||||
| Backend | Node.js, Express 5 |
|
||||
| Backend | Node.js 18+, Express 5 |
|
||||
| Database | SQLite3 |
|
||||
| File uploads | Multer 2 |
|
||||
| Auth | bcryptjs, cookie-based sessions |
|
||||
| Frontend | React 19, lucide-react, xlsx |
|
||||
| Auth | bcryptjs, cookie-based sessions, express-rate-limit |
|
||||
| Frontend | React 19, lucide-react, xlsx, rehype-sanitize |
|
||||
| Compliance xlsx parsing | Python 3, pandas, openpyxl |
|
||||
| Bulk notes import | Python 3 (stdlib only) |
|
||||
|
||||
@@ -84,7 +84,6 @@ cd cve-dashboard
|
||||
### 2. Install backend dependencies
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
npm install
|
||||
```
|
||||
|
||||
@@ -107,7 +106,20 @@ apt install -y python3-pandas python3-openpyxl
|
||||
|
||||
> The bulk notes import script (`import_notes_from_csv.py`) uses only Python stdlib and does **not** require these packages.
|
||||
|
||||
### 5. Initialize the database
|
||||
### 5. Configure environment variables
|
||||
|
||||
Create `backend/.env` — the server will refuse to start without `SESSION_SECRET`:
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
cp .env.example .env
|
||||
# Edit .env and set SESSION_SECRET to a random string:
|
||||
# openssl rand -base64 32
|
||||
```
|
||||
|
||||
See [Configuration](#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:
|
||||
|
||||
@@ -116,13 +128,9 @@ cd backend
|
||||
node setup.js
|
||||
```
|
||||
|
||||
This creates `backend/cve_database.db` and a default admin account:
|
||||
- Username: `admin`
|
||||
- Password: `admin123`
|
||||
This creates `backend/cve_database.db` and generates a random admin password printed to stdout. **Save the password — it is only shown once.**
|
||||
|
||||
**Change the admin password immediately after first login.**
|
||||
|
||||
### 6. Run database migrations
|
||||
### 7. Run database migrations
|
||||
|
||||
Apply all feature migrations in order:
|
||||
|
||||
@@ -136,9 +144,14 @@ 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_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_user_groups.js
|
||||
node migrations/add_created_by_columns.js
|
||||
```
|
||||
|
||||
### 7. Build the frontend
|
||||
### 8. Build the frontend
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
@@ -159,7 +172,7 @@ The application is configured via `.env` files. These files are gitignored and m
|
||||
PORT=3001
|
||||
API_HOST=localhost
|
||||
CORS_ORIGINS=http://YOUR_IP:3000
|
||||
SESSION_SECRET=change-this-to-a-long-random-string
|
||||
SESSION_SECRET=<generate with: openssl rand -base64 32>
|
||||
NODE_ENV=production
|
||||
|
||||
# Optional: NVD API key for higher rate limits (50 req/30s vs 5 req/30s)
|
||||
@@ -176,6 +189,8 @@ IVANTI_LAST_NAME=
|
||||
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`.
|
||||
|
||||
### Frontend: `frontend/.env`
|
||||
|
||||
```env
|
||||
@@ -225,17 +240,26 @@ npm start
|
||||
|
||||
## Features
|
||||
|
||||
### Authentication and User Roles
|
||||
### Authentication and User Groups
|
||||
|
||||
All routes require authentication. Three roles are supported:
|
||||
All routes require authentication. Four user groups are supported:
|
||||
|
||||
| Role | Permissions |
|
||||
| Group | Permissions |
|
||||
|---|---|
|
||||
| `viewer` | Read-only: CVEs, documents, findings, reports, knowledge base, Archer tickets, compliance data |
|
||||
| `editor` | All viewer permissions plus: create/update CVEs, upload documents, sync Ivanti findings, save notes and overrides, manage knowledge base, manage Archer tickets, upload compliance reports, manage Ivanti Queue |
|
||||
| `admin` | All editor permissions plus: delete documents, delete reports, manage users, view audit logs |
|
||||
| `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 are stored 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.
|
||||
| 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.
|
||||
**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
|
||||
- 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).
|
||||
|
||||
@@ -417,7 +442,7 @@ Allowed file types: PDF, Markdown, TXT, Office documents (DOC, DOCX, XLS, XLSX,
|
||||
|
||||
### Exports
|
||||
|
||||
Bulk export tools for reports and data extracts.
|
||||
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
|
||||
|
||||
---
|
||||
|
||||
@@ -506,130 +534,147 @@ python3 import_notes_from_csv.py input.csv --db /path/to/cve_database.db
|
||||
|
||||
## API Reference
|
||||
|
||||
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 |
|
||||
| POST | `/api/auth/cleanup-sessions` | Session | Delete expired sessions |
|
||||
| GET | `/api/auth/me` | Any | Get current user info (returns `group` field) |
|
||||
| POST | `/api/auth/cleanup-sessions` | Admin | Delete expired sessions |
|
||||
|
||||
### CVEs
|
||||
|
||||
| Method | Path | Role | Description |
|
||||
| Method | Path | Group | Description |
|
||||
|---|---|---|---|
|
||||
| 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 |
|
||||
| PATCH | `/api/ivanti/queue/:id` | editor+ | Update a queue item (mark complete, edit vendor/type) |
|
||||
| DELETE | `/api/ivanti/queue/:id` | editor+ | Delete a single queue item |
|
||||
| DELETE | `/api/ivanti/queue` | editor+ | Delete multiple queue items (body: `{ ids: [...] }`) |
|
||||
| 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 |
|
||||
| PUT | `/api/ivanti/todo-queue/:id` | Admin, Standard_User | Update a queue item (mark complete, edit vendor/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 | Role | Description |
|
||||
| Method | Path | Group | Description |
|
||||
|---|---|---|---|
|
||||
| POST | `/api/compliance/preview` | editor+ | Parse an xlsx upload and return diff + temp file path |
|
||||
| POST | `/api/compliance/commit` | editor+ | Commit a previewed upload to the database |
|
||||
| GET | `/api/compliance/uploads` | viewer+ | List all compliance upload records |
|
||||
| GET | `/api/compliance/summary` | viewer+ | Metric health summary; `?team=STEAM` |
|
||||
| GET | `/api/compliance/items` | viewer+ | Device list; `?team=STEAM&status=active` |
|
||||
| GET | `/api/compliance/items/:hostname` | viewer+ | Full detail for a device (metrics + notes) |
|
||||
| GET | `/api/compliance/notes/:hostname/:metricId` | viewer+ | Notes for a specific hostname/metric |
|
||||
| POST | `/api/compliance/notes` | editor+ | Add a note for a hostname/metric |
|
||||
| POST | `/api/compliance/preview` | Admin, Standard_User | Parse an xlsx upload and return diff + temp file path |
|
||||
| POST | `/api/compliance/commit` | Admin, Standard_User | Commit a previewed upload to the database |
|
||||
| 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 |
|
||||
|
||||
### Knowledge Base
|
||||
|
||||
| Method | Path | Role | Description |
|
||||
| Method | Path | Group | Description |
|
||||
|---|---|---|---|
|
||||
| POST | `/api/knowledge-base/upload` | editor+ | Upload a new knowledge base document |
|
||||
| GET | `/api/knowledge-base` | viewer+ | List all articles |
|
||||
| GET | `/api/knowledge-base/:id` | viewer+ | Get article metadata |
|
||||
| GET | `/api/knowledge-base/:id/content` | viewer+ | Get file content for inline display |
|
||||
| GET | `/api/knowledge-base/:id/download` | viewer+ | Download the file |
|
||||
| DELETE | `/api/knowledge-base/:id` | editor+ | Delete article and file |
|
||||
| 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 | Role | Description |
|
||||
| Method | Path | Group | Description |
|
||||
|---|---|---|---|
|
||||
| GET | `/api/archer-tickets` | viewer+ | List tickets; optional filters: `cve_id`, `vendor`, `status` |
|
||||
| POST | `/api/archer-tickets` | editor+ | Create a new Archer ticket |
|
||||
| PUT | `/api/archer-tickets/:id` | editor+ | Update an Archer ticket |
|
||||
| DELETE | `/api/archer-tickets/:id` | editor+ | Delete an Archer ticket |
|
||||
| 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 | Role | Description |
|
||||
| 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 |
|
||||
| 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 | Role | Description |
|
||||
| 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 |
|
||||
| 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 | Role | Description |
|
||||
| Method | Path | Group | Description |
|
||||
|---|---|---|---|
|
||||
| GET | `/api/vendors` | viewer+ | List all distinct vendor names |
|
||||
| GET | `/api/stats` | viewer+ | Dashboard statistics |
|
||||
| GET | `/api/vendors` | Any | List all distinct vendor names |
|
||||
| GET | `/api/stats` | Any | Dashboard statistics |
|
||||
|
||||
---
|
||||
|
||||
@@ -639,6 +684,7 @@ All endpoints are prefixed with `/api`. All endpoints except `/api/auth/login` a
|
||||
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
|
||||
@@ -649,7 +695,7 @@ cve-dashboard/
|
||||
│ │ ├── knowledge_base/ # Knowledge base documents
|
||||
│ │ └── temp/ # Temporary upload staging
|
||||
│ ├── routes/
|
||||
│ │ ├── auth.js # Login, logout, session check
|
||||
│ │ ├── auth.js # Login, logout, session check, rate limiting
|
||||
│ │ ├── users.js # User CRUD (admin)
|
||||
│ │ ├── auditLog.js # Audit log viewer (admin)
|
||||
│ │ ├── nvdLookup.js # NVD API proxy
|
||||
@@ -658,20 +704,13 @@ cve-dashboard/
|
||||
│ │ ├── 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 requireRole middleware
|
||||
│ │ └── auth.js # requireAuth and requireGroup middleware
|
||||
│ ├── helpers/
|
||||
│ │ ├── auditLog.js # logAudit helper (fire-and-forget)
|
||||
│ ├── migrations/
|
||||
│ │ ├── add_knowledge_base_table.js
|
||||
│ │ ├── add_archer_tickets_table.js
|
||||
│ │ ├── add_ivanti_sync_table.js
|
||||
│ │ ├── add_ivanti_findings_tables.js
|
||||
│ │ ├── add_ivanti_todo_queue_table.js # Ivanti Queue table
|
||||
│ │ ├── add_card_workflow_type.js # CARD workflow type support
|
||||
│ │ ├── add_todo_queue_ip_address.js # IP address column on queue items
|
||||
│ │ └── add_compliance_tables.js # AEO compliance tables
|
||||
│ │ └── auditLog.js # logAudit helper (fire-and-forget)
|
||||
│ ├── migrations/ # Sequential migration scripts (run manually with node)
|
||||
│ └── scripts/
|
||||
│ ├── parse_compliance_xlsx.py # Parses NTS_AEO xlsx compliance reports
|
||||
│ ├── import_notes_from_csv.py # Bulk-import finding notes from CSV
|
||||
@@ -682,24 +721,27 @@ cve-dashboard/
|
||||
├── App.js # Home dashboard — CVE list, filters, modals, calendar
|
||||
├── App.css # Global styles and CSS variables
|
||||
├── contexts/
|
||||
│ └── AuthContext.js # Auth state provider (login, logout, role helpers)
|
||||
│ └── AuthContext.js # Auth state provider (login, logout, group helpers)
|
||||
└── components/
|
||||
├── LoginForm.js # Login page
|
||||
├── NavDrawer.js # Side navigation drawer
|
||||
├── UserMenu.js # User dropdown in header
|
||||
├── 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 panel
|
||||
├── UserManagement.js # Admin user management panel (group assignment)
|
||||
├── AuditLog.js # Admin audit log viewer
|
||||
├── NvdSyncModal.js # Bulk NVD sync dialog
|
||||
├── KnowledgeBaseModal.js # Knowledge base upload/list modal
|
||||
├── KnowledgeBaseViewer.js # Inline document viewer
|
||||
├── KnowledgeBaseViewer.js # Inline document viewer (sandboxed iframe, sanitized markdown)
|
||||
└── pages/
|
||||
├── 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
|
||||
└── ExportsPage.js # Exports page (group-gated)
|
||||
```
|
||||
|
||||
---
|
||||
@@ -708,13 +750,13 @@ cve-dashboard/
|
||||
|
||||
### Core tables (created by `setup.js`)
|
||||
|
||||
**`cves`** — One row per CVE/vendor pair. `UNIQUE(cve_id, vendor)`.
|
||||
**`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)`.
|
||||
**`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 roles: `admin`, `editor`, `viewer`.
|
||||
**`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.
|
||||
|
||||
@@ -722,9 +764,11 @@ cve-dashboard/
|
||||
|
||||
### Feature tables (added by migrations)
|
||||
|
||||
**`knowledge_base`** — Document library entries with title, slug, category, description, and file metadata.
|
||||
**`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)`.
|
||||
**`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:
|
||||
|
||||
| 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,}$/`
|
||||
@@ -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
|
||||
|
||||
### Security headers
|
||||
|
||||
Applied to all responses:
|
||||
@@ -789,15 +858,11 @@ Applied to all responses:
|
||||
- `Referrer-Policy: strict-origin-when-cross-origin`
|
||||
- `Permissions-Policy: camera=(), microphone=(), geolocation=()`
|
||||
|
||||
### Session cookies
|
||||
|
||||
`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.
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
@@ -809,6 +874,11 @@ 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_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_user_groups.js
|
||||
node migrations/add_created_by_columns.js
|
||||
```
|
||||
|
||||
For deployments upgrading from an older schema, the following legacy migration scripts are also available in `backend/`:
|
||||
|
||||
Reference in New Issue
Block a user