Files
cve-dashboard/docs/bug-reports/ivanti-panel-bugs-2026-05-12.md

6.1 KiB

Ivanti Workflows Panel Bug Fixes — 2026-05-12

Summary

Multiple bugs in the Ivanti Workflows panel on the homepage caused incorrect data display, a React crash on archive card clicks, and missing BU scope filtering. These issues emerged after the PostgreSQL migration (timestamps changed format) and the multi-BU tenancy feature (archive panel was never wired to respect the admin scope toggle).

Commit: 8c93e86 on master


Bug 1: "Synced Invalid Date"

Symptom: The Ivanti Workflows panel and Vulnerability Triage page both display "Synced Invalid Date" instead of the last sync timestamp.

Cause: The frontend date parsing code was written for SQLite's datetime('now') format (2024-01-15 10:30:00) and applied .replace(' ', 'T') + 'Z' to convert it to ISO. After the PostgreSQL migration, the pg driver returns timestamps as ISO strings already (2026-05-12T16:29:39.154Z). Appending a second Z produces "2026-05-12T16:29:39.154ZZ" which is unparseable.

Fix: Removed the .replace(' ', 'T') + 'Z' transformation. Now uses new Date(syncedAt) directly, which handles ISO strings correctly.

Files changed:

  • frontend/src/App.js — home page sync display
  • frontend/src/components/pages/ReportingPage.js — triage page sync display

Bug 2: Incorrect Total Workflows Count

Symptom: The "Total Workflows" number displayed a value far higher than the actual number of workflows visible in the list (e.g., 6443 instead of 13).

Cause: The backend readState() function returned row.total from the database, which stored the Ivanti API's page.totalElements value. This represents the total matching the user filter across all pages on the Ivanti platform — not the count of workflows actually cached locally. The Ivanti API's total count appears unreliable (returns inflated numbers that don't match the actual page content).

Fix: Changed readState() to return workflows.length — the actual number of workflows in the cached JSON array.

Files changed:

  • backend/routes/ivantiWorkflows.js

Bug 3: React Crash on Archive Card Click (Blank Page)

Symptom: Clicking any state card (Active, Archived, Returned, Closed) in the ArchiveSummaryBar caused the entire page to go blank. No error message visible to the user.

Cause: PostgreSQL returns numeric columns as strings (e.g., "9.38" instead of 9.38). The archive list rendering called .toFixed(1) directly on these string values. Strings do not have a .toFixed() method, causing a TypeError that crashed the React component tree (no ErrorBoundary exists to catch it).

Fix: Wrapped severity values in Number() before calling .toFixed():

  • Number(a.last_severity).toFixed(1) for archive severity
  • Number(a.related_active.severity).toFixed(1) for related finding severity

Files changed:

  • frontend/src/App.js

Bug 4: Archive Panel Not Respecting BU Scope

Symptom: The ArchiveSummaryBar stat cards (Active, Archived, Returned, Closed) always showed counts for ALL business units, even when the admin scope toggle was set to "My Teams" (STEAM, ACCESS-ENG). Clicking a card also showed findings from all BUs.

Cause: The archive endpoints (/api/ivanti/archive/stats and /api/ivanti/archive) had no teams query parameter support. The ArchiveSummaryBar component fetched stats without passing the active scope. The handleArchiveStateClick function in App.js fetched the archive list without a teams filter. This was a gap from the multi-BU tenancy implementation — the archive panel was never wired to respect the admin scope.

Fix:

  • Backend: Added teams query parameter to both /api/ivanti/archive/stats and /api/ivanti/archive endpoints. When provided, the endpoints JOIN with ivanti_findings on finding_id to filter by bu_ownership ILIKE ANY(patterns).
  • Frontend: ArchiveSummaryBar now accepts a teamsParam prop and passes it to the stats fetch. App.js passes getActiveTeamsParam() to the component and to handleArchiveStateClick.
  • The stats and list re-fetch automatically when the admin scope toggle changes.

Files changed:

  • backend/routes/ivantiArchive.js
  • frontend/src/components/pages/ArchiveSummaryBar.js
  • frontend/src/App.js

Bug 5: ACTIVE State Click Returns Empty

Symptom: Clicking the "Active" stat card showed "No active findings" even though the count displayed a non-zero number.

Cause: The stats endpoint counted ACTIVE findings from ivanti_findings WHERE state = 'open' (correct), but the list endpoint queried ivanti_finding_archives WHERE current_state = 'ACTIVE' — which has zero records. Active findings are not archived; they live in ivanti_findings. The archive table only contains ARCHIVED, RETURNED, CLOSED, and CLOSED_GONE states.

Fix: When state=ACTIVE is requested, the list endpoint now queries ivanti_findings directly instead of ivanti_finding_archives. Results are formatted to match the archive card shape (finding_id, finding_title, host_name, ip_address, last_severity, etc.).

Files changed:

  • backend/routes/ivantiArchive.js

Bug 6: CLOSED_GONE Records Not Counted

Symptom: The "Closed" stat card showed a lower count than expected. 63 findings with current_state = 'CLOSED_GONE' were not included in the Closed count or shown when clicking the Closed card.

Cause: The stats endpoint only counted records matching the four known states (ACTIVE, ARCHIVED, RETURNED, CLOSED). The CLOSED_GONE state — added during the findings count investigation on 2026-04-24 — was not mapped to any display category.

Fix: CLOSED_GONE records are now rolled into the CLOSED count in stats, and clicking the Closed card queries for both CLOSED and CLOSED_GONE states.

Files changed:

  • backend/routes/ivantiArchive.js

Verification

  • Frontend build passes without errors
  • Backend syntax validated with node -c
  • Archive stats with teams filter returns scoped counts (64 active for STEAM+ACCESS-ENG vs 6545 unfiltered)
  • new Date(syncedAt).toLocaleString() produces valid date strings
  • Number("9.38").toFixed(1) returns "9.4" without throwing