# WIZ

## Export Vulnerability to WIZ on Vuln Created&#x20;

<figure><img src="/files/F7Qf70gGCtbfuy8rUPeK" alt=""><figcaption></figcaption></figure>

The purpose of this example is to export a vulnerability to [WIZ](https://www.wiz.io) when a vulnerability is created.

This example Flow can be downloaded from our [Flows GitHub Repository](https://github.com/AttackForge/Flows) and imported into your AttackForge.

**Initial Set Up**

* **Event**: Vuln Created
* **Secrets**:
  * af\_hostname - for example "demo.attackforge.com"
  * af\_key - your AttackForge user API key
  * logging\_level - set to "debug" if additional logging is required
  * wiz\_api\_host - for example "api.us17.app.wiz.io"
  * wiz\_client\_id - your WIZ Client Id
  * wiz\_client\_secret - your WIZ Client Secret
  * wiz\_integration\_id - your WIZ Intgration Id

**Action 1 - Validate Readiness for Export**

* **Script**:

```javascript
const vuln = data;

if (!vuln?.vulnerability_id) {
  return {
    decision: {
      status: 'abort',
      message: 'No vulnerability found in event data.'
    }
  };
};

// Check 1: Vuln is not deleted
const isDeleted = vuln?.vulnerability_is_deleted === true;
if (isDeleted) {
  return {
    decision: {
      status: 'finish',
      message: 'Vulnerability is deleted. Skipping WIZ export.'
    }
  };
};

// Check 2: Vuln is not pending
const isPending = vuln?.vulnerability_is_visible === false;
if (isPending) {
  return {
    decision: {
      status: 'finish',
      message: 'Vulnerability is pending. Skipping WIZ export.'
    }
  };
};

// Check 3: Vuln is not already exported to WIZ
const customFields = vuln?.vulnerability_custom_fields || [];
let exportedToWiz = false;
for (let i = 0; i < Array.length(customFields); i++) {
  const customField = customFields[i];
  if (customField.key === 'exported_to_wiz' && customField.value === 'Yes') {
    exportedToWiz = true;
  }
}
if (exportedToWiz) {
  return {
    decision: {
      status: 'finish',
      message: 'Vulnerability already exported to WIZ. Skipping.'
    }
  };
};

// Check 4  - Ensure all necessary asset fields exist
let asset_hostname;
let asset_port;
let asset_protocol;
let asset_name;
let asset_id;
let asset;
if (vuln?.vulnerability_affected_assets?[0]?.asset) {
  asset = vuln.vulnerability_affected_assets[0].asset;
}
if (!asset) {
  return {
    decision: {
      status: 'abort',
      message: 'Asset not found on vulnerability.'
    }
  };
}
if (asset.name) {
  asset_name = asset.name;
}
if (asset.id) {
  asset_id = asset.id;
}
if (asset.custom_fields && Array.isArray(asset.custom_fields)) {
  const custom_fields = asset.custom_fields;
  for (let i = 0; i < Array.length(custom_fields); i++) {
    const customField = custom_fields[i];
    if (customField.key === 'af_sys_hostnames' && customField.value?[0]) {
      asset_hostname = customField.value[0];
    }
    else if (customField.key === 'af_sys_ports' && customField.value?[0]) {
      asset_port = Number.parseInt(customField.value[0], 10);
    }
    else if (customField.key === 'protocols' && customField.value?[0]) {
      asset_protocol = customField.value[0];
    }
  }
}
if (!asset_id || !asset_name || !asset_hostname || !asset_port || !asset_protocol) {
  if (secrets.logging_level === 'debug'){
    Logger.debug('asset_id: ', asset_id);
    Logger.debug('asset_name: ', asset_name);
    Logger.debug('asset_hostname: ', asset_hostname);
    Logger.debug('asset_port: ', asset_port);
    Logger.debug('asset_protocol: ', asset_protocol);
  }
  return {
    deicision:{
      status: 'abort',
      message: 'Error: asset_id, asset_name, asset_hostname, asset_port, asset_protocol must all exist.'
    }
  };
}

if (secrets.logging_level === 'debug') {
  Logger.debug('Vulnerability ready for WIZ export: ', vuln.vulnerability_id);
}

return {
  decision: {
    status: 'continue',
    message: 'Vulnerability is ready for export to WIZ.'
  },
  data: {
    vuln: data,
    asset_id: asset_id,
    asset_name: asset_name,
    asset_hostname: asset_hostname,
    asset_port: asset_port,
    asset_protocol: asset_protocol
  }
};
```

**Action 2 - Build WIZ Payload**

* **Script**:

```javascript
const vuln = data?.vuln;
const vulnId = vuln?.vulnerability_id;
const payload = payloadGenerator(vuln);

if (secrets.logging_level === 'debug'){
  Logger.debug('payload built: ', JSON.stringify(payload));
}
return {
  decision:{
    status: 'continue',
    message: 'Successfully built JSON payload'
  },
  data: {
    vuln: vuln,
    wiz_payload: payload
  }
};

// Users can customise this to be multiple payload generation if needed.
function payloadGenerator(vuln){
  
  // Affected asset
  const endpoint = {
    assetId: data?.asset_id,
    assetName: data?.asset_name,
    host: data?.asset_hostname,
    port: data?.asset_port,
    protocol: data?.asset_protocol
  };

  // Severity mapping: AF → WIZ
  const severityMap = {
    'Critical': 'Critical',
    'High': 'High',
    'Medium': 'Medium',
    'Low': 'Low',
    'Info': 'None'
  };
  const afPriority = vuln?.vulnerability_priority || 'Low';
  const wizSeverity = severityMap[afPriority] || 'None';

  // description
  const description = (vuln?.vulnerability_description || '') + '\n\n' + (vuln?.vulnerability_attack_scenario || '');

  // assessmentDetails
  const affectedAssetsStr = '- ' + data?.asset_name + '\n';
  const notes = vuln?.vulnerability_notes || [];
  let notesStr = '';
  for (let i = 0; i < Array.length(notes); i++) {
    const noteText = notes[i]?.note || '';
    if (noteText) {
      notesStr = notesStr + noteText + '\n';
    }
  }
  const assessmentDetails = 'Affected Asset:\n\n' + (affectedAssetsStr || 'N/A')
    + '\nSteps to Reproduce:\n\n' + (vuln?.vulnerability_steps_to_reproduce || 'N/A')
    + '\n\nNotes:\n\n' + (notesStr || 'N/A');

  // Link to AttackForge vuln
  const projects = vuln?.vulnerability_projects || [];
  const projectId = (Array.length(projects) > 0) ? projects[0].id : '';
  const externalLink = 'https://' + secrets.af_hostname + '/projects/' + projectId + '/vulnerabilities/' + vulnId;

  // Stable datasource ID
  const datasourceId = secrets.wiz_datasource_id || ('attackforge-project-' + projectId);

  // Vulnerability type mapping
  const typeMap = {
    'Misconfiguration': 'Misconfiguration',
    'DAST': 'DAST',
    'SCA': 'SCA',
    'SAST': 'SAST',
    'IaC': 'IaC',
    'SecretDetection': 'SecretDetection',
    'ContainerScan': 'ContainerScan',
    'HostScan': 'HostScan'
  };
  const vulnType = 'DAST';
  const wizType = typeMap[vulnType] || 'DAST';

  const finding = {
    id: vulnId,
    name: vuln?.vulnerability_title || '',
    description: description,
    assessmentDetails: assessmentDetails,
    remediation: vuln?.vulnerability_remediation_recommendation || '',
    severity: wizSeverity,
    type: wizType,
    externalFindingLink: externalLink
  };  

  return {
    integrationId: secrets.wiz_integration_id,
    dataSources: [
      {
        id: datasourceId,
        analysisDate: vuln?.vulnerability_created || Date.datetime('now'),
        assets: [
          {
            details: {
              endpoint: endpoint
            },
            attackSurfaceFindings: [finding]
          }
        ]
      }
    ]
  };
}
```

**Action 3 - Get WIZ Access Token**

* **Method**: POST
* **URL**: <https://auth.app.wiz.io/oauth/token>
* **Headers**:
  * Key = Content-Type; Type = Value; Value = application/x-www-form-urlencoded
* **Request Script**:

```javascript
const req_body = 'grant_type=client_credentials&audience=wiz-api&client_id=' + secrets.wiz_client_id + '&client_secret=' + secrets.wiz_client_secret;

return {
  decision: {
    status: 'continue',
    message: 'Fetching WIZ access token.'
  },
  request: {
    body: 'grant_type=client_credentials&audience=wiz-api&client_id=' + secrets.wiz_client_id + '&client_secret=' + secrets.wiz_client_secret
  },
  data: {
    vuln: data?.vuln,
    wiz_payload: data?.wiz_payload
  }
};
```

* **Response Script**:

```javascript
if (response?.statusCode !== 200) {
  if (secrets.logging_level === 'debug') {
    Logger.debug('Error fetching WIZ token: ', JSON.stringify(response));
  }
  return {
    decision: {
      status: 'abort',
      message: 'Failed to get WIZ access token. HTTP status: ' + (response?.statusCode || 'unknown')
    }
  };
}

const token = response?.jsonBody?.access_token;
if (!token) {
  return {
    decision: {
      status: 'abort',
      message: 'WIZ access_token not found in token response.'
    }
  };
}

if (secrets.logging_level === 'debug') {
  Logger.debug('WIZ access token obtained successfully.');
}

return {
  decision: {
    status: 'continue',
    message: 'WIZ access token obtained.'
  },
  data: {
    vuln: data?.vuln,
    wiz_payload: data?.wiz_payload,
    wiz_token: token
  }
};
```

**Action 4 - Request WIZ Upload URL**

* **Method**: POST
* **URL**: https\://{{wiz\_api\_host}}/graphql
* **Headers**:
  * Key = Content-Type; Type = Value; Value = application/json
* **Request Script**:

```javascript
const vuln = data?.vuln;
const currTime = Date.datetime("now", "epoch");
const wiz_upload_filename = 'attackforge-vuln-' + currTime + '.json';

if (secrets.logging_level === 'debug') {
  Logger.debug('WIZ enrichment payload: ', JSON.stringify(payload));
}

return {
  decision: {
    status: 'continue',
    message: 'Requesting WIZ upload URL for vulnerability ' + vuln.vulnerability_id + '.'
  },
  request: {
    url: 'https://' + secrets.wiz_api_host + '/graphql',
    headers: {
      'Authorization': 'Bearer ' + data.wiz_token,
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    },
    body: {
      query: 'query RequestSecurityScanUpload($filename: String!) { requestSecurityScanUpload(filename: $filename) { upload { id url systemActivityId } } }',
      variables: {
        filename: wiz_upload_filename
      }
    }
  },
  data: {
    vuln: data?.vuln,
    wiz_payload: data?.wiz_payload,
    wiz_token: data?.wiz_token
  }
};
```

* **Response Script**:

```javascript
if (response?.statusCode !== 200) {
  if (secrets.logging_level === 'debug') {
    Logger.debug('WIZ upload URL request error: ', JSON.stringify(response));
  }
  return {
    decision: {
      status: 'abort',
      message: 'Failed to request WIZ upload URL. HTTP status: ' + (response?.statusCode || 'unknown')
    }
  };
}

const upload = response?.jsonBody?.data?.requestSecurityScanUpload?.upload;
if (!upload?.url) {
  return {
    decision: {
      status: 'abort',
      message: 'WIZ upload URL not found in response.'
    }
  };
}

if (secrets.logging_level === 'debug') {
  Logger.debug('WIZ upload URL obtained. Upload ID: ' + upload.id + ', Activity ID: ' + upload.systemActivityId);
}

return {
  decision: {
    status: 'continue',
    message: 'WIZ upload URL obtained successfully.'
  },
  data: {
    vuln: data?.vuln,
    wiz_payload: data?.wiz_payload,
    wiz_token: data?.wiz_token,
    wiz_upload_id: upload.id,
    wiz_upload_url: upload.url,
    wiz_system_activity_id: upload.systemActivityId
  }
};
```

**Action 5 - Upload Scan to WIZ S3 Bucket**

* **Method**: PUT
* **URL**: https\://{{preSignedUrl}}
* **Headers**:
  * Key = Content-Type; Type = Value; Value = application/json
* **Request Script**:

```javascript
const uploadUrl = data?.wiz_upload_url;
if (!uploadUrl) {
  return {
    decision: {
      status: 'abort',
      message: 'WIZ upload URL not found in data. Cannot upload enrichment payload.'
    }
  };
}

if (secrets.logging_level === 'debug') {
  Logger.debug('Uploading enrichment JSON to S3. Upload ID: ' + data.wiz_upload_id);
}

return {
  decision: {
    status: 'continue',
    message: 'Uploading enrichment JSON to WIZ S3 bucket.'
  },
  request: {
    url: uploadUrl,
    body: data?.wiz_payload
  },
  data: {
    vuln: data?.vuln,
    wiz_token: data?.wiz_token,
    wiz_upload_id: data?.wiz_upload_id,
    wiz_system_activity_id: data?.wiz_system_activity_id
  }
};
```

* **Response Script**:

```javascript
// S3 presigned PUT returns 200 on success
if (response?.statusCode !== 200) {
  if (secrets.logging_level === 'debug') {
    Logger.debug('S3 upload error: ', JSON.stringify(response));
  }
  return {
    decision: {
      status: 'abort',
      message: 'Failed to upload enrichment JSON to S3. HTTP status: ' + (response?.statusCode || 'unknown')
    }
  };
}

if (secrets.logging_level === 'debug') {
  Logger.debug('Enrichment JSON uploaded to S3 successfully. Upload ID: ' + data.wiz_upload_id);
  Logger.debug('response: ', JSON.stringify(response));
}

return {
  decision: {
    status: 'continue',
    message: 'Enrichment JSON uploaded to WIZ S3 bucket successfully.',
    delay: 3000
  },
  data: {
    vuln: data?.vuln,
    wiz_token: data?.wiz_token,
    wiz_system_activity_id: data?.wiz_system_activity_id
  }
};
```

**Action 6 - Check WIZ Upload Status**

* **Method**: POST
* **URL**: https\://{{wiz\_api\_host}}/graphql
* **Headers**:
  * Key = Content-Type; Type = Value; Value = application/json
  * Key = Accept; Type = Value; Value = application/json
* **Request Script**:

```javascript
const activityId = data?.wiz_system_activity_id;
if (!activityId) {
  return {
    decision: {
      status: 'abort',
      message: 'systemActivityId not found. Cannot check WIZ upload status.'
    }
  };
}

if (secrets.logging_level === 'debug') {
  Logger.debug('Checking WIZ upload status for systemActivityId: ' + activityId);
}

const query = 'query SystemActivity($id: ID!) { systemActivity(id: $id) { id status statusInfo result { ... on SystemActivityEnrichmentIntegrationResult { dataSources { ... IngestionStatsDetails } findings { ... IngestionStatsDetails } events { ... IngestionStatsDetails } tags { ... IngestionStatsDetails } } } context { ... on SystemActivityEnrichmentIntegrationContext { fileUploadId } } } } fragment IngestionStatsDetails on EnrichmentIntegrationStats { incoming handled }';

return {
  decision: {
    status: 'continue',
    message: 'Checking WIZ upload status for activity ' + activityId + '.'
  },
  request: {
    url: 'https://' + secrets.wiz_api_host + '/graphql',
    headers: {
      'Authorization': 'Bearer ' + data.wiz_token,
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    },
    body: {
      query: query,
      variables: {
        id: activityId
      }
    }
  },
  data: {
    vuln: data?.vuln,
    wiz_token: data?.wiz_token,
    wiz_system_activity_id: data?.wiz_system_activity_id
  }
};
```

* **Response Script**:

```javascript
if (response?.statusCode !== 200) {
  if (secrets.logging_level === 'debug') {
    Logger.debug('WIZ status check error: ', JSON.stringify(response));
  }
  return {
    decision: {
      status: 'abort',
      message: 'Failed to check WIZ upload status. HTTP status: ' + (response?.statusCode || 'unknown')
    }
  };
}

const activity = response?.jsonBody?.data?.systemActivity;
if (!activity) {
  return {
    decision: {
      status: 'abort',
      message: 'systemActivity not found in WIZ status response.'
    }
  };
}

const activityStatus = activity?.status || '';
const statusInfo = activity?.statusInfo || '';

if (secrets.logging_level === 'debug') {
  Logger.debug('WIZ activity status: ' + activityStatus + ' — ' + statusInfo);
  Logger.debug('WIZ activity result: ', JSON.stringify(activity?.result));
}

if (activityStatus === 'FAILURE') {
  return {
    decision: {
      status: 'abort',
      message: 'WIZ enrichment ingestion failed. Status: ' + activityStatus + ' — ' + statusInfo
    }
  };
}

// Success
if (activityStatus === 'SKIPPED' || activityStatus === 'SUCCESS'){
  // 
  return {
    decision: {
      status: 'continue',
      message: 'WIZ enrichment upload status: ' + activityStatus + '. Proceeding to mark vulnerability.'
    },
    data: {
      vuln: data?.vuln,
    }
  };
} 
else {
  return {
    decision:{
      status: 'repeat',
      message: 'Current Status: ' + activityStatus + '. Polling Check Upload Status again in 5 seconds...',
      delay: 5000
    },
    data: {
      vuln: data?.vuln,
      wiz_token: data?.wiz_token,
      wiz_system_activity_id: data?.wiz_system_activity_id
    }
  };
}
```

**Action 7 - Update Vuln - Mark Vuln as Exported to WIZ**

* **Method**: PUT
* **URL**: https\://{{af-tenant}}/api/ss/vulnerability/{id}
* **Headers**:
  * Key = Content-Type; Type = Value; Value = application/json
  * Key = X-SSAPI-KEY; Type = Secret; Value = af\_auth
* **Request Script**:

```javascript
const vuln = data?.vuln;
const vulnId = vuln?.vulnerability_id;
const projects = vuln?.vulnerability_projects;
// Assume first project as project
const projectId = projects?[0]?.id ;

if (!vulnId || !projectId) {
  if (secrets.logging_level === 'debug'){
    Logger.debug('vuln: ', vuln);
    Logger.debug('project: ', vuln?.vulnerability_projects);
  }
  return {
    decision: {
      status: 'abort',
      message: 'Vulnerability ID or Project ID not found. Cannot mark as exported to WIZ. Please check the log.'
    }
  };
}

return {
  decision: {
    status: 'continue',
    message: 'Updating vulnerability as exported to WIZ.'
  },
  request: {
    url: 'https://' + secrets.af_hostname + '/api/ss/vulnerability/' + vulnId,
    body: {
      project_id: projectId,
      custom_fields: [
        { 
          key: 'exported_to_wiz',
          value: 'Yes' 
        }
      ]
    }
  }
};
```

* **Response Script**:

```javascript
if (response?.statusCode !== 200) {
  if (secrets.logging_level === 'debug') {
    Logger.debug('Error marking vulnerability as exported: ', JSON.stringify(response));
  }
  return {
    decision: {
      status: 'abort',
      message: 'Failed to update vulnerability as exported to WIZ. HTTP status: ' + (response?.statusCode || 'unknown')
    }
  };
}

if (secrets.logging_level === 'debug') {
  Logger.debug('Vulnerability marked as exported to WIZ successfully.');
}

return {
  decision: {
    status: 'finish',
    message: 'Vulnerability exported to WIZ and marked as "exported_to_wiz" = "Yes".'
  }
};
```

## Export Vulnerability to WIZ on Vuln Updated

<figure><img src="/files/Wr4v6dGTKUon1OAugXvS" alt=""><figcaption></figcaption></figure>

The purpose of this example is to export a vulnerability to [WIZ](https://www.wiz.io) when a vulnerability is updated, if it has not yet been exported to WIZ.

This example Flow can be downloaded from our [Flows GitHub Repository](https://github.com/AttackForge/Flows) and imported into your AttackForge.

**Initial Set Up**

* **Event**: Vuln Updated
* **Secrets**:
  * af\_hostname - for example "demo.attackforge.com"
  * af\_key - your AttackForge user API key
  * logging\_level - set to "debug" if additional logging is required
  * wiz\_api\_host - for example "api.us17.app.wiz.io"
  * wiz\_client\_id - your WIZ Client Id
  * wiz\_client\_secret - your WIZ Client Secret
  * wiz\_integration\_id - your WIZ Intgration Id

**Action 1 - Validate Readiness for Export**

* **Script**:

```javascript
const vuln = data;

if (!vuln?.vulnerability_id) {
  return {
    decision: {
      status: 'abort',
      message: 'No vulnerability found in event data.'
    }
  };
};

// Check 1: Vuln is not deleted
const isDeleted = vuln?.vulnerability_is_deleted === true;
if (isDeleted) {
  return {
    decision: {
      status: 'finish',
      message: 'Vulnerability is deleted. Skipping WIZ export.'
    }
  };
};

// Check 2: Vuln is not pending
const isPending = vuln?.vulnerability_is_visible === false;
if (isPending) {
  return {
    decision: {
      status: 'finish',
      message: 'Vulnerability is pending. Skipping WIZ export.'
    }
  };
};

// Check 3: Vuln is not already exported to WIZ
const customFields = vuln?.vulnerability_custom_fields || [];
let exportedToWiz = false;
for (let i = 0; i < Array.length(customFields); i++) {
  const customField = customFields[i];
  if (customField.key === 'exported_to_wiz' && customField.value === 'Yes') {
    exportedToWiz = true;
  }
}
if (exportedToWiz) {
  return {
    decision: {
      status: 'finish',
      message: 'Vulnerability already exported to WIZ. Skipping.'
    }
  };
};

// Check 4  - Ensure all necessary asset fields exist
let asset_hostname;
let asset_port;
let asset_protocol;
let asset_name;
let asset_id;
let asset;
if (vuln?.vulnerability_affected_assets?[0]?.asset) {
  asset = vuln.vulnerability_affected_assets[0].asset;
}
if (!asset) {
  return {
    decision: {
      status: 'abort',
      message: 'Asset not found on vulnerability.'
    }
  };
}
if (asset.name) {
  asset_name = asset.name;
}
if (asset.id) {
  asset_id = asset.id;
}
if (asset.custom_fields && Array.isArray(asset.custom_fields)) {
  const custom_fields = asset.custom_fields;
  for (let i = 0; i < Array.length(custom_fields); i++) {
    const customField = custom_fields[i];
    if (customField.key === 'af_sys_hostnames' && customField.value?[0]) {
      asset_hostname = customField.value[0];
    }
    else if (customField.key === 'af_sys_ports' && customField.value?[0]) {
      asset_port = Number.parseInt(customField.value[0], 10);
    }
    else if (customField.key === 'protocols' && customField.value?[0]) {
      asset_protocol = customField.value[0];
    }
  }
}
if (!asset_id || !asset_name || !asset_hostname || !asset_port || !asset_protocol) {
  if (secrets.logging_level === 'debug'){
    Logger.debug('asset_id: ', asset_id);
    Logger.debug('asset_name: ', asset_name);
    Logger.debug('asset_hostname: ', asset_hostname);
    Logger.debug('asset_port: ', asset_port);
    Logger.debug('asset_protocol: ', asset_protocol);
  }
  return {
    deicision:{
      status: 'abort',
      message: 'Error: asset_id, asset_name, asset_hostname, asset_port, asset_protocol must all exist.'
    }
  };
}

if (secrets.logging_level === 'debug') {
  Logger.debug('Vulnerability ready for WIZ export: ', vuln.vulnerability_id);
}

return {
  decision: {
    status: 'continue',
    message: 'Vulnerability is ready for export to WIZ.'
  },
  data: {
    vuln: data,
    asset_id: asset_id,
    asset_name: asset_name,
    asset_hostname: asset_hostname,
    asset_port: asset_port,
    asset_protocol: asset_protocol
  }
};
```

**Action 2 - Build WIZ Payload**

* **Script**:

```javascript
const vuln = data?.vuln;
const vulnId = vuln?.vulnerability_id;
const payload = payloadGenerator(vuln);

if (secrets.logging_level === 'debug'){
  Logger.debug('payload built: ', JSON.stringify(payload));
}
return {
  decision:{
    status: 'continue',
    message: 'Successfully built JSON payload'
  },
  data: {
    vuln: vuln,
    wiz_payload: payload
  }
};

// Users can customise this to be multiple payload generation if needed.
function payloadGenerator(vuln){
  
  // Affected asset
  const endpoint = {
    assetId: data?.asset_id,
    assetName: data?.asset_name,
    host: data?.asset_hostname,
    port: data?.asset_port,
    protocol: data?.asset_protocol
  };

  // Severity mapping: AF → WIZ
  const severityMap = {
    'Critical': 'Critical',
    'High': 'High',
    'Medium': 'Medium',
    'Low': 'Low',
    'Info': 'None'
  };
  const afPriority = vuln?.vulnerability_priority || 'Low';
  const wizSeverity = severityMap[afPriority] || 'None';

  // description
  const description = (vuln?.vulnerability_description || '') + '\n\n' + (vuln?.vulnerability_attack_scenario || '');

  // assessmentDetails
  const affectedAssetsStr = '- ' + data?.asset_name + '\n';
  const notes = vuln?.vulnerability_notes || [];
  let notesStr = '';
  for (let i = 0; i < Array.length(notes); i++) {
    const noteText = notes[i]?.note || '';
    if (noteText) {
      notesStr = notesStr + noteText + '\n';
    }
  }
  const assessmentDetails = 'Affected Asset:\n\n' + (affectedAssetsStr || 'N/A')
    + '\nSteps to Reproduce:\n\n' + (vuln?.vulnerability_steps_to_reproduce || 'N/A')
    + '\n\nNotes:\n\n' + (notesStr || 'N/A');

  // Link to AttackForge vuln
  const projects = vuln?.vulnerability_projects || [];
  const projectId = (Array.length(projects) > 0) ? projects[0].id : '';
  const externalLink = 'https://' + secrets.af_hostname + '/projects/' + projectId + '/vulnerabilities/' + vulnId;

  // Stable datasource ID
  const datasourceId = secrets.wiz_datasource_id || ('attackforge-project-' + projectId);

  // Vulnerability type mapping
  const typeMap = {
    'Misconfiguration': 'Misconfiguration',
    'DAST': 'DAST',
    'SCA': 'SCA',
    'SAST': 'SAST',
    'IaC': 'IaC',
    'SecretDetection': 'SecretDetection',
    'ContainerScan': 'ContainerScan',
    'HostScan': 'HostScan'
  };
  const vulnType = 'DAST';
  const wizType = typeMap[vulnType] || 'DAST';

  const finding = {
    id: vulnId,
    name: vuln?.vulnerability_title || '',
    description: description,
    assessmentDetails: assessmentDetails,
    remediation: vuln?.vulnerability_remediation_recommendation || '',
    severity: wizSeverity,
    type: wizType,
    externalFindingLink: externalLink
  };  

  return {
    integrationId: secrets.wiz_integration_id,
    dataSources: [
      {
        id: datasourceId,
        analysisDate: vuln?.vulnerability_created || Date.datetime('now'),
        assets: [
          {
            details: {
              endpoint: endpoint
            },
            attackSurfaceFindings: [finding]
          }
        ]
      }
    ]
  };
}
```

**Action 3 - Get WIZ Access Token**

* **Method**: POST
* **URL**: <https://auth.app.wiz.io/oauth/token>
* **Headers**:
  * Key = Content-Type; Type = Value; Value = application/x-www-form-urlencoded
* **Request Script**:

```javascript
const req_body = 'grant_type=client_credentials&audience=wiz-api&client_id=' + secrets.wiz_client_id + '&client_secret=' + secrets.wiz_client_secret;

return {
  decision: {
    status: 'continue',
    message: 'Fetching WIZ access token.'
  },
  request: {
    body: 'grant_type=client_credentials&audience=wiz-api&client_id=' + secrets.wiz_client_id + '&client_secret=' + secrets.wiz_client_secret
  },
  data: {
    vuln: data?.vuln,
    wiz_payload: data?.wiz_payload
  }
};
```

* **Response Script**:

```javascript
if (response?.statusCode !== 200) {
  if (secrets.logging_level === 'debug') {
    Logger.debug('Error fetching WIZ token: ', JSON.stringify(response));
  }
  return {
    decision: {
      status: 'abort',
      message: 'Failed to get WIZ access token. HTTP status: ' + (response?.statusCode || 'unknown')
    }
  };
}

const token = response?.jsonBody?.access_token;
if (!token) {
  return {
    decision: {
      status: 'abort',
      message: 'WIZ access_token not found in token response.'
    }
  };
}

if (secrets.logging_level === 'debug') {
  Logger.debug('WIZ access token obtained successfully.');
}

return {
  decision: {
    status: 'continue',
    message: 'WIZ access token obtained.'
  },
  data: {
    vuln: data?.vuln,
    wiz_payload: data?.wiz_payload,
    wiz_token: token
  }
};
```

**Action 4 - Request WIZ Upload URL**

* **Method**: POST
* **URL**: https\://{{wiz\_api\_host}}/graphql
* **Headers**:
  * Key = Content-Type; Type = Value; Value = application/json
* **Request Script**:

```javascript
const vuln = data?.vuln;
const currTime = Date.datetime("now", "epoch");
const wiz_upload_filename = 'attackforge-vuln-' + currTime + '.json';

if (secrets.logging_level === 'debug') {
  Logger.debug('WIZ enrichment payload: ', JSON.stringify(payload));
}

return {
  decision: {
    status: 'continue',
    message: 'Requesting WIZ upload URL for vulnerability ' + vuln.vulnerability_id + '.'
  },
  request: {
    url: 'https://' + secrets.wiz_api_host + '/graphql',
    headers: {
      'Authorization': 'Bearer ' + data.wiz_token,
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    },
    body: {
      query: 'query RequestSecurityScanUpload($filename: String!) { requestSecurityScanUpload(filename: $filename) { upload { id url systemActivityId } } }',
      variables: {
        filename: wiz_upload_filename
      }
    }
  },
  data: {
    vuln: data?.vuln,
    wiz_payload: data?.wiz_payload,
    wiz_token: data?.wiz_token
  }
};
```

* **Response Script**:

```javascript
if (response?.statusCode !== 200) {
  if (secrets.logging_level === 'debug') {
    Logger.debug('WIZ upload URL request error: ', JSON.stringify(response));
  }
  return {
    decision: {
      status: 'abort',
      message: 'Failed to request WIZ upload URL. HTTP status: ' + (response?.statusCode || 'unknown')
    }
  };
}

const upload = response?.jsonBody?.data?.requestSecurityScanUpload?.upload;
if (!upload?.url) {
  return {
    decision: {
      status: 'abort',
      message: 'WIZ upload URL not found in response.'
    }
  };
}

if (secrets.logging_level === 'debug') {
  Logger.debug('WIZ upload URL obtained. Upload ID: ' + upload.id + ', Activity ID: ' + upload.systemActivityId);
}

return {
  decision: {
    status: 'continue',
    message: 'WIZ upload URL obtained successfully.'
  },
  data: {
    vuln: data?.vuln,
    wiz_payload: data?.wiz_payload,
    wiz_token: data?.wiz_token,
    wiz_upload_id: upload.id,
    wiz_upload_url: upload.url,
    wiz_system_activity_id: upload.systemActivityId
  }
};
```

**Action 5 - Upload Scan to WIZ S3 Bucket**

* **Method**: PUT
* **URL**: https\://{{preSignedUrl}}
* **Headers**:
  * Key = Content-Type; Type = Value; Value = application/json
* **Request Script**:

```javascript
const uploadUrl = data?.wiz_upload_url;
if (!uploadUrl) {
  return {
    decision: {
      status: 'abort',
      message: 'WIZ upload URL not found in data. Cannot upload enrichment payload.'
    }
  };
}

if (secrets.logging_level === 'debug') {
  Logger.debug('Uploading enrichment JSON to S3. Upload ID: ' + data.wiz_upload_id);
}

return {
  decision: {
    status: 'continue',
    message: 'Uploading enrichment JSON to WIZ S3 bucket.'
  },
  request: {
    url: uploadUrl,
    body: data?.wiz_payload
  },
  data: {
    vuln: data?.vuln,
    wiz_token: data?.wiz_token,
    wiz_upload_id: data?.wiz_upload_id,
    wiz_system_activity_id: data?.wiz_system_activity_id
  }
};
```

* **Response Script**:

```javascript
// S3 presigned PUT returns 200 on success
if (response?.statusCode !== 200) {
  if (secrets.logging_level === 'debug') {
    Logger.debug('S3 upload error: ', JSON.stringify(response));
  }
  return {
    decision: {
      status: 'abort',
      message: 'Failed to upload enrichment JSON to S3. HTTP status: ' + (response?.statusCode || 'unknown')
    }
  };
}

if (secrets.logging_level === 'debug') {
  Logger.debug('Enrichment JSON uploaded to S3 successfully. Upload ID: ' + data.wiz_upload_id);
  Logger.debug('response: ', JSON.stringify(response));
}

return {
  decision: {
    status: 'continue',
    message: 'Enrichment JSON uploaded to WIZ S3 bucket successfully.',
    delay: 3000
  },
  data: {
    vuln: data?.vuln,
    wiz_token: data?.wiz_token,
    wiz_system_activity_id: data?.wiz_system_activity_id
  }
};
```

**Action 6 - Check WIZ Upload Status**

* **Method**: POST
* **URL**: https\://{{wiz\_api\_host}}/graphql
* **Headers**:
  * Key = Content-Type; Type = Value; Value = application/json
  * Key = Accept; Type = Value; Value = application/json
* **Request Script**:

```javascript
const activityId = data?.wiz_system_activity_id;
if (!activityId) {
  return {
    decision: {
      status: 'abort',
      message: 'systemActivityId not found. Cannot check WIZ upload status.'
    }
  };
}

if (secrets.logging_level === 'debug') {
  Logger.debug('Checking WIZ upload status for systemActivityId: ' + activityId);
}

const query = 'query SystemActivity($id: ID!) { systemActivity(id: $id) { id status statusInfo result { ... on SystemActivityEnrichmentIntegrationResult { dataSources { ... IngestionStatsDetails } findings { ... IngestionStatsDetails } events { ... IngestionStatsDetails } tags { ... IngestionStatsDetails } } } context { ... on SystemActivityEnrichmentIntegrationContext { fileUploadId } } } } fragment IngestionStatsDetails on EnrichmentIntegrationStats { incoming handled }';

return {
  decision: {
    status: 'continue',
    message: 'Checking WIZ upload status for activity ' + activityId + '.'
  },
  request: {
    url: 'https://' + secrets.wiz_api_host + '/graphql',
    headers: {
      'Authorization': 'Bearer ' + data.wiz_token,
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    },
    body: {
      query: query,
      variables: {
        id: activityId
      }
    }
  },
  data: {
    vuln: data?.vuln,
    wiz_token: data?.wiz_token,
    wiz_system_activity_id: data?.wiz_system_activity_id
  }
};
```

* **Response Script**:

```javascript
if (response?.statusCode !== 200) {
  if (secrets.logging_level === 'debug') {
    Logger.debug('WIZ status check error: ', JSON.stringify(response));
  }
  return {
    decision: {
      status: 'abort',
      message: 'Failed to check WIZ upload status. HTTP status: ' + (response?.statusCode || 'unknown')
    }
  };
}

const activity = response?.jsonBody?.data?.systemActivity;
if (!activity) {
  return {
    decision: {
      status: 'abort',
      message: 'systemActivity not found in WIZ status response.'
    }
  };
}

const activityStatus = activity?.status || '';
const statusInfo = activity?.statusInfo || '';

if (secrets.logging_level === 'debug') {
  Logger.debug('WIZ activity status: ' + activityStatus + ' — ' + statusInfo);
  Logger.debug('WIZ activity result: ', JSON.stringify(activity?.result));
}

if (activityStatus === 'FAILURE') {
  return {
    decision: {
      status: 'abort',
      message: 'WIZ enrichment ingestion failed. Status: ' + activityStatus + ' — ' + statusInfo
    }
  };
}

// Success
if (activityStatus === 'SKIPPED' || activityStatus === 'SUCCESS'){
  // 
  return {
    decision: {
      status: 'continue',
      message: 'WIZ enrichment upload status: ' + activityStatus + '. Proceeding to mark vulnerability.'
    },
    data: {
      vuln: data?.vuln,
    }
  };
} 
else {
  return {
    decision:{
      status: 'repeat',
      message: 'Current Status: ' + activityStatus + '. Polling Check Upload Status again in 5 seconds...',
      delay: 5000
    },
    data: {
      vuln: data?.vuln,
      wiz_token: data?.wiz_token,
      wiz_system_activity_id: data?.wiz_system_activity_id
    }
  };
}
```

**Action 7 - Update Vuln - Mark Vuln as Exported to WIZ**

* **Method**: PUT
* **URL**: https\://{{af-tenant}}/api/ss/vulnerability/{id}
* **Headers**:
  * Key = Content-Type; Type = Value; Value = application/json
  * Key = X-SSAPI-KEY; Type = Secret; Value = af\_auth
* **Request Script**:

```javascript
const vuln = data?.vuln;
const vulnId = vuln?.vulnerability_id;
const projects = vuln?.vulnerability_projects;
// Assume first project as project
const projectId = projects?[0]?.id ;

if (!vulnId || !projectId) {
  if (secrets.logging_level === 'debug'){
    Logger.debug('vuln: ', vuln);
    Logger.debug('project: ', vuln?.vulnerability_projects);
  }
  return {
    decision: {
      status: 'abort',
      message: 'Vulnerability ID or Project ID not found. Cannot mark as exported to WIZ. Please check the log.'
    }
  };
}

return {
  decision: {
    status: 'continue',
    message: 'Updating vulnerability as exported to WIZ.'
  },
  request: {
    url: 'https://' + secrets.af_hostname + '/api/ss/vulnerability/' + vulnId,
    body: {
      project_id: projectId,
      custom_fields: [
        { 
          key: 'exported_to_wiz',
          value: 'Yes' 
        }
      ]
    }
  }
};
```

* **Response Script**:

```javascript
if (response?.statusCode !== 200) {
  if (secrets.logging_level === 'debug') {
    Logger.debug('Error marking vulnerability as exported: ', JSON.stringify(response));
  }
  return {
    decision: {
      status: 'abort',
      message: 'Failed to update vulnerability as exported to WIZ. HTTP status: ' + (response?.statusCode || 'unknown')
    }
  };
}

if (secrets.logging_level === 'debug') {
  Logger.debug('Vulnerability marked as exported to WIZ successfully.');
}

return {
  decision: {
    status: 'finish',
    message: 'Vulnerability exported to WIZ and marked as "exported_to_wiz" = "Yes".'
  }
};
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://support.attackforge.com/attackforge-enterprise/modules/flows/wiz.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
