Files
cve-dashboard/.kiro/specs/ivanti-fp-workflow-submission/design.md
jramos 382bc81a7e feat: add Ivanti FP workflow submission from Queue
- Add shared ivantiApi.js helper (ivantiPost + ivantiMultipartPost)
- Add ivantiFpWorkflow.js backend route with validation, Ivanti API
  workflow creation, attachment uploads, submission tracking, and audit
- Add add_fp_submissions_table.js migration
- Wire route into server.js at /api/ivanti/fp-workflow
- Add FpWorkflowModal component in ReportingPage.js with form fields,
  drag-and-drop file upload, progress indicator, and result views
- Add Create FP Workflow button to QueuePanel footer (editor/admin only)
- Refactor ivantiWorkflows.js and ivantiFindings.js to use shared helper
2026-04-07 16:20:24 -06:00

14 KiB

Design Document: Ivanti FP Workflow Submission

Overview

This feature extends the existing Ivanti Queue (QueuePanel) in the Reporting Page to allow users to submit False Positive (FP) workflows directly to the Ivanti/RiskSense API. The implementation adds a submission modal triggered from the queue panel, a backend API endpoint that proxies the workflow creation and attachment upload to Ivanti, and local tracking of submissions in SQLite.

The design follows existing codebase conventions: factory-pattern Express routes, inline React styles with the dark tactical theme, Multer for file uploads, and the ivantiPost() HTTP helper for Ivanti API calls.

Architecture

sequenceDiagram
    participant U as User (Browser)
    participant FE as React Frontend
    participant BE as Express Backend
    participant IV as Ivanti API
    participant DB as SQLite

    U->>FE: Select FP queue items, click "Create FP Workflow"
    FE->>FE: Open FpWorkflowModal with selected items
    U->>FE: Fill form, attach files, click Submit
    FE->>BE: POST /api/ivanti/fp-workflow (multipart/form-data)
    BE->>BE: Validate input, check auth
    BE->>IV: POST /client/{clientId}/workflowBatch (create FP workflow)
    IV-->>BE: 200 + workflow batch response (id, generatedId)
    alt Attachments present
        loop For each attachment
            BE->>IV: POST /client/{clientId}/workflowBatch/{id}/attachment
            IV-->>BE: 200 OK
        end
    end
    BE->>DB: INSERT into ivanti_fp_submissions
    BE->>DB: INSERT audit log entry
    BE->>DB: UPDATE ivanti_todo_queue SET status='complete'
    BE-->>FE: 200 + { workflowBatchId, generatedId, status }
    FE->>FE: Show success, refresh queue panel

Components and Interfaces

Backend

New Route Module: backend/routes/ivantiFpWorkflow.js

Exports createIvantiFpWorkflowRouter(db, requireAuth) following the existing factory pattern.

Endpoint: POST /api/ivanti/fp-workflow

  • Auth: requireAuth(db), requireGroup('Admin', 'Standard_User')

  • Content-Type: multipart/form-data (handled by Multer)

  • Request fields:

    • name (string, required) — workflow name, max 255 chars
    • reason (string, required) — justification text
    • description (string, optional) — additional details, max 2000 chars
    • expirationDate (string, required) — ISO date string, must be future
    • scopeOverride (string, optional) — "Authorized" (default) or "None"
    • findingIds (string, required) — JSON-encoded array of finding ID strings
    • queueItemIds (string, required) — JSON-encoded array of local queue item IDs
    • attachments (files, optional) — up to 10 files, 10MB each
  • Response (success):

    {
      "success": true,
      "workflowBatchId": 12345,
      "generatedId": "FP#12345",
      "attachmentResults": [
        { "filename": "evidence.pdf", "success": true },
        { "filename": "screenshot.png", "success": true }
      ],
      "queueItemsUpdated": 3
    }
    
  • Response (error):

    {
      "success": false,
      "error": "Ivanti API returned status 401",
      "step": "create_workflow",
      "details": "..."
    }
    

Internal flow:

  1. Parse and validate all form fields
  2. Verify all queueItemIds belong to the requesting user and are FP-type with pending status
  3. Call Ivanti API to create the workflow batch
  4. If attachments exist, upload each to the created workflow batch
  5. Insert a submission record into ivanti_fp_submissions
  6. Log audit entry via logAudit()
  7. Mark queue items as complete
  8. Return combined result

Ivanti API Calls

Reuses the existing ivantiPost() helper pattern from ivantiWorkflows.js. Adds a new ivantiMultipartPost() helper for attachment uploads that sends multipart/form-data instead of JSON.

Create Workflow Batch:

POST /client/{clientId}/workflowBatch
{
  "name": "FP - CVE-2024-1234 - Vendor X",
  "type": "FALSE_POSITIVE",
  "reason": "Scanner false positive confirmed by manual investigation",
  "description": "Additional context...",
  "expirationDate": "2025-12-31",
  "scopeOverrideAuthorization": "AUTHORIZED",
  "hostFindingIds": [123456, 789012],
  "subType": "FALSE_POSITIVE"
}

Upload Attachment:

POST /client/{clientId}/workflowBatch/{workflowBatchId}/attachment
Content-Type: multipart/form-data

Form field: file — the binary file content.

Shared HTTP Helpers

The existing ivantiPost() function is duplicated across ivantiWorkflows.js and ivantiFindings.js. This design extracts it into a shared helper at backend/helpers/ivantiApi.js alongside the new multipart helper:

  • ivantiPost(urlPath, body, apiKey, skipTls) — JSON POST (existing logic)
  • ivantiMultipartPost(urlPath, fileBuffer, fileName, apiKey, skipTls) — multipart file upload

Frontend

New Component: FpWorkflowModal

Located in frontend/src/components/pages/ReportingPage.js (inline, following the existing pattern where QueuePanel and AddToQueuePopover are defined in the same file).

Props:

  • open (boolean) — controls visibility
  • onClose (function) — close handler
  • selectedItems (array) — FP queue items selected for submission
  • onSuccess (function) — callback after successful submission, triggers queue refresh

State:

  • name, reason, description, expirationDate, scopeOverride — form fields
  • files — array of File objects for upload
  • submitting — boolean, disables form during submission
  • progress — object tracking current step and attachment progress
  • errors — validation error map
  • result — submission result (success/failure details)

UI Layout:

  • Modal overlay with dark backdrop (matching existing modal patterns)
  • Header: "Create FP Workflow" with close button
  • Body sections:
    1. Selected findings summary (read-only list with finding_id, title, CVEs)
    2. Workflow configuration form (name, reason, description, expiration, scope override toggle)
    3. File upload area (drag-and-drop zone + file list)
  • Footer: Cancel and Submit buttons, progress indicator when submitting

QueuePanel Modifications

  • Add a "Create FP Workflow" button in the footer, next to existing "Delete Selected" and "Clear Completed" buttons
  • Button enabled only when selectedIds contains at least one pending FP-type item
  • Clicking opens FpWorkflowModal with the filtered FP items
  • After successful submission, the onSuccess callback triggers queue refresh

Data Models

New Table: ivanti_fp_submissions

CREATE TABLE IF NOT EXISTS ivanti_fp_submissions (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER NOT NULL,
    username TEXT NOT NULL,
    ivanti_workflow_batch_id INTEGER,
    ivanti_generated_id TEXT,
    workflow_name TEXT NOT NULL,
    reason TEXT NOT NULL,
    description TEXT,
    expiration_date TEXT NOT NULL,
    scope_override TEXT NOT NULL DEFAULT 'Authorized',
    finding_ids_json TEXT NOT NULL,
    queue_item_ids_json TEXT NOT NULL,
    attachment_count INTEGER DEFAULT 0,
    attachment_results_json TEXT,
    status TEXT NOT NULL DEFAULT 'success' CHECK(status IN ('success', 'partial', 'failed')),
    error_message TEXT,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_fp_submissions_user ON ivanti_fp_submissions(user_id);
CREATE INDEX IF NOT EXISTS idx_fp_submissions_ivanti_id ON ivanti_fp_submissions(ivanti_generated_id);

Status values:

  • success — workflow created and all attachments uploaded
  • partial — workflow created but one or more attachments failed
  • failed — workflow creation itself failed (record kept for audit)

Migration Script: backend/migrations/add_fp_submissions_table.js

Standard migration script following the existing pattern (e.g., add_ivanti_todo_queue_table.js).

Correctness Properties

A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.

Property 1: FP Workflow Button Enabled State

For any set of queue items and any selection of item IDs, the "Create FP Workflow" button should be enabled if and only if the selection contains at least one queue item that has workflow_type === 'FP' and status === 'pending'.

Validates: Requirements 1.1

Property 2: FP-Only Item Filtering

For any set of selected queue items containing a mix of workflow types (FP, Archer, CARD), the items passed to the FP workflow submission modal should contain only items where workflow_type === 'FP', and the count of filtered items should be less than or equal to the count of selected items.

Validates: Requirements 1.2

Property 3: Form Validation Correctness

For any form state (name, reason, description, expirationDate, scopeOverride), validation should pass if and only if: name is a non-empty string of at most 255 characters, reason is a non-empty string, description (if provided) is at most 2000 characters, and expirationDate is a valid date strictly after today. When validation fails, the returned error map should contain a key for each invalid field and no keys for valid fields.

Validates: Requirements 2.4, 2.5

Property 4: File Extension Validation

For any filename string, the file acceptance function should return true if and only if the file's extension (case-insensitive) is one of: .pdf, .png, .jpg, .jpeg, .gif, .doc, .docx, .xlsx, .csv, .txt, .zip. Files with disallowed extensions should be rejected.

Validates: Requirements 3.3

Property 5: API Payload Construction

For any valid form input (name, reason, description, expirationDate, scopeOverride, findingIds), the constructed Ivanti API request body should contain: type equal to "FALSE_POSITIVE", name equal to the input name, reason equal to the input reason, expirationDate equal to the input date, scopeOverrideAuthorization mapped from the input scopeOverride value, and hostFindingIds equal to the input finding IDs parsed as integers.

Validates: Requirements 4.1

Property 6: Queue Items Marked Complete on Success

For any set of queue item IDs associated with a successful FP workflow submission, after the post-submission handler runs, all those queue items should have status === 'complete'.

Validates: Requirements 5.1

Property 7: Post-Submission Persistence Completeness

For any successful FP workflow submission with a given workflow batch ID, name, user ID, and finding IDs, the resulting submission record should contain all of: ivanti_workflow_batch_id, workflow_name, user_id, finding_ids_json (parseable to the original finding IDs array), and a non-null created_at timestamp. Additionally, the audit log entry should have action "ivanti_fp_workflow_created", entity_type "ivanti_workflow", and details containing the workflow name and finding IDs.

Validates: Requirements 6.1, 6.2

Property 8: Role-Based UI Visibility

For any user role, the "Create FP Workflow" button should be visible if and only if the user's role is "editor" or "admin". Users with the "viewer" role should not see the button.

Validates: Requirements 7.2

Error Handling

Ivanti API Errors

HTTP Status Error Type User-Facing Message System Behavior
401 Auth failure "Ivanti API key is invalid or missing. Contact your administrator." Log error, preserve form state
419 Insufficient privileges "API key lacks workflow creation permissions." Log error, preserve form state
429 Rate limited "Ivanti API rate limit reached. Please try again in a few minutes." Log error, preserve form state
5xx Server error "Ivanti API is temporarily unavailable. Please try again later." Log error, preserve form state
Other Unknown "Workflow creation failed: {status} — {message}" Log error with full response, preserve form state

Partial Failure (Attachment Upload)

When the workflow batch is created successfully but one or more attachment uploads fail:

  • The submission record is saved with status = 'partial'
  • The response includes the workflow batch ID and per-attachment success/failure details
  • The UI shows which attachments failed and allows retry
  • The queue items are still marked complete (the workflow itself was created)

Local Database Errors

  • If the submission record INSERT fails: log error, still return success to user (Ivanti workflow was created)
  • If queue item status UPDATE fails: return success with a warning that local queue state may be stale
  • If audit log INSERT fails: fire-and-forget (existing pattern from logAudit())

Input Validation Errors

  • All validation errors return 400 with a structured error object mapping field names to error messages
  • Frontend validates before sending to prevent unnecessary API calls
  • Backend re-validates all inputs as a security measure

Testing Strategy

Property-Based Testing

Use fast-check as the property-based testing library for JavaScript.

Each correctness property maps to a single property-based test with a minimum of 100 iterations. Tests are tagged with the format: Feature: ivanti-fp-workflow-submission, Property {number}: {title}.

Property tests focus on pure functions extracted from the implementation:

  • isCreateFpButtonEnabled(items, selectedIds) — Property 1
  • filterFpItems(items) — Property 2
  • validateFpWorkflowForm(formData) — Property 3
  • isAllowedFileExtension(filename) — Property 4
  • buildIvantiPayload(formData, findingIds) — Property 5
  • Queue item status update logic — Property 6
  • Submission record creation — Property 7
  • Role-based visibility check — Property 8

Unit Testing

Unit tests complement property tests by covering:

  • Specific examples: known-good form submissions, known-bad inputs
  • Edge cases: empty finding lists, maximum file size boundary, expiration date exactly tomorrow
  • Error code mapping: verify each Ivanti HTTP status maps to the correct error message
  • Integration points: Multer file handling, multipart form construction
  • API response parsing: various Ivanti response formats

Test File Locations

  • backend/__tests__/ivantiFpWorkflow.test.js — backend route handler tests, validation, payload construction
  • backend/__tests__/ivantiFpWorkflow.property.test.js — property-based tests for backend logic
  • frontend/src/__tests__/fpWorkflowModal.test.js — frontend component and validation tests