- Infoblox WAPI integration (read-only) for resolving IPs to FQDNs
- backend/helpers/infobloxApi.js — Basic auth, host/PTR/IPv6 lookups
- backend/routes/infoblox.js — /api/infoblox endpoints
- Globe icon on Reporting page hostName/dns columns for one-click DNS lookup
- Pending: WAPI credentials with API access permissions
- Scan Posture page for Access Ops platform/version tracking
- backend/routes/scanPosture.js
- frontend/src/components/pages/ScanPosturePage.js
- Page visibility and nav drawer entries
- Ivanti findings OS field enrichment
- Migration to add os_name/os_class/os_version columns
- Backfill script for existing findings
- ivantiFindings route updates to persist OS data on sync
Transform Exports page from download-only to interactive report workbench.
All export buttons now open a full-page ReportViewer with sort, filter,
inline edit, row deletion, and xlsx download of the curated data.
New features:
- ReportViewer component with multi-sheet tab support
- Atlas Commitment Dates report (overdue highlighting, hostname resolution from DB)
- Scan Type Coverage report (agent/network/mixed per host with summary bar)
- GET /api/atlas/commitments endpoint (JOINs atlas cache with findings for hostnames)
After the unify_tickets_table migration runs, jira_tickets is renamed to
jira_tickets_legacy. The four earlier Jira migrations now detect the unified
tickets table and exit cleanly instead of failing with 'table does not exist'.
The Jira tickets preserve their original IDs via OVERRIDING SYSTEM VALUE,
then the sequence is reset to max(id) before Archer rows auto-increment.
Without this, Archer inserts collide with Jira IDs on the primary key.
Add unified tickets table with ticket_type discriminator column ('archer'|'jira').
Migration preserves Jira ticket IDs for FK integrity, updates junction table FK,
and renames old tables to *_legacy for rollback path.
New /api/tickets router provides full CRUD with type-aware validation, plus all
Jira integration endpoints (lookup, sync, create-in-jira). Old routes
(/api/jira-tickets, /api/archer-tickets) refactored as backward-compatible
proxies querying the unified table.
Updated ivantiTodoQueue ticket-links JOIN and server.js CVE cascade queries
to reference the new tickets table.
run-all.js executes each migration as a child process via node. The migration
must call process.exit() or the pool keeps it alive and the runner times out
(exit code null).
Alpine containers lack git and BusyBox grep doesn't support -P (Perl regex).
Install git in after_script and use grep -oE for POSIX extended regex instead.
Detect whether a finding was discovered by Qualys Cloud Agent (authenticated)
or a network appliance scan (unauthenticated) based on the presence of
'Agent ID' in hostAdditionalDetails from the Ivanti API response.
- Add scan_type column to ivanti_findings table (migration)
- Extract scanType in extractFinding() during sync
- Include scan_type in upsert and API response
- Add ScanTypeBadge component (green AGT / orange NET) on ReportingPage
- Add /raw-inspect diagnostic endpoint for inspecting raw Ivanti data
The /devices/by-ip/:ip endpoint previously returned 404 when an IP existed
in NetBox IPAM but wasn't assigned to a device interface. Now returns the
IPAM record (dns_name, status, role, description, comments) with a 200
response and ipamOnly flag.
Frontend NetBoxBadge gains a new 'ipam_only' state — purple 'IP' pill badge
with a dedicated tooltip showing available IPAM metadata (address, status,
DNS name, FQDN, role, VRF, tenant). Distinguishes clearly from full device
records while still surfacing useful infrastructure context.
New features:
- Granite supplemental workbook ingest with reconciliation
- Drag-fill and bulk default cascade in Granite Loader
- OS type searchable dropdown in Granite Loader
- Persist selections across pages + Select All
- NetBox badge on Reporting page with CARD cross-check
- CARD API connection retry for DNS round-robin dead nodes
- Granite Loader export to supplemental hygiene section
Fixes:
- Compliance reconcile stale core columns (type field vs string matching)
- Compliance upload case-insensitive column matching
- Compliance summary query NULL vertical handling
- Compliance page state persistence across uploads
- TLS for GitLab feedback integration after v19 upgrade
Also: updated user guide with all new features, added release comms template.
The compliance_config.json was pruned to remove metrics no longer tracked,
but the frontend category map was not updated in sync. This caused the
compliance-drift-metric-coverage test to fail in CI.
nidl.charter.com resolves to multiple A records via round-robin but not all
are reachable from this network. Both acquireToken() and cardRequest() now
retry up to 2 times on ETIMEDOUT/ECONNREFUSED/ECONNRESET before failing.
Also prunes obsolete metric categories from compliance_config.json and fixes
a comment referencing the correct CARD API hostname.
Map the OS (UDA#CONTROLLER CONFIG#OS) and OSTYPE (UDA#EQUIP MIGRATION#OSTYPE)
columns to a picklist of 38 standard OS types extracted from the Granite
Team_Device Loader reference sheet. Both columns now render as searchable
dropdowns in the bulk defaults row and inline cell editing.
The /summary endpoint now uses a single query instead of two-step fallback.
Update the test mock to match the new SQL pattern while keeping legacy
patterns as fallback for compatibility.
- Parser (parse_compliance_xlsx.py): column name matching is now
case-insensitive via _resolve_col() helper. New reports using lowercase
headers (e.g. 'preferred - hostname') are parsed correctly instead of
silently returning 0 items.
- Drift checker (driftChecker.js): core column presence check uses
case-insensitive comparison so lowercase headers no longer trigger
false 'missing from all detail sheets' breaking findings.
- Summary endpoint (compliance.js): query now selects the most recent
upload where vertical IS NULL OR vertical = 'NTS_AEO' in a single
query, instead of preferring NULL-vertical legacy uploads that are
outdated.
- Page persistence (App.js): localStorage page restore no longer checks
canAccessPage during useState init (user is null at that point).
An effect validates the page once auth resolves.
Closes#45
Adds a Known Bugs & Lessons Learned section to the workflow steering
file documenting the reconcileConfig message-matching failure pattern
and the lesson: use type fields, not message substring matching, for
programmatic finding identification.
The reconcileConfig() function used string matching 'is missing core column'
to identify core column findings, but compareSchemaToDrift() produces messages
like 'Core column "X" is missing from all N detail sheet(s)'. The mismatch
meant reconciliation never touched core_cols — stale columns persisted and
blocked uploads indefinitely.
Additionally, the old logic counted findings per column (always 1 since the
detection aggregates) and compared against detailSheetCount. Since the finding
already confirms the column is missing from ALL sheets, the count check was
redundant and always failed.
Fix: match on the actual message format and trust the detection's all-sheets
assertion directly.
The feedback route had a typo: rejectAuthorized (no-op) instead of
rejectUnauthorized. With GitLab now on HTTPS with a self-signed cert,
this caused issue creation and screenshot uploads to fail.
- Fix rejectAuthorized → rejectUnauthorized in both request blocks
- Update GITLAB_URL default from http:// to https:// in .env.example,
configure.js, README, and reference manual
- Selections persist when paging through the device table
- Select All button fetches all hostnames for the active sheet
- Granite Loader button fetches full device data for all selected
(not just current page) before opening the modal
- Clear selection button when items are selected across pages
- Page checkbox adds to existing selection instead of replacing
- Drag-fill: hover bottom-right corner of a cell to get a fill handle,
drag down to fill that value into rows below (Excel-style)
- Bulk default deletion now cascades: clearing a default also removes
per-row overrides that matched that value
- initialDevices now passes equip_inst_id through from supplemental data
Select devices via checkboxes in the Granite Hygiene device table and
generate Granite Loader sheets directly — equip_inst_id is already
present from the supplemental data so no CARD enrichment needed.
New features:
- NetBox device inventory API framework (future Granite replacement)
- Quick-create CVE from tooltip on missing CVEs
- Auto-convert .msg attachments to PDF for FP workflows
- APPID + APPREFID searchable picker in Granite Loader
- BU Lookup tool in Admin panel
- In-app User Guide with TOC navigation
- User Management search/filter/sort
- Admin scope toggle for team-filtered views
- Dual-port TLS support for FQDN access
Fixes:
- EQUIP_INST_ID extraction fallbacks
- APPID picker crash
- BU reassignment banner accuracy
- User Guide TOC scroll navigation
- CI verify jobs HTTPS support
Adds API helper and route module for NetBox DCIM integration as a future
replacement for Granite inventory management.
Helper (helpers/netboxApi.js):
- Token-based auth (Authorization: Token <token>)
- Device CRUD: list, get, create, update, delete
- IP cross-reference: find device by IP via IPAM
- Reference data: sites, device types, device roles
- Connection test, TLS skip support
Routes (routes/netbox.js) mounted at /api/netbox:
- GET /status — config and connectivity check
- GET /devices — list with filters and pagination
- GET /devices/search — general search
- GET /devices/by-ip/:ip — cross-reference IP to device
- GET /devices/:id — single device detail
- POST /devices — create (Admin, Standard_User)
- PATCH /devices/:id — partial update
- DELETE /devices/:id — delete (Admin only)
- GET /sites, /device-types, /device-roles — reference data
Integration is optional — gracefully returns 503 if env vars are unset.
New env vars: NETBOX_API_URL, NETBOX_API_TOKEN, NETBOX_SKIP_TLS
When hovering a CVE badge on the Reporting Page that doesn't exist in the
database, the tooltip now shows a 'Not yet tracked' message with an 'Add CVE'
button (permission-gated to Admin/Standard_User). Clicking opens a modal that
auto-populates metadata from NVD and submits via POST /api/cves.
Features:
- Create_Tooltip variant in CveTooltip for exists:false responses
- CveCreateModal with NVD auto-population, form validation, error handling
- 150ms hover bridge so users can reliably click the tooltip button
- Cache invalidation on successful creation
- Success toast notification (4s auto-dismiss)
Ivanti rejects .msg (Outlook email) file uploads. Now when a user
attaches a .msg file, the backend:
1. Accepts the upload (added .msg to ALLOWED_EXTENSIONS)
2. Converts it to PDF via Python (extract-msg + reportlab)
3. Sends the PDF to Ivanti instead
The PDF preserves subject, sender, date, recipients, and body.
Conversion applies to both initial FP submission and add-attachments
resubmit endpoint. Returns 422 if conversion fails.
Dependencies: extract-msg, reportlab (pip3 install)
The rsync --delete was wiping CA-signed certificates on every deploy.
Add backend/certs to the exclude list alongside .env and uploads so
certs installed on the target host are never overwritten by deploys.
The fallback cert generation step still runs if no cert exists (fresh
deploy), but will not replace an existing CA-signed cert.
On retry, only send devices still missing EQUIP_INST_ID to CARD.
Devices that already came back with data on the first pass are
skipped, reducing API calls and wait time when only a subset of
devices timed out.
Add 10 most-used APPIDs (CMTS, Jump Servers, Console Servers, RANCID,
CableOS, NDI, NTP, Vecima, Tools Infra, Adtran) as starred entries
at the top of the dropdown for quick access. Full CMDB list follows
below. Star prefix is stripped during value extraction so cell still
stores clean 'APP_ID - APPREFID' format.
The SearchableSelect shows full context for searching (app name, env)
but when a selection is made, only the 'APP_ID - APPREFID' portion is
stored as the cell value. Applies to both bulk defaults and per-cell
inline edits.
SearchableSelect expects string arrays, not objects. Convert CMDB
options to strings in format 'APP_NAME | APP_ID - APPREFID [ENV]'
so filtering and display work correctly.
Load CMDB app instance data (4797 entries) from static JSON and present
a searchable dropdown in the APP_ID_ASSET_TAG column. Users search by
app name or APP ID, select an entry, and the field populates with the
'APP_ID - APPREFID' format required by Granite.
- Convert Cherwell CMDB CSV export to frontend/public/cmdb-appids.json
- LoaderModal: load APPID options on open, provide via getPicklist()
helper that overrides COLUMN_PICKLISTS for the APP_ID_ASSET_TAG field
- Options display as 'APP_NAME (APP_ID) [ENV]' for easy identification
- Works in both bulk defaults and per-cell inline editing
The BU history records may have been written days before the anomaly
detected them leaving scope. Instead of a fragile time window, fetch
the N most recent BU change records where N matches the classification
count shown in the banner. This ensures the detail always matches
the summary.
- Backend: /anomaly/latest now returns the most recent SIGNIFICANT
anomaly instead of the absolute latest (non-significant ones are
useless to the banner which skips them anyway)
- Frontend: expand BU detail time window from 60 minutes to 25 hours
before the anomaly timestamp to capture changes from the full sync
cycle (syncs run once per 24h)
When ALSO_LISTEN_PORT is set (e.g., 443), the server binds to both
the primary PORT (3001) and the additional port using the same TLS
certs. This enables clean FQDN access (https://aegis-uat.charterlab.com)
on port 443 while keeping the existing IP:3001 URL working as a
fallback during the DNS transition period.
Same UX improvements as the modal — search bar, group filter chips,
sortable column headers, and filtered count. Applied to the in-page
UserManagementPanel in AdminPage.js so the Admin Panel tab has
parity with the modal.
- Search bar filters users by name, email, or team assignment
- Group filter chips: All, Admin, Standard_User, Leadership, Read_Only, No Teams
- Sortable column headers (User, Group, Teams, Status, Last Login)
- Shows filtered count (e.g., '5 of 12 users')
- 'No Teams' filter quickly identifies users needing team assignment
Backend:
- GET /api/ivanti/findings/org-hierarchy — returns all SVP names and
all BU names from Ivanti suggest API (Admin only)
- GET /api/ivanti/findings/bus-by-svp?svp=<name> — returns BUs under
a specific SVP with finding counts (Admin only)
Frontend (Admin > BU Lookup tab):
- 'Load Org Hierarchy' button fetches all SVPs and BUs
- SVP dropdown (sorted by finding count) to filter BUs by org leader
- Results table shows BU name, finding count, and configured status
(green badge for BUs already in the dashboard, grey for unconfigured)
- Enables admin to discover which BUs roll up under an SVP for correct
team assignment when onboarding new users
Backend:
- GET /api/ivanti/findings/bu-lookup?q=<hostname|ip> — queries the
live Ivanti API to discover which BU a host is assigned to. Returns
deduplicated results with hostName, ipAddress, BU, and hostId.
Admin-only endpoint.
Frontend:
- Add 'BU Lookup' tab to Admin page with search input and results table
- Shows BU assignment badge (blue for tagged, amber for untagged)
- Supports Enter key to search, loading state, error display
- Useful for verifying team assignments when onboarding new users
Document the step-by-step plan to transition from IP:port access to
friendly DNS names (aegis.charterlab.com for production,
aegis-uat.charterlab.com for UAT). Covers cert regen, port change,
CORS update, CI/CD changes, and rollback procedure.
Headings now render with id attributes derived from their text so
TOC anchor links (#getting-started, etc.) have targets. Internal
anchor clicks use scrollIntoView with smooth behavior instead of
full page navigation.
- UserGuideModal: full-screen overlay rendering user-guide.md as
styled markdown with search filtering
- UserMenu: add 'User Guide' item with BookOpen icon between
Feature Request and Sign Out
- Serve user-guide.md as static file from frontend/public/
- Guide renders with dark theme styling matching the dashboard
aesthetic (blue headers, slate text, amber blockquotes)
Create docs/user-guide.md covering all dashboard features from the
perspective of a security engineer. Includes getting started, all
page workflows (Home, Triage, Compliance, CCP Metrics, Knowledge Base,
Exports, Jira, Archer Templates), user settings, admin features
(View As, scope toggle), and tips/best practices for daily triage.
No code references, deployment steps, or technical implementation
details — purely operational guidance for end users.
requireTeam() now respects an optional ?teams= query param from Admin
users as a voluntary scope filter. When the Admin Scope Toggle is set
to 'My Teams', the frontend sends ?teams=STEAM,ACCESS-ENG and the
backend applies the filter. When set to 'All BUs' (no param), Admin
gets the full unfiltered view.
Non-admin users continue to be enforced by their bu_teams assignment
regardless of any query param.
Non-Admin users should not see user management events (create, delete,
group changes, password resets), impersonation events, or admin-only
compliance operations (config reconcile, upload rollback) in the
Recent Activity panel.
Health checks now try HTTPS first (with -k for self-signed certs),
then fall back to HTTP. Handles both TLS-enabled and plain HTTP
deployments without failing the pipeline.
The certs/ directory is gitignored so rsync --delete wipes it on each
deploy. Add a post-rsync step that generates a self-signed cert if
cert.pem is missing, preventing TLS-enabled servers from crashing on
restart. Applies to both staging and production deploy jobs.