- Feedback modal now supports up to 3 image attachments (PNG/JPG/GIF/WebP, 5MB
each) with thumbnail previews. Images are uploaded to GitLab project uploads
and embedded as markdown in the issue description.
- New webhook endpoint (POST /api/webhooks/gitlab) receives issue close events,
parses the submitter from the description, looks up their email, and sends a
Webex DM via the Patches O'Houlihan bot.
- New helper: backend/helpers/webexBot.js (fire-and-forget DM sender).
- Requires WEBEX_BOT_TOKEN and GITLAB_WEBHOOK_SECRET in backend/.env.
- Rewrite .gitlab-ci.yml with proper stages, blocking tests, staging
environment on dev box, and SSH-based production deploy to 71.85.90.6
- Add POST /api/health endpoint for pipeline verification
- Add POST /atlas/hosts/:hostId/refresh-cache for Atlas cache staleness
- AtlasSlideOutPanel: auto-resolve qualys_id from Atlas vulnerabilities,
prefer qualys_id over active_host_findings_id, retry on failure
- Add FeedbackModal component with bug report button in header and
feature request in UserMenu, creates GitLab issues via /api/feedback
- Fix all frontend test failures (ESM transforms, TextDecoder polyfill,
fast-check resolution, App.test.js boilerplate replacement)
- Fix root package.json test script to run jest
- Add deploy/ directory with staging systemd service and setup script
- Create ivanti_counts_history_by_bu table (bu_ownership, state, count per sync)
- Sync writes per-BU snapshot alongside global history on each sync
- Seed table with current counts for immediate first data point
- GET /counts/history accepts ?teams param — queries per-BU table when filtered
- IvantiCountsChart accepts teamsParam prop, re-fetches on scope change
- ReportingPage passes getActiveTeamsParam() to the chart
- Historical per-BU data accumulates from this point forward
- Global history (no filter) still uses the original aggregate table
- Replace 2.6MB JSON blob with individual rows in ivanti_findings table
- Batch upsert via INSERT ... ON CONFLICT in chunks of 100
- Sync stores both open AND closed findings as rows with state column
- Per-BU closed counts now possible via SQL GROUP BY
- GET /findings queries indexed table with optional ILIKE BU filter
- GET /counts returns per-BU open+closed via GROUP BY state
- Notes and overrides are columns on ivanti_findings (no separate tables)
- Removed: readState, readStateWithNotes, _findingsCache, initTables
- Preserved: extractFinding, archive detection, FP workflow counts, anomaly log
- Response shape unchanged — frontend works without modification
- All 16 route files now import pool from ../db directly
- Removed db parameter from all factory functions
- All callbacks replaced with async/await pool.query()
- All ? placeholders converted to $1, $2... numbered params
- datetime('now') → NOW(), INSERT OR IGNORE → ON CONFLICT DO NOTHING
- LIKE → ILIKE for case-insensitive searches
- Error detection: err.code === '23505' for unique violations
- server.js no longer passes pool/db/requireAuth to route factories
- Only ivantiFindings.js still receives pool (pending task 8 rewrite)
- Add bu_teams column to users table (migration + fresh schema)
- Create shared KNOWN_TEAMS constant and validateTeams helper
- Expose user teams in auth middleware, login, and /me responses
- Add bu_teams CRUD to user management routes with audit logging
- Make Ivanti FINDINGS_FILTERS configurable via IVANTI_BU_FILTER env var
- Add query-time team filtering to GET /findings and /findings/counts
- Update AuthContext with teams helpers and admin scope toggle
- Create AdminScopeToggle component (My Teams / All BUs)
- Scope ReportingPage findings fetch by user teams
- Scope CompliancePage team selector by user teams
- Scope ExportsPage findings exports by user teams
- Add BU teams multi-select to UserManagement create/edit forms
- Display team badges in user list table
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.
Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync
Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row
Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
- Add GET /api/cves/:cveId/tooltip backend endpoint with description truncation
- Create CveTooltip portal component with caching, severity badges, and viewport-aware positioning
- Integrate tooltip into ReportingPage with 300ms hover delay on CVE badge spans
Bugs fixed:
- knowledgeBase.js: logAudit calls converted from positional args to object signature
- archerTickets.js: targetType/targetId renamed to entityType/entityId
- server.js: single CVE delete now has cascade/compliance check for Standard_User
Unprotected endpoints secured:
- ivantiTodoQueue.js: POST/PUT/DELETE now require Admin or Standard_User
- ivantiFindings.js: PUT note and POST sync now require Admin or Standard_User
- compliance.js: POST notes now requires Admin or Standard_User
- ivantiWorkflows.js: POST sync now requires Admin or Standard_User
- auth.js: cleanup-sessions now requires Admin via requireAuth + requireGroup
Additional fixes:
- ExportsPage.js: canExport() guard blocks Read_Only users
- knowledgeBase.js: Standard_User delete checks created_by ownership
- Migration: added INSERT/UPDATE triggers to enforce valid user_group values
- Add migration script for ivanti_finding_archives and ivanti_archive_transitions tables
- Add archive detection logic (detectArchiveChanges, detectClosedFindings) in sync pipeline
- Add archive API router with list, stats, and history endpoints at /api/ivanti/archive
- Add ArchiveSummaryBar UI component with four state cards (ACTIVE, ARCHIVED, RETURNED, CLOSED)
- Integrate ArchiveSummaryBar into Ivanti findings page in App.js
- Register archive router in server.js
- Migration: compliance_uploads, compliance_items, compliance_notes tables
with indexes on (hostname, metric_id) identity key and team/status
- Python parser (parse_compliance_xlsx.py): reads NTS_AEO xlsx, extracts
non-compliant assets from all detail sheets, parses Summary sheet for
metric health data and overall scores, outputs JSON to stdout
- Route (/api/compliance): preview/commit upload flow with diff summary,
items endpoint grouped by hostname with seen_count tracking, metric
summary endpoint for health cards, notes endpoints keyed on
(hostname, metric_id) persisting across uploads
- server.js: register compliance router at /api/compliance
- .gitignore: exclude planning docs and xlsx source files
Adds ip_address column to ivanti_todo_queue so CARD entries carry the
host IP needed to locate the asset in CARD.
- Migration: ALTER TABLE ADD COLUMN ip_address TEXT (safe to re-run)
- Backend: accepts ip_address in POST body, stores up to 64 chars
- Frontend: captures finding.ipAddress when adding to queue; CARD items
in the queue panel show the IP in green instead of the CVE list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Popover now flips above the row when it would overflow the bottom of the
viewport, and clamps horizontally to stay within the window.
Adds CARD as a third workflow type (for out-of-team asset disposition in
CARD) alongside FP and Archer. CARD is styled in green (#10B981) across
the popover toggle and queue panel badge.
DB: new migration (add_card_workflow_type.js) recreates ivanti_todo_queue
with an updated CHECK constraint to allow 'CARD'; run manually on dev.
App-level validation in the route is updated to match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a persistent per-user staging queue so analysts can tag findings
during review and batch-process Ivanti workflows in one focused session.
Backend:
- New ivanti_todo_queue table (user-scoped, vendor, workflow_type, status)
- Table auto-created on server startup via idempotent CREATE IF NOT EXISTS
- New route /api/ivanti/todo-queue: GET, POST, PUT/:id, DELETE/:id,
DELETE/completed — all scoped to req.user.id
Frontend (ReportingPage):
- Fixed checkbox column on findings table; clicking opens an add-to-queue
popover (portal) with vendor input and FP/Archer toggle
- Already-queued rows show checked/disabled checkbox
- Queue slide-out panel (420px fixed, CSS transition) with items grouped
by vendor, per-item complete toggle + delete, Clear Completed footer
- Queue button in header with live pending-count badge
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the placeholder with a fully functional exports page.
Backend:
- Add GET /api/cves/compliance endpoint reading from cve_document_status view
Frontend (ExportsPage.js):
1. Ivanti Host Findings — 4 sub-exports:
- Full dump (all findings, all columns)
- Pending Action (no FP# and no EXC in notes)
- Overdue SLA (past due date or OVERDUE SLA status)
- By Business Unit (multi-sheet XLSX, one sheet per BU)
2. FP Workflow Summary — one row per unique FP# ticket ID with state,
finding count, affected hosts, BUs, and CVEs
3. CVE Database — status filter dropdown + CSV and XLSX format options
4. Archer Tickets — full EXC ticket list with linked CVEs and URLs
5. Document Compliance Report — per CVE/vendor doc coverage with
"missing only" toggle to generate a gap list
All exports are lazy (data fetched on click), per-button loading states,
global dismissable error banner, auto-fit column widths in XLSX outputs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New panel below Archer tickets showing workflow count and list
- Backend proxies platform4.risksense.com workflowBatch/search via x-api-key
- SQLite cache table (ivanti_sync_state) stores latest sync result
- Auto-syncs on server startup if >24h stale, then every 24h via setInterval
- POST /api/ivanti/workflows/sync for on-demand sync with spinner feedback
- GET /api/ivanti/workflows returns cached data instantly (no live API call)
- Displays id.value, name, currentState, type, createdOn per workflow
- Shows last-synced timestamp and error messages inline
- IVANTI_SKIP_TLS flag for Charter SSL proxy environments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements a comprehensive system for uploading and processing weekly
vulnerability reports that automatically splits multiple CVE IDs in a
single cell into separate rows for easier filtering and analysis.
Backend Changes:
- Add weekly_reports table with migration
- Create Excel processor helper using Python child_process
- Implement API routes for upload, list, download, delete
- Mount routes in server.js after multer initialization
- Move split_cve_report.py to backend/scripts/
Frontend Changes:
- Add WeeklyReportModal component with phase-based UI
- Add "Weekly Report" button next to NVD Sync
- Integrate modal into App.js with state management
- Display existing reports with current report indicator
- Download buttons for original and processed files
Features:
- Upload .xlsx files (editor/admin only)
- Automatic CVE ID splitting via Python script
- Store metadata in database + files on filesystem
- Auto-archive previous reports (mark one as current)
- Download both original and processed versions
- Audit logging for all operations
- Security: file validation, auth checks, path sanitization
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- New jira_tickets table (migration script included)
- CRUD API endpoints for tickets with validation and audit logging
- Dashboard section showing all open vendor tickets
- JIRA tickets section within CVE vendor cards
- Tickets linked to CVE + vendor with status tracking (Open/In Progress/Closed)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Changed CVEs table constraint from UNIQUE(cve_id) to UNIQUE(cve_id, vendor)
- Added vendor column to documents table for proper file organization
- Updated backend INSERT statements to include vendor field in both CVE and document creation
- Fixed document retrieval to filter by vendor
- Created corrected setup.js that includes multi-vendor support from initial setup
- Added migration scripts for existing databases
Resolves#1: Users can now add the same CVE-ID with multiple different vendors, each maintaining separate document storage organized as CVE-ID/Vendor/files