From ada644668927e010fec128f4136eb3dce5c0db4f Mon Sep 17 00:00:00 2001 From: Danbi Wyman Date: Fri, 12 Jun 2026 12:00:30 -0400 Subject: [PATCH 01/10] feat(INT-2037): relax QID regex; add customType entity and option --- config/config.json | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/config/config.json b/config/config.json index bfbe776..b5929d2 100644 --- a/config/config.json +++ b/config/config.json @@ -12,7 +12,13 @@ "customTypes": [ { "key": "qid", - "regex": "(?:QID|qid):\\s*\\d{1,8}" + "regex": "(?:QID|qid)(?:\\s*:\\s*|\\s+)\\d{1,8}" + }, + { + "key": "customType", + "regex": "", + "editable": true, + "enabled": false } ], "defaultColor": "light-purple", @@ -92,6 +98,15 @@ "type": "text", "userCanEdit": false, "adminOnly": true + }, + { + "key": "customTypeValueRegex", + "name": "Custom Type Value Regex", + "description": "When the Custom Type entity type is enabled, this regex is used to extract the QID numeric value from the matched entity string. Leave blank to extract the last sequence of digits found in the match.", + "default": "", + "type": "text", + "userCanEdit": false, + "adminOnly": true } ], "reducer": { @@ -99,4 +114,4 @@ "file": "./reducers/details.json" } } -} \ No newline at end of file +} From 3b1eb7f1e8f5e53f8b843693c6f129a600513754 Mon Sep 17 00:00:00 2001 From: Danbi Wyman Date: Fri, 12 Jun 2026 12:01:01 -0400 Subject: [PATCH 02/10] chore: bump version to 3.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b93045b..fd39bc0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "qualys", - "version": "3.3.3", + "version": "3.4.0", "main": "./integration.js", "private": true, "license": "MIT", From 16fac20a1a383651aa15139efbee79996df5c10b Mon Sep 17 00:00:00 2001 From: Danbi Wyman Date: Fri, 12 Jun 2026 12:02:50 -0400 Subject: [PATCH 03/10] feat(INT-2037): fix QID extraction regex; add customType value extraction --- src/getLookupResults.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/getLookupResults.js b/src/getLookupResults.js index 254fb21..a8213b6 100644 --- a/src/getLookupResults.js +++ b/src/getLookupResults.js @@ -7,6 +7,25 @@ const queryKnowledgeBaseForAllEntities = require('./querying/queryKnowledgeBaseF const queryScanListForAllEntities = require('./querying/queryScanListForEntities'); const associateDataWithEntities = require('./associateDataWithEntities'); +const extractQidValue = (value) => { + const match = value.match(/(?:QID|qid)(?:\s*:\s*|\s+)(\d{1,8})/i); + return match ? match[1] : value.trim(); +}; + +const extractCustomTypeValue = (value, customTypeValueRegex) => { + if (customTypeValueRegex) { + try { + const re = new RegExp(customTypeValueRegex); + const match = value.match(re); + return match ? (match[1] !== undefined ? match[1] : match[0]) : value.trim(); + } catch (_) { + // fall through to default + } + } + const match = value.match(/\d+$/); + return match ? match[0] : value.trim(); +}; + const getLookupResults = async ( entities, options, @@ -16,10 +35,17 @@ const getLookupResults = async ( const entitiesWithCustomTypesSpecified = map(({ type, types, value, ...entity }) => { type = type === 'custom' ? flow(first, split('.'), last)(types) : type; + let resolvedValue = value; + if (type === 'qid') { + resolvedValue = extractQidValue(value); + } else if (type === 'customType') { + resolvedValue = extractCustomTypeValue(value, options.customTypeValueRegex && options.customTypeValueRegex.value); + } + return { ...entity, type, - value: type === 'qid' ? flow(split(':'), last, trim)(value) : value + value: resolvedValue }; }, entities); From 871bb4d8339a3b63bdc7727e87f489a2c91f7b90 Mon Sep 17 00:00:00 2001 From: Danbi Wyman Date: Fri, 12 Jun 2026 12:04:51 -0400 Subject: [PATCH 04/10] feat(INT-2037): add customType to QUERY_PATHS_BY_TYPE --- src/constants.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/constants.js b/src/constants.js index 4a0cfa8..12cb6c0 100644 --- a/src/constants.js +++ b/src/constants.js @@ -39,6 +39,8 @@ const QUERY_PATHS_BY_TYPE = { } }; +QUERY_PATHS_BY_TYPE.customType = QUERY_PATHS_BY_TYPE.qid; + const SEARCH_COLUMN_NAMES_BY_TYPE = { cve: ['title', 'category', 'diagnosis', 'solution', 'cves', 'vender_references'], qid: ['qid'], From 0760d0b0932526904740ff744e0b080244204cbd Mon Sep 17 00:00:00 2001 From: Danbi Wyman Date: Fri, 12 Jun 2026 12:08:46 -0400 Subject: [PATCH 05/10] feat(INT-2037): add validateCustomTypeValueRegex --- src/validateOptions.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/validateOptions.js b/src/validateOptions.js index 9eeb158..6bb2c9e 100644 --- a/src/validateOptions.js +++ b/src/validateOptions.js @@ -41,5 +41,21 @@ const validateUrlOption = (url, otherErrors = []) => { return otherErrors; }; +const validateCustomTypeValueRegex = (options, otherErrors = []) => { + const regexValue = options.customTypeValueRegex && options.customTypeValueRegex.value; + if (!regexValue || regexValue.trim() === '') return otherErrors; + + try { + new RegExp(regexValue); + } catch (_) { + return otherErrors.concat({ + key: 'customTypeValueRegex', + message: 'The Custom Type Regex is not a valid regular expression.' + }); + } + + return otherErrors; +}; + -module.exports = { validateStringOptions, validateUrlOption }; +module.exports = { validateStringOptions, validateUrlOption, validateCustomTypeValueRegex }; From 704bb036a5b129da6530a209c336de030756d4ac Mon Sep 17 00:00:00 2001 From: Danbi Wyman Date: Fri, 12 Jun 2026 12:11:27 -0400 Subject: [PATCH 06/10] feat(INT-2037): include customType entities in qid host detection query --- src/querying/queryHostDetectionListForAllEntities.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/querying/queryHostDetectionListForAllEntities.js b/src/querying/queryHostDetectionListForAllEntities.js index d7c4cb3..d759757 100644 --- a/src/querying/queryHostDetectionListForAllEntities.js +++ b/src/querying/queryHostDetectionListForAllEntities.js @@ -33,7 +33,7 @@ const queryHostDetectionListForAllEntities = async ( )(entities); const allHostDetectionResultForQids = await flow( - filter(flow(get('type'), eq('qid'))), + filter(flow(get('type'), or(eq('qid'), eq('customType')))), map(get('value')), cond([[size, queryHostDetectionList('qids', options, requestWithDefaults, Logger)]]) )(entities); From 76fcfcde49dbaf916a0a19619176b403cd08feb2 Mon Sep 17 00:00:00 2001 From: Danbi Wyman Date: Fri, 12 Jun 2026 12:11:54 -0400 Subject: [PATCH 07/10] feat(INT-2037): wire validateCustomTypeValueRegex into validateOptions --- integration.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/integration.js b/integration.js index 370d685..e785883 100644 --- a/integration.js +++ b/integration.js @@ -1,5 +1,5 @@ const createRequestWithDefaults = require('./src/createRequestWithDefaults'); -const { validateStringOptions, validateUrlOption } = require('./src/validateOptions'); +const { validateStringOptions, validateUrlOption, validateCustomTypeValueRegex } = require(./src/validateOptions.); const { parseErrorToReadableJSON } = require('./src/dataTransformations'); const { getLookupResults } = require('./src/getLookupResults'); const launchScan = require('./src/launchScan'); @@ -52,7 +52,8 @@ const validateOptions = async (options, callback) => { const urlValidationErrors = validateUrlOption(options.url.value); - const errors = stringValidationErrors.concat(urlValidationErrors); + const customTypeRegexErrors = validateCustomTypeValueRegex(options); + const errors = stringValidationErrors.concat(urlValidationErrors).concat(customTypeRegexErrors); callback(null, errors); }; From 1ca98133c6e879ec5f8472457b1cdf6013f3d979 Mon Sep 17 00:00:00 2001 From: Danbi Wyman Date: Fri, 12 Jun 2026 14:06:48 -0400 Subject: [PATCH 08/10] chore(INT-2037): sync package-lock.json version to 3.4.0 --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 65bbfa5..8d03adc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "qualys", - "version": "3.3.3", + "version": "3.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "qualys", - "version": "3.3.3", + "version": "3.4.0", "license": "MIT", "dependencies": { "lodash": "^4.18.1", From be99ae48c8ceaeff542a787dcfa62e10860009b5 Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 22 Jun 2026 13:26:54 -0400 Subject: [PATCH 09/10] PR feedback and other fixes --- README.md | 113 +++++++++++++----- config/config.json | 22 ++-- integration.js | 4 +- src/constants.js | 2 +- src/getLookupResults.js | 33 ++--- .../queryHostDetectionListForAllEntities.js | 2 +- src/validateOptions.js | 8 +- 7 files changed, 115 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index e257c9b..520605c 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,100 @@ # Polarity Qualys Integration - The Polarity Qualys Integration queries the Qualys Cloud Platform's Host Detection List for IP Addresses and QIDs. The Host Detections list will only get queried when searching for IP Addresses and QIDs, the host detection API does not enable other searching at this time. T - -> *NOTE:* QIDs can be searched onDemand by prefixing the QID with `QID: ` - -### How to Review Polarity - Qualys Integration -***Host Detections*** -* **Summary View** -> Number of Host Dections associated with QID or IP Address -* **Detail View** - * *Host Information* - * Asset ID -> Asset ID from Qualys - * Operating System -> Host OS - * DNS -> Host DNS/Domain - * Last Scan Information - * *Detections List* - * List of all detections associated with Host - -
- Host List Detections -
- -## About Qualys -The Qualys Cloud Platform helps businesses simplify security operations and lower the cost of compliance by delivering critical security intelligence on demand and automating the full spectrum of auditing, compliance and protection for IT systems and web applications. + +The Polarity Qualys Integration queries the Qualys Cloud Platform for IP addresses, domains, CVEs, and QIDs. Host detection results are returned for IP addresses and QIDs. CVE lookups query the Qualys KnowledgeBase and return matching vulnerability records along with any associated host detections. + +## About Qualys + +The Qualys Cloud Platform helps businesses simplify security operations and lower the cost of compliance by delivering critical security intelligence on demand and automating the full spectrum of auditing, compliance, and protection for IT systems and web applications. + To learn more about Qualys, visit the [official website](https://www.qualys.com/). +## Supported Entity Types + +| Entity Type | Description | +|---|---| +| IPv4 | Returns host detection list and scan history for the IP address | +| IPv6 | Returns host detection list for the IPv6 address | +| Domain | Returns host detection list for hosts matching the domain name | +| CVE | Returns KnowledgeBase vulnerability records matching the CVE and associated host detections | +| Qualys ID (QID) | Returns KnowledgeBase records and host detections for the matched QID | +| Custom QID Value | User-configured type for matching arbitrary strings and extracting a QID value | + +## QID Entity Type + +The built-in **Qualys ID (QID)** data type automatically recognizes QID strings in the following formats (case-insensitive): + +| Format | Example | +|---|---| +| `QID` (no separator) | `QID12345` | +| `QID:` | `QID:12345` | +| `QID: ` | `QID: 12345` | +| `QID : ` | `QID : 12345` | +| `QID-` | `QID-12345` | +| `QID_` | `QID_12345` | +| `QID ` | `QID 12345` | +| `qid:` (lowercase) | `qid:38623` | + +The numeric QID value is extracted automatically and used for the API lookup. + +## Custom QID Value Data Type + +The **Custom QID Value** data type is disabled by default. It is designed for environments where QID values appear embedded in custom string formats not covered by the built-in QID pattern (e.g., internal ticket references, asset tags, or CMDB identifiers). + +When enabled, Polarity will match text against the regex you configure in the **Custom QID Value Regex** option. The integration then extracts a numeric QID from the matched string and looks it up in Qualys. + +**To enable it:** +1. Go to **Integration Options → Qualys → Data Types** +2. Enable the **Custom QID Value** type and enter a regex pattern that matches your custom format +3. Optionally set the **Custom QID Value Regex** option to extract the QID number (see below) + ## Integration Limitations + ### Host Detection List Lookup Limits -Qualys' Host Detection List API only allows lookups on IP Addresses and QIDs, so only IP Addresses and QIDs will show Host Detection List results. +Qualys' Host Detection List API filters results by the query parameter. When searching by QID, only the detection entry for that specific QID is returned per host — not the host's full detection list. Searching by IP address returns all detections for that host. + +## Integration Options + +All options are admin-only and cannot be edited by regular users. -## Qualys Integration User Options ### Qualys URL -The URL of the Qualys you would like to connect to (including http:// or https://) +*(Required)* The base URL of your Qualys subscription, including the protocol (e.g., `https://qualysapi.qualys.com`). Do not include a trailing slash. ### Qualys Username -The Username for your Qualys Account +*(Required)* The username for your Qualys account. ### Qualys Password -The Password associated with the Qualys Account +*(Required)* The password associated with your Qualys account. + +### Enable Scan Launch +*(Default: disabled)* When enabled, a **Launch Scan** button appears in the Scans tab for IP address entities, allowing analysts to initiate a Qualys VM scan directly from Polarity. Requires the **Scan Option Profile** to be configured. + +### Scan Option Profile +The Qualys option profile title or numeric ID to use when launching scans (e.g., `Initial Options` or `43165`). Required when **Enable Scan Launch** is enabled. You can find the Scan Option Profile by navigating to the "Scans" page and then click on the "Option Profiles" tab. The "Title" column is the name of the scan option profile. Do not include the word `(default)` if selecting the default Scan Option Profile. + +### Scanner Appliance Name +The name of the scanner appliance to target when launching scans (e.g., `scanner1`). Leave blank to use the account's default scanner for the target IP. + +### Custom QID Value Regex +When the **Custom QID Value** data type is enabled, this regex is used to extract the numeric QID from the matched entity string. + +- If the regex contains a **capture group**, the first capture group's value is used as the QID (e.g., `TICKET-(\d+)` would extract `42` from `TICKET-42`). +- If the regex has **no capture group**, the full match is used as the QID. +- If left **blank**, the integration falls back to extracting the last contiguous sequence of digits found in the matched string (e.g., `ASSET-00038623` → `38623`). + +**Examples:** + +| Custom type regex (Data Types) | Custom QID Value Regex (option) | Matched string | Extracted QID | +|---|---|---|---| +| `TICKET-\d+` | `TICKET-(\d+)` | `TICKET-38623` | `38623` | +| `VULN#\d{4,8}` | *(blank)* | `VULN#12345` | `12345` | +| `asset-tag-\d+` | `(\d+)$` | `asset-tag-00091` | `00091` | ## Installation Instructions + Installation instructions for integrations are provided on the [PolarityIO GitHub Page](https://polarityio.github.io/). ## Polarity -Polarity is a memory-augmentation platform that improves and accelerates analyst decision making. For more information about the Polarity platform please see: -https://polarity.io/ \ No newline at end of file + +Polarity is a memory-augmentation platform that improves and accelerates analyst decision making. For more information about the Polarity platform please see: + +https://polarity.io/ diff --git a/config/config.json b/config/config.json index b5929d2..185aa9f 100644 --- a/config/config.json +++ b/config/config.json @@ -3,20 +3,26 @@ "name": "Qualys", "acronym": "QLS", "description": "The Polarity Qualys Integration queries the Qualys Cloud Platform's Host Detection List and KnowledgeBase for IP Addresses, IPv6 Addresses, Domains, CVEs, and QIDs.", - "entityTypes": [ + "dataTypes": [ "IPv4", "IPv6", "domain", - "cve" - ], - "customTypes": [ + "cve", { + "type": "custom", + "name": "Qualys ID (QID)", + "description": "Matches QID values prefixed with QID followed by a 1 to 8 digit number (e.g., QID1234, QID-1234, QID:1234, QID 1234)", "key": "qid", - "regex": "(?:QID|qid)(?:\\s*:\\s*|\\s+)\\d{1,8}" + "regex": "(?:QID|qid)(?:\\s*[:_-]\\s*|\\s*)\\d{1,8}", + "editable": false, + "enabled": true }, { - "key": "customType", - "regex": "", + "type": "custom", + "name": "Custom QID Value", + "description": "Match custom QID values and extract the numeric QID via the \"Custom QID Value Regex\" integration option", + "key": "customQid", + "regex": "(?:QID|qid)(?:\\s*[:_-]\\s*|\\s*)\\d{1,8}", "editable": true, "enabled": false } @@ -100,7 +106,7 @@ "adminOnly": true }, { - "key": "customTypeValueRegex", + "key": "customQidValueRegex", "name": "Custom Type Value Regex", "description": "When the Custom Type entity type is enabled, this regex is used to extract the QID numeric value from the matched entity string. Leave blank to extract the last sequence of digits found in the match.", "default": "", diff --git a/integration.js b/integration.js index e785883..d0dc7ca 100644 --- a/integration.js +++ b/integration.js @@ -1,5 +1,5 @@ const createRequestWithDefaults = require('./src/createRequestWithDefaults'); -const { validateStringOptions, validateUrlOption, validateCustomTypeValueRegex } = require(./src/validateOptions.); +const { validateStringOptions, validateUrlOption, validateCustomQidValueRegex } = require('./src/validateOptions'); const { parseErrorToReadableJSON } = require('./src/dataTransformations'); const { getLookupResults } = require('./src/getLookupResults'); const launchScan = require('./src/launchScan'); @@ -52,7 +52,7 @@ const validateOptions = async (options, callback) => { const urlValidationErrors = validateUrlOption(options.url.value); - const customTypeRegexErrors = validateCustomTypeValueRegex(options); + const customTypeRegexErrors = validateCustomQidValueRegex(options); const errors = stringValidationErrors.concat(urlValidationErrors).concat(customTypeRegexErrors); callback(null, errors); diff --git a/src/constants.js b/src/constants.js index 12cb6c0..10bfb6e 100644 --- a/src/constants.js +++ b/src/constants.js @@ -39,7 +39,7 @@ const QUERY_PATHS_BY_TYPE = { } }; -QUERY_PATHS_BY_TYPE.customType = QUERY_PATHS_BY_TYPE.qid; +QUERY_PATHS_BY_TYPE.customQid = { ...QUERY_PATHS_BY_TYPE.qid }; const SEARCH_COLUMN_NAMES_BY_TYPE = { cve: ['title', 'category', 'diagnosis', 'solution', 'cves', 'vender_references'], diff --git a/src/getLookupResults.js b/src/getLookupResults.js index a8213b6..d07fa60 100644 --- a/src/getLookupResults.js +++ b/src/getLookupResults.js @@ -8,14 +8,14 @@ const queryScanListForAllEntities = require('./querying/queryScanListForEntities const associateDataWithEntities = require('./associateDataWithEntities'); const extractQidValue = (value) => { - const match = value.match(/(?:QID|qid)(?:\s*:\s*|\s+)(\d{1,8})/i); + const match = value.match(/(?:QID|qid)(?:\s*[:\-_]\s*|\s*)(\d{1,8})/i); return match ? match[1] : value.trim(); }; -const extractCustomTypeValue = (value, customTypeValueRegex) => { - if (customTypeValueRegex) { +const extractCustomQidValue = (value, customQidValueRegex) => { + if (customQidValueRegex) { try { - const re = new RegExp(customTypeValueRegex); + const re = new RegExp(customQidValueRegex); const match = value.match(re); return match ? (match[1] !== undefined ? match[1] : match[0]) : value.trim(); } catch (_) { @@ -26,20 +26,15 @@ const extractCustomTypeValue = (value, customTypeValueRegex) => { return match ? match[0] : value.trim(); }; -const getLookupResults = async ( - entities, - options, - requestWithDefaults, - Logger -) => { +const getLookupResults = async (entities, options, requestWithDefaults, Logger) => { const entitiesWithCustomTypesSpecified = map(({ type, types, value, ...entity }) => { type = type === 'custom' ? flow(first, split('.'), last)(types) : type; let resolvedValue = value; if (type === 'qid') { resolvedValue = extractQidValue(value); - } else if (type === 'customType') { - resolvedValue = extractCustomTypeValue(value, options.customTypeValueRegex && options.customTypeValueRegex.value); + } else if (type === 'customQid') { + resolvedValue = extractCustomQidValue(value, options.customQidValueRegex?.value); } return { @@ -53,12 +48,7 @@ const getLookupResults = async ( entitiesWithCustomTypesSpecified ); - const data = await getData( - entitiesPartition, - options, - requestWithDefaults, - Logger - ); + const data = await getData(entitiesPartition, options, requestWithDefaults, Logger); const foundEntities = associateDataWithEntities(entitiesPartition, data, Logger); const lookupResults = createLookupResults(foundEntities, options, Logger); @@ -66,12 +56,7 @@ const getLookupResults = async ( return lookupResults.concat(ignoredIpLookupResults); }; -const getData = async ( - entitiesPartition, - options, - requestWithDefaults, - Logger -) => { +const getData = async (entitiesPartition, options, requestWithDefaults, Logger) => { // Sequential queries — Qualys enforces concurrent call limits per subscription tier const allHostDetections = uniqBy( 'id', diff --git a/src/querying/queryHostDetectionListForAllEntities.js b/src/querying/queryHostDetectionListForAllEntities.js index d759757..257ccb9 100644 --- a/src/querying/queryHostDetectionListForAllEntities.js +++ b/src/querying/queryHostDetectionListForAllEntities.js @@ -33,7 +33,7 @@ const queryHostDetectionListForAllEntities = async ( )(entities); const allHostDetectionResultForQids = await flow( - filter(flow(get('type'), or(eq('qid'), eq('customType')))), + filter(flow(get('type'), or(eq('qid'), eq('customQid')))), map(get('value')), cond([[size, queryHostDetectionList('qids', options, requestWithDefaults, Logger)]]) )(entities); diff --git a/src/validateOptions.js b/src/validateOptions.js index 6bb2c9e..14fc0f6 100644 --- a/src/validateOptions.js +++ b/src/validateOptions.js @@ -41,15 +41,15 @@ const validateUrlOption = (url, otherErrors = []) => { return otherErrors; }; -const validateCustomTypeValueRegex = (options, otherErrors = []) => { - const regexValue = options.customTypeValueRegex && options.customTypeValueRegex.value; +const validateCustomQidValueRegex = (options, otherErrors = []) => { + const regexValue = options.customQidValueRegex && options.customQidValueRegex.value; if (!regexValue || regexValue.trim() === '') return otherErrors; try { new RegExp(regexValue); } catch (_) { return otherErrors.concat({ - key: 'customTypeValueRegex', + key: 'customQidValueRegex', message: 'The Custom Type Regex is not a valid regular expression.' }); } @@ -58,4 +58,4 @@ const validateCustomTypeValueRegex = (options, otherErrors = []) => { }; -module.exports = { validateStringOptions, validateUrlOption, validateCustomTypeValueRegex }; +module.exports = { validateStringOptions, validateUrlOption, validateCustomQidValueRegex }; From d7cb12310bacc6e56a4112d23c362e3e55abce8b Mon Sep 17 00:00:00 2001 From: Ed Date: Mon, 22 Jun 2026 13:56:45 -0400 Subject: [PATCH 10/10] Fix scans --- components/block.js | 52 ++++++++++++++++++++++++++++----------------- integration.js | 12 +++++++++-- src/launchScan.js | 3 ++- styles/styles.less | 2 +- 4 files changed, 46 insertions(+), 23 deletions(-) diff --git a/components/block.js b/components/block.js index c7dfa21..225963b 100644 --- a/components/block.js +++ b/components/block.js @@ -65,17 +65,26 @@ polarity.export = PolarityComponent.extend({ this.set('scanState', ''); this.set('scanSubState', ''); this.set('scanDuration', ''); - this.get('block').notifyPropertyChange('data'); - this.sendIntegrationMessage({ action: 'LAUNCH_SCAN', entityValue }, (err, result) => { - this.set('isScanLaunching', false); - if (err) { - this.set('scanLaunchError', err.detail || 'Scan launch failed. Check Polarity logs.'); - } else { - this.set('scanRef', (result && result.scanRef) || ''); - } - this.get('block').notifyPropertyChange('data'); - }); + this.sendIntegrationMessage({ action: 'LAUNCH_SCAN', entityValue }) + .then((result) => { + // Sanitize scanRef: extract just the scan/XXXXX.XXXXX portion to handle + // xml2js charkey collision that can prefix the value with garbage characters. + const rawScanRef = result.scanRef || ''; + const scanRefMatch = rawScanRef.match(/scan\/\d+\.\d+/); + const scanRef = scanRefMatch ? scanRefMatch[0] : rawScanRef.trim(); + this.set('scanRef', scanRef); + }) + .catch((err) => { + this.set( + 'scanLaunchError', + err.detail || 'Scan launch failed. Check Polarity logs.' + ); + }) + .finally(() => { + this.set('isScanLaunching', false); + this.get('block').notifyPropertyChange('data'); + }); }, checkScanStatus: function () { @@ -87,18 +96,23 @@ polarity.export = PolarityComponent.extend({ this.set('isCheckingStatus', true); this.get('block').notifyPropertyChange('data'); - this.sendIntegrationMessage({ action: 'CHECK_SCAN_STATUS', scanRef }, (err, result) => { - this.set('isCheckingStatus', false); - if (err) { - this.set('scanLaunchError', err.detail || 'Status check failed. Check Polarity logs.'); - this.set('scanState', ''); - } else { + this.sendIntegrationMessage({ action: 'CHECK_SCAN_STATUS', scanRef }) + .then((result) => { this.set('scanState', (result && result.state) || 'Unknown'); this.set('scanSubState', (result && result.subState) || ''); this.set('scanDuration', (result && result.duration) || ''); - } - this.get('block').notifyPropertyChange('data'); - }); + }) + .catch((err) => { + this.set( + 'scanLaunchError', + err.detail || 'Status check failed. Check Polarity logs.' + ); + this.set('scanState', ''); + }) + .finally(() => { + this.get('block').notifyPropertyChange('data'); + this.set('isCheckingStatus', false); + }); }, toggleExpandableTitle: function ( displayFieldIndex, diff --git a/integration.js b/integration.js index d0dc7ca..8b4018b 100644 --- a/integration.js +++ b/integration.js @@ -1,5 +1,9 @@ const createRequestWithDefaults = require('./src/createRequestWithDefaults'); -const { validateStringOptions, validateUrlOption, validateCustomQidValueRegex } = require('./src/validateOptions'); +const { + validateStringOptions, + validateUrlOption, + validateCustomQidValueRegex +} = require('./src/validateOptions'); const { parseErrorToReadableJSON } = require('./src/dataTransformations'); const { getLookupResults } = require('./src/getLookupResults'); const launchScan = require('./src/launchScan'); @@ -53,7 +57,9 @@ const validateOptions = async (options, callback) => { const urlValidationErrors = validateUrlOption(options.url.value); const customTypeRegexErrors = validateCustomQidValueRegex(options); - const errors = stringValidationErrors.concat(urlValidationErrors).concat(customTypeRegexErrors); + const errors = stringValidationErrors + .concat(urlValidationErrors) + .concat(customTypeRegexErrors); callback(null, errors); }; @@ -69,6 +75,7 @@ const onMessage = async (payload, options, cb) => { requestWithDefaults, Logger ); + Logger.debug({ scanResult: result }, 'Scan Result'); return cb(null, result); } @@ -79,6 +86,7 @@ const onMessage = async (payload, options, cb) => { requestWithDefaults, Logger ); + Logger.debug({ scanStatus: result }, 'Scan Status'); return cb(null, result); } diff --git a/src/launchScan.js b/src/launchScan.js index 4ae3369..f767357 100644 --- a/src/launchScan.js +++ b/src/launchScan.js @@ -52,9 +52,10 @@ const launchScan = async (entityValue, options, requestWithDefaults, Logger) => 'body', await requestWithDefaults({ method: 'POST', - url: `${options.url}/api/2.0/fo/scan/`, + url: `${options.url}/api/3.0/fo/scan/`, form: formData, headers: { 'X-Requested-With': 'Polarity' }, + json: false, options }) ); diff --git a/styles/styles.less b/styles/styles.less index b846ade..4c6423a 100644 --- a/styles/styles.less +++ b/styles/styles.less @@ -140,7 +140,7 @@ hr { .qls-scan-ref-code { font-size: 10px; - color: #b0b0b0; + color: #000; background: rgba(0, 0, 0, 0.25); padding: 1px 4px; border-radius: 3px;