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)
|
- [Configuration](#configuration)
|
||||||
- [Running the Application](#running-the-application)
|
- [Running the Application](#running-the-application)
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- [Authentication and User Roles](#authentication-and-user-roles)
|
- [Authentication and User Groups](#authentication-and-user-groups)
|
||||||
- [Home — CVE Management](#home--cve-management)
|
- [Home — CVE Management](#home--cve-management)
|
||||||
- [Reporting — Host Findings](#reporting--host-findings)
|
- [Reporting — Host Findings](#reporting--host-findings)
|
||||||
- [Ivanti Queue](#ivanti-queue)
|
- [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
|
- **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
|
- Archer risk acceptance ticket tracking (EXC numbers) linked to CVE/vendor pairs
|
||||||
- A knowledge base for internal documentation and policies
|
- 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 |
|
| Layer | Technology |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Backend | Node.js, Express 5 |
|
| Backend | Node.js 18+, Express 5 |
|
||||||
| Database | SQLite3 |
|
| Database | SQLite3 |
|
||||||
| File uploads | Multer 2 |
|
| File uploads | Multer 2 |
|
||||||
| Auth | bcryptjs, cookie-based sessions |
|
| Auth | bcryptjs, cookie-based sessions, express-rate-limit |
|
||||||
| Frontend | React 19, lucide-react, xlsx |
|
| Frontend | React 19, lucide-react, xlsx, rehype-sanitize |
|
||||||
| Compliance xlsx parsing | Python 3, pandas, openpyxl |
|
| Compliance xlsx parsing | Python 3, pandas, openpyxl |
|
||||||
| Bulk notes import | Python 3 (stdlib only) |
|
| Bulk notes import | Python 3 (stdlib only) |
|
||||||
|
|
||||||
@@ -84,7 +84,6 @@ cd cve-dashboard
|
|||||||
### 2. Install backend dependencies
|
### 2. Install backend dependencies
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd backend
|
|
||||||
npm install
|
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.
|
> 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:
|
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
|
node setup.js
|
||||||
```
|
```
|
||||||
|
|
||||||
This creates `backend/cve_database.db` and a default admin account:
|
This creates `backend/cve_database.db` and generates a random admin password printed to stdout. **Save the password — it is only shown once.**
|
||||||
- Username: `admin`
|
|
||||||
- Password: `admin123`
|
|
||||||
|
|
||||||
**Change the admin password immediately after first login.**
|
### 7. Run database migrations
|
||||||
|
|
||||||
### 6. Run database migrations
|
|
||||||
|
|
||||||
Apply all feature migrations in order:
|
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_card_workflow_type.js
|
||||||
node migrations/add_todo_queue_ip_address.js
|
node migrations/add_todo_queue_ip_address.js
|
||||||
node migrations/add_compliance_tables.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
|
```bash
|
||||||
cd frontend
|
cd frontend
|
||||||
@@ -159,7 +172,7 @@ The application is configured via `.env` files. These files are gitignored and m
|
|||||||
PORT=3001
|
PORT=3001
|
||||||
API_HOST=localhost
|
API_HOST=localhost
|
||||||
CORS_ORIGINS=http://YOUR_IP:3000
|
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
|
NODE_ENV=production
|
||||||
|
|
||||||
# Optional: NVD API key for higher rate limits (50 req/30s vs 5 req/30s)
|
# 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
|
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`
|
### Frontend: `frontend/.env`
|
||||||
|
|
||||||
```env
|
```env
|
||||||
@@ -225,17 +240,26 @@ npm start
|
|||||||
|
|
||||||
## Features
|
## 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 |
|
| `Admin` | Full CRUD on all resources, user management, audit log access, export all data, delete any resource regardless of ownership |
|
||||||
| `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 |
|
| `Standard_User` | View all data, create and edit resources, delete own resources (with state and compliance restrictions), basic export (CSV/XLSX) |
|
||||||
| `admin` | All editor permissions plus: delete documents, delete reports, manage users, view audit logs |
|
| `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)
|
- Color-coded severity badges: Critical (red), High (amber), Medium (sky blue), Low (green)
|
||||||
- Paginated list view
|
- 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
|
- Add a new CVE entry — NVD auto-fill populates description, severity, and published date automatically
|
||||||
- Edit any field on an existing CVE entry
|
- Edit any field on an existing CVE entry
|
||||||
- Update status for all vendor rows matching a CVE ID in one click
|
- 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
|
- The same CVE ID can be tracked across multiple vendors independently
|
||||||
|
|
||||||
**Document Management**
|
**Document Management**
|
||||||
@@ -265,7 +289,7 @@ The home page is the primary CVE research and tracking tool.
|
|||||||
|
|
||||||
**NVD Integration**
|
**NVD Integration**
|
||||||
- Auto-fill CVE description, severity, and published date from the NIST NVD API 2.0 when adding a new CVE
|
- 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
|
- 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
|
- 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
|
#### 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)
|
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
|
2. Fetches the closed finding count separately
|
||||||
3. Sweeps closed findings to capture FP workflow states (including Approved FPs now closed)
|
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 |
|
| Last Found | Last detection date from Ivanti |
|
||||||
| Notes | Free-form notes — inline editable, persists across syncs |
|
| 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.
|
**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`.
|
**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
|
### 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:
|
**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 **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
|
#### 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
|
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
|
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
|
- 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
|
- **Resolved Metrics** — previously failing metrics now back in compliance
|
||||||
- **History** — how many times the device has appeared on the report and since when
|
- **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.
|
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.
|
A document library for internal reference material — policies, runbooks, vendor advisories, and process guides.
|
||||||
|
|
||||||
- Upload documents with a title, optional description, and category
|
- Upload documents with a title, optional description, and category (Admin/Standard_User)
|
||||||
- View documents inline in the browser (PDFs render in an iframe; Markdown files render as HTML)
|
- View documents inline in the browser (PDFs render in a sandboxed iframe; Markdown files render as sanitized HTML)
|
||||||
- Download any document
|
- Download any document
|
||||||
- Filter and browse by category
|
- 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).
|
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
|
### 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
|
- Optional Archer URL field for deep-linking to the Archer record
|
||||||
- Filter tickets by CVE ID, vendor, or status
|
- 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
|
- 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)
|
### User Management (Admin)
|
||||||
|
|
||||||
- Create users with a role assignment
|
- Create users with a group assignment (Admin, Standard_User, Leadership, Read_Only)
|
||||||
- Change username, email, password, role, or active status
|
- 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
|
- Deactivating a user immediately invalidates all their active sessions
|
||||||
- Admins cannot demote themselves or deactivate their own account
|
- 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
|
## 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
|
### 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 |
|
| POST | `/api/auth/logout` | Public | Invalidate session |
|
||||||
| GET | `/api/auth/me` | Session | Get current user info |
|
| GET | `/api/auth/me` | Any | Get current user info (returns `group` field) |
|
||||||
| POST | `/api/auth/cleanup-sessions` | Session | Delete expired sessions |
|
| POST | `/api/auth/cleanup-sessions` | Admin | Delete expired sessions |
|
||||||
|
|
||||||
### CVEs
|
### CVEs
|
||||||
|
|
||||||
| Method | Path | Role | Description |
|
| Method | Path | Group | Description |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| GET | `/api/cves` | viewer+ | List CVEs; query params: `search`, `vendor`, `severity`, `status` |
|
| GET | `/api/cves` | Any | List CVEs; query params: `search`, `vendor`, `severity`, `status` |
|
||||||
| POST | `/api/cves` | editor+ | Create a new CVE entry |
|
| POST | `/api/cves` | Admin, Standard_User | Create a new CVE entry |
|
||||||
| PUT | `/api/cves/:id` | editor+ | Update a CVE entry by row ID |
|
| PUT | `/api/cves/:id` | Admin, Standard_User | Update a CVE entry by row ID |
|
||||||
| PATCH | `/api/cves/:cveId/status` | editor+ | Update status for all vendor rows matching a CVE ID |
|
| PATCH | `/api/cves/:cveId/status` | Admin, Standard_User | Update status for all vendor rows matching a CVE ID |
|
||||||
| DELETE | `/api/cves/:id` | editor+ | Delete a single CVE vendor entry |
|
| 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` | editor+ | Delete all vendor entries for a CVE ID |
|
| 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` | viewer+ | Quick check: existence and status of a CVE |
|
| GET | `/api/cves/check/:cveId` | Any | 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/distinct-ids` | Any | 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/:cveId/vendors` | Any | All vendor entries for a specific CVE ID |
|
||||||
|
| GET | `/api/cves/compliance` | Any | Document compliance status view |
|
||||||
|
|
||||||
### Documents
|
### Documents
|
||||||
|
|
||||||
| Method | Path | Role | Description |
|
| Method | Path | Group | Description |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| GET | `/api/cves/:cveId/documents` | viewer+ | List documents for a CVE; optional `?vendor=` filter |
|
| GET | `/api/cves/:cveId/documents` | Any | List documents for a CVE; optional `?vendor=` filter |
|
||||||
| POST | `/api/cves/:cveId/documents` | editor+ | Upload a document for a CVE/vendor pair |
|
| 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 |
|
| DELETE | `/api/documents/:id` | Admin | Delete a document and its file from disk |
|
||||||
|
|
||||||
### NVD
|
### 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 |
|
| GET | `/api/nvd/lookup/:cveId` | Any | Look up a single CVE in the NVD 2.0 API |
|
||||||
| POST | `/api/cves/nvd-sync` | editor+ | Bulk update CVE metadata from NVD |
|
| 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
|
### 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 |
|
| GET | `/api/ivanti/findings` | Any | Get cached findings with notes and overrides merged in |
|
||||||
| POST | `/api/ivanti/findings/sync` | viewer+ | Trigger an immediate findings sync from Ivanti |
|
| POST | `/api/ivanti/findings/sync` | Admin, Standard_User | Trigger an immediate findings sync from Ivanti |
|
||||||
| GET | `/api/ivanti/findings/counts` | viewer+ | Open vs closed finding totals |
|
| GET | `/api/ivanti/findings/counts` | Any | Open vs closed finding totals |
|
||||||
| GET | `/api/ivanti/findings/fp-workflow-counts` | viewer+ | FP workflow state breakdown |
|
| GET | `/api/ivanti/findings/fp-workflow-counts` | Any | 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/override` | Admin, Standard_User | 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) |
|
| PUT | `/api/ivanti/findings/:findingId/note` | Admin, Standard_User | Save or update a finding note (max 255 chars) |
|
||||||
|
|
||||||
### Ivanti — Workflows
|
### Ivanti — Workflows
|
||||||
|
|
||||||
| Method | Path | Role | Description |
|
| Method | Path | Group | Description |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| GET | `/api/ivanti/workflows` | viewer+ | Get cached workflow data |
|
| GET | `/api/ivanti/workflows` | Any | Get cached workflow data |
|
||||||
| POST | `/api/ivanti/workflows/sync` | viewer+ | Trigger an immediate workflow sync |
|
| 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 |
|
| GET | `/api/ivanti/todo-queue` | Any | Get all queue items for the current user |
|
||||||
| POST | `/api/ivanti/queue` | editor+ | Add a finding to the queue |
|
| POST | `/api/ivanti/todo-queue` | Admin, Standard_User | Add a finding to the queue |
|
||||||
| PATCH | `/api/ivanti/queue/:id` | editor+ | Update a queue item (mark complete, edit vendor/type) |
|
| PUT | `/api/ivanti/todo-queue/:id` | Admin, Standard_User | Update a queue item (mark complete, edit vendor/type) |
|
||||||
| DELETE | `/api/ivanti/queue/:id` | editor+ | Delete a single queue item |
|
| DELETE | `/api/ivanti/todo-queue/:id` | Admin, Standard_User | Delete a single queue item |
|
||||||
| DELETE | `/api/ivanti/queue` | editor+ | Delete multiple queue items (body: `{ ids: [...] }`) |
|
| 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
|
### 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/preview` | Admin, Standard_User | Parse an xlsx upload and return diff + temp file path |
|
||||||
| POST | `/api/compliance/commit` | editor+ | Commit a previewed upload to the database |
|
| POST | `/api/compliance/commit` | Admin, Standard_User | Commit a previewed upload to the database |
|
||||||
| GET | `/api/compliance/uploads` | viewer+ | List all compliance upload records |
|
| GET | `/api/compliance/uploads` | Any | List all compliance upload records |
|
||||||
| GET | `/api/compliance/summary` | viewer+ | Metric health summary; `?team=STEAM` |
|
| GET | `/api/compliance/summary` | Any | Metric health summary; `?team=STEAM` |
|
||||||
| GET | `/api/compliance/items` | viewer+ | Device list; `?team=STEAM&status=active` |
|
| GET | `/api/compliance/items` | Any | Device list; `?team=STEAM&status=active` |
|
||||||
| GET | `/api/compliance/items/:hostname` | viewer+ | Full detail for a device (metrics + notes) |
|
| GET | `/api/compliance/items/:hostname` | Any | Full detail for a device (metrics + notes) |
|
||||||
| GET | `/api/compliance/notes/:hostname/:metricId` | viewer+ | Notes for a specific hostname/metric |
|
| GET | `/api/compliance/notes/:hostname/:metricId` | Any | Notes for a specific hostname/metric |
|
||||||
| POST | `/api/compliance/notes` | editor+ | Add a note for a hostname/metric |
|
| POST | `/api/compliance/notes` | Admin, Standard_User | Add a note for a hostname/metric |
|
||||||
|
|
||||||
### Knowledge Base
|
### Knowledge Base
|
||||||
|
|
||||||
| Method | Path | Role | Description |
|
| Method | Path | Group | Description |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| POST | `/api/knowledge-base/upload` | editor+ | Upload a new knowledge base document |
|
| POST | `/api/knowledge-base/upload` | Admin, Standard_User | Upload a new knowledge base document |
|
||||||
| GET | `/api/knowledge-base` | viewer+ | List all articles |
|
| GET | `/api/knowledge-base` | Any | List all articles |
|
||||||
| GET | `/api/knowledge-base/:id` | viewer+ | Get article metadata |
|
| GET | `/api/knowledge-base/:id` | Any | Get article metadata |
|
||||||
| GET | `/api/knowledge-base/:id/content` | viewer+ | Get file content for inline display |
|
| GET | `/api/knowledge-base/:id/content` | Any | Get file content for inline display |
|
||||||
| GET | `/api/knowledge-base/:id/download` | viewer+ | Download the file |
|
| GET | `/api/knowledge-base/:id/download` | Any | Download the file |
|
||||||
| DELETE | `/api/knowledge-base/:id` | editor+ | Delete article and file |
|
| DELETE | `/api/knowledge-base/:id` | Admin, Standard_User | Delete article and file (Standard_User: own articles only) |
|
||||||
|
|
||||||
### Archer Tickets
|
### Archer Tickets
|
||||||
|
|
||||||
| Method | Path | Role | Description |
|
| Method | Path | Group | Description |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| GET | `/api/archer-tickets` | viewer+ | List tickets; optional filters: `cve_id`, `vendor`, `status` |
|
| GET | `/api/archer-tickets` | Any | List tickets; optional filters: `cve_id`, `vendor`, `status` |
|
||||||
| POST | `/api/archer-tickets` | editor+ | Create a new Archer ticket |
|
| GET | `/api/archer-tickets/status-trend` | Any | Ticket counts by date and status for pipeline chart |
|
||||||
| PUT | `/api/archer-tickets/:id` | editor+ | Update an Archer ticket |
|
| POST | `/api/archer-tickets` | Admin, Standard_User | Create a new Archer ticket |
|
||||||
| DELETE | `/api/archer-tickets/:id` | editor+ | Delete an 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)
|
### Users (Admin only)
|
||||||
|
|
||||||
| Method | Path | Role | Description |
|
| Method | Path | Group | Description |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| GET | `/api/users` | admin | List all users |
|
| GET | `/api/users` | Admin | List all users |
|
||||||
| GET | `/api/users/:id` | admin | Get a single user |
|
| GET | `/api/users/:id` | Admin | Get a single user |
|
||||||
| POST | `/api/users` | admin | Create a user |
|
| POST | `/api/users` | Admin | Create a user |
|
||||||
| PATCH | `/api/users/:id` | admin | Update a user |
|
| PATCH | `/api/users/:id` | Admin | Update a user |
|
||||||
| DELETE | `/api/users/:id` | admin | Delete a user |
|
| DELETE | `/api/users/:id` | Admin | Delete a user |
|
||||||
|
|
||||||
### Audit Logs (Admin only)
|
### 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` | 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/actions` | Admin | List distinct action types for filter dropdowns |
|
||||||
|
|
||||||
### Utility
|
### Utility
|
||||||
|
|
||||||
| Method | Path | Role | Description |
|
| Method | Path | Group | Description |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| GET | `/api/vendors` | viewer+ | List all distinct vendor names |
|
| GET | `/api/vendors` | Any | List all distinct vendor names |
|
||||||
| GET | `/api/stats` | viewer+ | Dashboard statistics |
|
| 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/
|
cve-dashboard/
|
||||||
├── start-servers.sh # Start backend + frontend in background
|
├── start-servers.sh # Start backend + frontend in background
|
||||||
├── stop-servers.sh # Stop all servers
|
├── stop-servers.sh # Stop all servers
|
||||||
|
├── package.json # Root package.json (backend dependencies)
|
||||||
│
|
│
|
||||||
├── backend/
|
├── backend/
|
||||||
│ ├── server.js # Express app — routes, middleware, security headers
|
│ ├── server.js # Express app — routes, middleware, security headers
|
||||||
@@ -649,7 +695,7 @@ cve-dashboard/
|
|||||||
│ │ ├── knowledge_base/ # Knowledge base documents
|
│ │ ├── knowledge_base/ # Knowledge base documents
|
||||||
│ │ └── temp/ # Temporary upload staging
|
│ │ └── temp/ # Temporary upload staging
|
||||||
│ ├── routes/
|
│ ├── routes/
|
||||||
│ │ ├── auth.js # Login, logout, session check
|
│ │ ├── auth.js # Login, logout, session check, rate limiting
|
||||||
│ │ ├── users.js # User CRUD (admin)
|
│ │ ├── users.js # User CRUD (admin)
|
||||||
│ │ ├── auditLog.js # Audit log viewer (admin)
|
│ │ ├── auditLog.js # Audit log viewer (admin)
|
||||||
│ │ ├── nvdLookup.js # NVD API proxy
|
│ │ ├── nvdLookup.js # NVD API proxy
|
||||||
@@ -658,20 +704,13 @@ cve-dashboard/
|
|||||||
│ │ ├── ivantiWorkflows.js # Ivanti workflow batch sync and cache
|
│ │ ├── ivantiWorkflows.js # Ivanti workflow batch sync and cache
|
||||||
│ │ ├── ivantiFindings.js # Ivanti host findings sync, notes, overrides, FP counts
|
│ │ ├── ivantiFindings.js # Ivanti host findings sync, notes, overrides, FP counts
|
||||||
│ │ ├── ivantiTodoQueue.js # Ivanti Queue — personal FP/Archer/CARD staging list
|
│ │ ├── 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
|
│ │ └── compliance.js # AEO compliance upload, diff, device tracking, notes
|
||||||
│ ├── middleware/
|
│ ├── middleware/
|
||||||
│ │ └── auth.js # requireAuth and requireRole middleware
|
│ │ └── auth.js # requireAuth and requireGroup middleware
|
||||||
│ ├── helpers/
|
│ ├── helpers/
|
||||||
│ │ ├── auditLog.js # logAudit helper (fire-and-forget)
|
│ │ └── auditLog.js # logAudit helper (fire-and-forget)
|
||||||
│ ├── migrations/
|
│ ├── migrations/ # Sequential migration scripts (run manually with node)
|
||||||
│ │ ├── 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
|
|
||||||
│ └── scripts/
|
│ └── scripts/
|
||||||
│ ├── parse_compliance_xlsx.py # Parses NTS_AEO xlsx compliance reports
|
│ ├── parse_compliance_xlsx.py # Parses NTS_AEO xlsx compliance reports
|
||||||
│ ├── import_notes_from_csv.py # Bulk-import finding notes from CSV
|
│ ├── 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.js # Home dashboard — CVE list, filters, modals, calendar
|
||||||
├── App.css # Global styles and CSS variables
|
├── App.css # Global styles and CSS variables
|
||||||
├── contexts/
|
├── contexts/
|
||||||
│ └── AuthContext.js # Auth state provider (login, logout, role helpers)
|
│ └── AuthContext.js # Auth state provider (login, logout, group helpers)
|
||||||
└── components/
|
└── components/
|
||||||
├── LoginForm.js # Login page
|
├── LoginForm.js # Login page
|
||||||
├── NavDrawer.js # Side navigation drawer
|
├── NavDrawer.js # Side navigation drawer (Admin Panel link for Admin group)
|
||||||
├── UserMenu.js # User dropdown in header
|
├── UserMenu.js # User dropdown in header (shows group badge)
|
||||||
├── CalendarWidget.js # Due-date calendar with Ivanti finding indicators
|
├── 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
|
├── AuditLog.js # Admin audit log viewer
|
||||||
├── NvdSyncModal.js # Bulk NVD sync dialog
|
├── NvdSyncModal.js # Bulk NVD sync dialog
|
||||||
├── KnowledgeBaseModal.js # Knowledge base upload/list modal
|
├── KnowledgeBaseModal.js # Knowledge base upload/list modal
|
||||||
├── KnowledgeBaseViewer.js # Inline document viewer
|
├── KnowledgeBaseViewer.js # Inline document viewer (sandboxed iframe, sanitized markdown)
|
||||||
└── pages/
|
└── pages/
|
||||||
├── ReportingPage.js # Host findings: charts, table, queue, export
|
├── ReportingPage.js # Host findings: charts, table, queue, export
|
||||||
├── CompliancePage.js # AEO compliance: metric cards, device table
|
├── CompliancePage.js # AEO compliance: metric cards, device table
|
||||||
├── ComplianceUploadModal.js # xlsx upload with diff preview
|
├── ComplianceUploadModal.js # xlsx upload with diff preview
|
||||||
├── ComplianceDetailPanel.js # Per-device metrics, history, notes
|
├── 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
|
├── 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`)
|
### 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.
|
**`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.
|
**`sessions`** — Active sessions with 24-hour expiry.
|
||||||
|
|
||||||
@@ -722,9 +764,11 @@ cve-dashboard/
|
|||||||
|
|
||||||
### Feature tables (added by migrations)
|
### 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.
|
**`ivanti_sync_state`** — Single-row cache for Ivanti workflow batch data.
|
||||||
|
|
||||||
@@ -752,18 +796,47 @@ cve-dashboard/
|
|||||||
|
|
||||||
## Security Model
|
## 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
|
### File upload security
|
||||||
|
|
||||||
- Extension allowlist enforced by Multer; executables (`.exe`, `.js`, `.sh`, `.py`, `.bat`, etc.) are blocked
|
- Extension allowlist enforced by Multer; executables (`.exe`, `.js`, `.sh`, `.py`, `.bat`, etc.) are blocked
|
||||||
- MIME type prefix validation in addition to extension checking
|
- MIME type prefix validation in addition to extension checking
|
||||||
- 10 MB per-file size limit
|
- 10 MB per-file size limit
|
||||||
- Filenames are sanitized: path separators, `..` sequences, null bytes, and non-alphanumeric characters are removed
|
- 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
|
### Path traversal prevention
|
||||||
|
|
||||||
- `sanitizePathSegment()` strips `/`, `\`, `..`, and null bytes from any value used in `path.join()`
|
- `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
|
- `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
|
### Input validation
|
||||||
|
|
||||||
- CVE ID must match `/^CVE-\d{4}-\d{4,}$/`
|
- 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`
|
- Status must be one of: `Open`, `Addressed`, `In Progress`, `Resolved`
|
||||||
- Archer EXC numbers must match `/^EXC-\d+$/`
|
- Archer EXC numbers must match `/^EXC-\d+$/`
|
||||||
- Finding override field must be one of: `hostName`, `dns`
|
- 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
|
- 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
|
### Security headers
|
||||||
|
|
||||||
Applied to all responses:
|
Applied to all responses:
|
||||||
@@ -789,15 +858,11 @@ Applied to all responses:
|
|||||||
- `Referrer-Policy: strict-origin-when-cross-origin`
|
- `Referrer-Policy: strict-origin-when-cross-origin`
|
||||||
- `Permissions-Policy: camera=(), microphone=(), geolocation=()`
|
- `Permissions-Policy: camera=(), microphone=(), geolocation=()`
|
||||||
|
|
||||||
### Session cookies
|
|
||||||
|
|
||||||
`httpOnly: true`, `sameSite: lax`, `secure: true` in production (`NODE_ENV=production`).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Migrations
|
## 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
|
```bash
|
||||||
cd backend
|
cd backend
|
||||||
@@ -809,6 +874,11 @@ node migrations/add_ivanti_todo_queue_table.js
|
|||||||
node migrations/add_card_workflow_type.js
|
node migrations/add_card_workflow_type.js
|
||||||
node migrations/add_todo_queue_ip_address.js
|
node migrations/add_todo_queue_ip_address.js
|
||||||
node migrations/add_compliance_tables.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/`:
|
For deployments upgrading from an older schema, the following legacy migration scripts are also available in `backend/`:
|
||||||
|
|||||||
@@ -16,7 +16,20 @@ const loginLimiter = rateLimit({
|
|||||||
function createAuthRouter(db, logAudit) {
|
function createAuthRouter(db, logAudit) {
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// Login
|
/**
|
||||||
|
* POST /api/auth/login
|
||||||
|
*
|
||||||
|
* Authenticates a user with username and password, creates a session,
|
||||||
|
* and sets an httpOnly session cookie. Rate-limited to 20 attempts per 15 minutes.
|
||||||
|
*
|
||||||
|
* @body {string} username - The user's login username
|
||||||
|
* @body {string} password - The user's password
|
||||||
|
* @returns {object} 200 - { message: 'Login successful', user: { id, username, email, group } }
|
||||||
|
* @returns {object} 400 - { error: 'Username and password are required' }
|
||||||
|
* @returns {object} 401 - { error: 'Invalid username or password' } | { error: 'Account is disabled' }
|
||||||
|
* @returns {object} 429 - { error: 'Too many login attempts. Please try again in 15 minutes.' }
|
||||||
|
* @returns {object} 500 - { error: 'Login failed' }
|
||||||
|
*/
|
||||||
router.post('/login', loginLimiter, async (req, res) => {
|
router.post('/login', loginLimiter, async (req, res) => {
|
||||||
const { username, password } = req.body;
|
const { username, password } = req.body;
|
||||||
|
|
||||||
@@ -139,7 +152,14 @@ function createAuthRouter(db, logAudit) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Logout
|
/**
|
||||||
|
* POST /api/auth/logout
|
||||||
|
*
|
||||||
|
* Ends the current user session by deleting it from the database
|
||||||
|
* and clearing the session cookie.
|
||||||
|
*
|
||||||
|
* @returns {object} 200 - { message: 'Logged out successfully' }
|
||||||
|
*/
|
||||||
router.post('/logout', async (req, res) => {
|
router.post('/logout', async (req, res) => {
|
||||||
const sessionId = req.cookies?.session_id;
|
const sessionId = req.cookies?.session_id;
|
||||||
|
|
||||||
@@ -182,7 +202,16 @@ function createAuthRouter(db, logAudit) {
|
|||||||
res.json({ message: 'Logged out successfully' });
|
res.json({ message: 'Logged out successfully' });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get current user
|
/**
|
||||||
|
* GET /api/auth/me
|
||||||
|
*
|
||||||
|
* Returns the currently authenticated user based on the session cookie.
|
||||||
|
* Clears the cookie and returns 401 if the session is expired or the account is disabled.
|
||||||
|
*
|
||||||
|
* @returns {object} 200 - { user: { id, username, email, group } }
|
||||||
|
* @returns {object} 401 - { error: 'Not authenticated' } | { error: 'Session expired' } | { error: 'Account is disabled' }
|
||||||
|
* @returns {object} 500 - { error: 'Failed to get user' }
|
||||||
|
*/
|
||||||
router.get('/me', async (req, res) => {
|
router.get('/me', async (req, res) => {
|
||||||
const sessionId = req.cookies?.session_id;
|
const sessionId = req.cookies?.session_id;
|
||||||
|
|
||||||
@@ -229,7 +258,16 @@ function createAuthRouter(db, logAudit) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clean up expired sessions (admin only)
|
/**
|
||||||
|
* POST /api/auth/cleanup-sessions
|
||||||
|
*
|
||||||
|
* Deletes all expired sessions from the database. Requires Admin group.
|
||||||
|
*
|
||||||
|
* @returns {object} 200 - { message: 'Expired sessions cleaned up' }
|
||||||
|
* @returns {object} 401 - { error: 'Authentication required' }
|
||||||
|
* @returns {object} 403 - { error: 'Insufficient permissions', required: ['Admin'], current: '...' }
|
||||||
|
* @returns {object} 500 - { error: 'Cleanup failed' }
|
||||||
|
*/
|
||||||
router.post('/cleanup-sessions', requireAuth(db), requireGroup('Admin'), async (req, res) => {
|
router.post('/cleanup-sessions', requireAuth(db), requireGroup('Admin'), async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user