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
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
Adds a four-state lifecycle tracker (ACTIVE → ARCHIVED → RETURNED → CLOSED)
to detect and monitor findings that disappear from Ivanti sync results due to
severity score drift rather than actual remediation.
- Archive detection runs automatically after each sync, comparing previous
and current finding sets to identify disappearances and reappearances
- Full transition history stored in ivanti_finding_archives and
ivanti_archive_transitions tables with timestamps and severity scores
- Three new API endpoints: /api/ivanti/archive, /api/ivanti/archive/stats,
/api/ivanti/archive/:findingId/history
- Archive Summary Bar UI on the home page shows counts for each state
(Active, Archived, Returned, Closed) with click-through finding lists
- Two new migrations: add_finding_archive_tables, add_archer_tickets_timestamps
- Mermaid diagram support added to Knowledge Base viewer
1. ACTIVE state never populated — stats endpoint now computes ACTIVE from live findings cache count instead of querying archive table
2. CHECK constraint mismatch — migration now uses 3-state constraint (ARCHIVED, RETURNED, CLOSED) matching runtime initArchiveTables()
3. Archive filter click non-functional — handleArchiveStateClick now fetches and renders filtered archive list below summary bar
4. Hook glob pattern mismatch — changed **/migrate*.js to **/migrations/*.js so hook fires for actual migration filenames
5. Stale stats after sync — ArchiveSummaryBar polls every 60s and refreshes immediately after workflow sync via refreshKey prop
- 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
Installs mermaid v11 and adds a custom ReactMarkdown code renderer
that intercepts fenced mermaid blocks and renders them as SVG diagrams
using the dark theme. SVGs are made responsive (width: 100%).
Non-mermaid code blocks are unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Production instances where the table was created before these columns
were added to the schema will see 500 errors on all /api/archer-tickets
endpoints. This migration safely checks PRAGMA table_info before each
ALTER TABLE so it is idempotent and safe to run multiple times.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Merges feature/compliance-time-charts into master.
Changes included:
- Compliance page: 6 Recharts trend charts (active totals, deltas, per-team,
MTTR, recurring items, Archer pipeline)
- Ivanti findings trend chart on Vulnerability Triage page: open/closed
counts history stored on every sync, aggregated to end-of-day snapshots
- Rename 'Reporting' page to 'Vulnerability Triage' throughout (nav, routes,
docs, all cross-page navigation references)
- Knowledge Base page: full article library with category filter, search,
inline viewer, upload/delete for editor+ roles
- Remove Knowledge Base sidebar panel from home page (now lives on KB page);
home layout adjusts to 2-column (9+3)
- Add ivanti_counts_history migration script for documentation consistency
- Update security-posture-workflow-diagrams.md and team-training-agenda.md
to reflect Vulnerability Triage page name
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The dedicated Knowledge Base page now provides the full library
experience. Remove the KB sidebar panel, viewer inline embed,
upload modal, and all supporting state/functions from App.js.
Home page layout adjusts from 3-column to 2-column (9+3 grid):
main CVE content expands to col-span-9, right panel unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the 'coming soon' placeholder with a full library UI.
No backend changes needed — all existing endpoints and components
(KnowledgeBaseViewer, KnowledgeBaseModal) are reused.
Features:
- Article card grid (responsive auto-fill, min 240px per card)
- Category filter tabs (Procedure, Guide, Policy, Reference, General)
with live article counts; tabs only shown for populated categories
- Search bar — filters by title and description, client-side
- Inline viewer — clicking a card opens KnowledgeBaseViewer below
the grid; clicking again or pressing the close button collapses it
- Upload modal (editor/admin only) refreshes the grid on success
- Delete button on each card (editor/admin only) with confirmation
- Graceful empty states for no articles and no search results
- Loading and error states with retry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Standalone migration script for consistency with other files in
backend/migrations/. The table is also created automatically at server
startup via CREATE TABLE IF NOT EXISTS in initTables() so no manual
step is required on the dev server — just restart the backend after
pulling.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Updated all human-readable references in documentation to reflect the
page rename. File path citations in security-audit-2026-04-01.md
(ReportingPage.js:51) are left unchanged as the file itself was not
renamed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
One reference to the old page ID was missed in the previous rename commit.
The Archer ticket EXC filter button in App.js was still navigating to
'reporting', which would silently fail to navigate. Updated to 'triage'.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add time-based open/closed tracking for Ivanti findings (Tier 2 from
the reporting recommendations doc) and rename the Reporting page to
Vulnerability Triage to better reflect its purpose.
Backend — ivantiFindings.js:
- Create ivanti_counts_history table (appended on every sync, never
overwritten — Option B from design discussion)
- INSERT snapshot after each successful syncClosedCount() call
- GET /api/ivanti/findings/counts/history endpoint — returns last
snapshot per calendar day using ROW_NUMBER window function, so
multiple daily syncs collapse to the end-of-day value
Frontend:
- New IvantiCountsChart component: collapsible dual-line chart
(open vs closed) with dark tooltip, delta label showing change
since previous day, and graceful no-data states
- Chart placed between the donut metrics panel and the findings table
on the Vulnerability Triage page
- Renamed page: 'reporting' → 'triage' (page ID, nav label, component
export, all cross-file references)
- ComplianceDetailPanel "View in Reporting" link updated to "View in
Triage" and navigates to the correct page ID
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 6 Recharts charts in a collapsible Historical Trends panel on the
Compliance page, covering all Tier-1 recommendations from the reporting
design doc.
Backend — 5 new API endpoints:
- GET /api/compliance/trends — active totals + per-team counts per upload
- GET /api/compliance/mttr — mean days to resolution per team
- GET /api/compliance/top-recurring — most persistent active findings by seen_count
- GET /api/compliance/category-trend — category breakdown per upload (future use)
- GET /api/archer-tickets/status-trend — ticket pipeline by creation date + status
Frontend — new ComplianceChartsPanel component:
- Active Findings Over Time (multi-line: total + per-team dashed)
- Change per Report Cycle (stacked bar: new/recurring + resolved)
- Team Compliance Health (multi-line per team)
- Mean Time to Resolution (horizontal bar per team)
- Most Persistent Findings (horizontal bar top-10 by seen_count)
- Archer Exception Pipeline (stacked bar by date + status)
All charts degrade gracefully to a no-data placeholder until uploads
accumulate. Panel is collapsible to stay out of the way when not needed.
Adds recharts dependency to frontend.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dev server uses apt-managed python3-pandas and python3-openpyxl.
Production fix is the same. Updates README install step and rewrites
python-venv-setup.md to reflect the real setup with venv as fallback.
Modern Debian/Ubuntu enforces PEP 668 which blocks system-wide pip
installs. The backend now reads PYTHON_BIN from the environment
(defaulting to 'python3') so each server can point to a venv.
Updates README with venv setup instructions.
Weekly report feature was removed previously. Cleans up all remaining
references from README, architecture diagram, and deletes
WEEKLY_REPORT_FEATURE.md entirely.
In ComplianceDetailPanel, active metrics with a metric_id starting with
'2.3' and an Ivanti_Vulnerability_ID in extra_json now surface the ID
prominently alongside a 'View in Reporting →' button. Clicking navigates
directly to the Reporting page. onNavigate prop threaded through
App → CompliancePage → ComplianceDetailPanel → MetricRow.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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
Comprehensive team-facing process documentation covering the full host
finding review workflow, vulnerability designations, Ivanti queue usage,
workflow status colour codes, and quick reference tables.
Synthesises the skeletal Security posture workflow.md, the MOP colour
codes doc, and current dashboard feature set into a single guide suitable
for Confluence/internal publishing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The column was missing from the new table definition, causing the
INSERT...SELECT to fail on any DB that already had ip_address (e.g.
auto-created by the updated server.js).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a persistent per-user staging queue on the Reporting page so
analysts can tag findings during review and batch-process Ivanti
workflows in one focused session.
Features:
- Checkbox column on findings table to tag rows into the queue
- Add-to-queue popover: vendor input, FP / Archer / CARD workflow toggle
(CARD skips vendor requirement and stores IP address instead)
- Queue slide-out panel (420px, CSS transition) with items grouped by
vendor; CARD items are their own top section showing IP address
- Per-item complete toggle, individual delete, and multi-select bulk delete
- Clear Completed footer button
- Queue button in header with live pending-count badge
- All data DB-backed (ivanti_todo_queue table, per-user scoped)
- Popover flips above row when near bottom of viewport
Migrations required on existing DBs:
node backend/migrations/add_ivanti_todo_queue_table.js (or let server auto-create)
node backend/migrations/add_card_workflow_type.js
node backend/migrations/add_todo_queue_ip_address.js
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Table: removed disabled={queued} from the row checkbox so accentColor
renders properly — checked rows now show a solid blue tick instead of
the greyed-out browser default.
Queue panel: each item now has a small red selection checkbox (opacity
0.35 when idle, full when selected). Selecting any items reveals a red
'Delete (N)' button in the footer alongside 'Clear Completed'. Bulk
deletes run in parallel; selection state is automatically pruned when
items are removed via the individual trash button.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
CARD workflow type no longer requires a vendor/platform entry since
asset disposition is handled entirely within CARD. In the popover the
vendor field is replaced with a note when CARD is selected, and the
Add button is enabled immediately.
In the queue panel, CARD items are separated into their own top section
(green header) rather than being mixed into vendor groups.
Backend validation updated to skip vendor requirement for CARD.
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>
Rewrites the README from scratch to reflect the full current state of the
application. Major additions over the previous version:
- Ivanti/RiskSense integration: env vars, sync behaviour, findings cache
- Reporting page: all 4 donut charts, findings table columns, column
management, per-column filtering (including empty-cell filter),
inline hostname/DNS overrides, inline notes, CSV/XLSX export
- FP workflow tracking: finding vs ticket count distinction, closed-finding
sweep for Approved FPs
- import_notes_from_csv.py script documentation with usage/args
- Full API reference updated with all Ivanti findings endpoints
- Architecture diagram updated with new route and component files
- Database schema updated with all Ivanti tables and new columns
- Migrations section updated with two new Ivanti migration scripts
- Configuration section updated with all IVANTI_* env vars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Columns that contain any blank values now show a '— empty —' entry at the
top of the filter dropdown. Selecting only that entry shows findings with
nothing in that column (e.g. workflow with no FP# ticket assigned).
Uses an EMPTY_SENTINEL constant ('__EMPTY__') in the filter Set so blank
cells are handled distinctly from non-blank values. Works for both
single-value and multi-value (CVEs) columns.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Renamed the existing FP chart to "FP Finding Status" (counts findings per
workflow state) and added a new "FP Workflow Status" chart that counts
unique FP# ticket IDs per state — so 10 findings under one FP# ticket
counts as 1 ticket, not 10.
Backend: extractFPWorkflow now returns { id, state }; syncFPWorkflowCounts
builds both a finding-count map and a deduped FP# ID map, storing them in
separate columns (fp_workflow_counts_json, fp_id_counts_json). The endpoint
returns findingCounts/findingTotal and idCounts/idTotal.
Frontend: FPWorkflowDonut accepts a centerLabel prop; both donuts share the
same component fed with their respective data slices from the single fetch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The FP Workflow Status donut was reading from the in-memory open findings
array, so Approved FPs (which close the finding and remove it from the
open cache) were invisible.
Backend: during each sync, compute FP workflow state counts from open
findings then sweep all pages of closed findings to capture Approved
(and any other closed-state) FP workflows. Counts are stored in a new
fp_workflow_counts_json column on ivanti_counts_cache and exposed via
GET /api/ivanti/findings/fp-workflow-counts.
Frontend: FPWorkflowDonut now receives counts/total props from the new
endpoint (fetched on load and refreshed after manual sync) instead of
deriving them from the findings prop.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a new SVG donut chart showing the distribution of FP workflow states
(Actionable, Requested, Reworked, Approved, Rejected, Expired, Unknown)
for all findings that have an associated FP# workflow ticket.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
If a finding ID from the CSV isn't in ivanti_findings_cache it is now
silently skipped (resolved or outdated) rather than stored. Also aborts
early with a clear message if the cache is empty, prompting the user to
run a Sync first.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reads a CSV with ID and NOTES columns, matches finding IDs against
the cache, and upserts notes into ivanti_finding_notes. Supports
--dry-run for previewing changes, warns on unknown IDs, truncates
notes over 255 chars, and skips unchanged rows.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- New ivanti_finding_overrides table (finding_id, field, value) with
UNIQUE(finding_id, field) — same survival-across-sync pattern as notes
- PUT /api/ivanti/findings/:id/override (editor/admin only) — saves or
clears a field override; empty value = revert to Ivanti
- Overrides merged into findings at read time via readOverrides()
- Whitelisted fields: hostName, dns
Frontend:
- OverrideCell component — click to edit inline (editor/admin only),
Enter/blur to save, Escape to cancel
- Amber dot indicator on cells with an active local override
- Hover tooltip shows original Ivanti value when overridden
- RotateCcw button reverts cell back to Ivanti value in one click
- canWrite() gating via useAuth — viewers see the value, can't edit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace FP# Workflow chart with a 3-segment Action Coverage donut:
- FP Request — finding has an Ivanti FP# workflow
- Archer Exception — note matches EXC-\d+ pattern
- Pending — no action taken yet
Clicking a segment filters the findings table to that category with a
colored badge in the action bar (click again or × to clear).
Home page: each Archer ticket now has a filter icon button that navigates
directly to the Reporting page pre-filtered to findings whose notes
reference that EXC number. The EXC badge appears in the table action bar
with a one-click clear.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a second SVG donut chart showing the distribution of FP# workflow
states (Expired, Rejected, Reworked, Actionable, Requested, Approved,
No FP#) computed from the already-loaded findings array — no new API
calls or backend changes required.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend: adds ivanti_counts_cache table, fetches Closed count (page 0,
size 1) from Ivanti after each Open sync, and exposes GET /counts endpoint.
Frontend: replaces the Metrics placeholder with an SVG donut chart showing
Open vs Closed proportions with counts and percentages. Counts are fetched
on mount and refreshed after manual sync.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds an Export dropdown button to the Reporting page action bar.
Exports respect current filters, sort order, and column visibility.
CSV uses pure JS (UTF-8 BOM for Excel compatibility); XLSX uses SheetJS
with auto-fitted column widths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>