IPQualityScore
Proactively Prevent Fraud.
Data Enrichment & Threat Intelligence · IPQualityScore (IPQS) Threat Risk Scoring
Details
| ID | IPQualityScore |
|---|---|
| Provider | IPQualityScore |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10230415 |
| Supported Modules | Agentix XSIAM |
README
Overview
IPQualityScore (IPQS) provides enterprise-grade real-time threat intelligence for IP addresses, email addresses, URLs, phone numbers, and files. Detect fraud, phishing, malware, leaked credentials, and abusive behavior using IPQS’s global threat network.
This integration was integrated and tested with version 1.0 of IPQualityScore.
Getting an API Key
- Register for a free account at https://www.ipqualityscore.com/create-account. Free accounts include 5,000 API lookups per month.
- After registering, log in and visit https://www.ipqualityscore.com/documentation/proxy-detection/overview. Your API key is listed under the Private Key subheading.
Configure IPQualityScore in Cortex
| Parameter | Description | Required |
|---|---|---|
| API Key | Your IPQS private API key. | True |
| Trust any certificate (not secure) | Skip SSL certificate verification. | False |
| Use system proxy settings | Route requests through the system proxy. | False |
| IP Suspicious Score Threshold | Threshold for fraud score from IP lookup to be marked as suspicious. | False |
| IP Malicious Score Threshold | Threshold for fraud score from IP lookup to be marked as malicious. | False |
| Email Suspicious Score Threshold | Threshold for fraud score from Email lookup to be marked as suspicious. | False |
| Email Malicious Score Threshold | Threshold for fraud score from Email lookup to be marked as malicious. | False |
| Url Suspicious Score Threshold | Threshold for fraud score from Url lookup to be marked as suspicious. | False |
| Url Malicious Score Threshold | Threshold for fraud score from Url lookup to be marked as malicious. | False |
| Phone Suspicious Score Threshold | Threshold for fraud score from Phone lookup to be marked as suspicious. | False |
| Phone Malicious Score Threshold | Threshold for fraud score from Phone lookup to be marked as malicious. | False |
| Source Reliability | Reliability of the source providing the intelligence data. | True |
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
ipqs-ip-reputation
Runs reputation on IPs.
Base Command
ipqs-ip-reputation
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | IP address(es) to check. Supports comma-separated list. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| IPQualityScore.IP.success | Boolean | Was the request successful? |
| IPQualityScore.IP.message | String | A generic status message, either success or some form of an error notice. |
| IPQualityScore.IP.fraud_score | Number | The overall fraud score of the user based on the IP, user agent, language, and any other optionally passed variables. Fraud Scores >= 75 are suspicious, but not necessarily fraudulent. We recommend flagging or blocking traffic with Fraud Scores >= 85, but you may find it beneficial to use a higher or lower threshold. |
| IPQualityScore.IP.country_code | String | Two character country code of IP address or “N/A” if unknown. |
| IPQualityScore.IP.region | String | Region (state) of IP address if available or “N/A” if unknown. |
| IPQualityScore.IP.city | String | City of IP address if available or “N/A” if unknown. |
| IPQualityScore.IP.ISP | String | ISP if one is known. Otherwise “N/A”. |
| IPQualityScore.IP.ASN | Number | Autonomous System Number if one is known. Null if nonexistent. |
| IPQualityScore.IP.organization | String | Organization if one is known. Can be parent company or sub company of the listed ISP. Otherwise “N/A”. |
| IPQualityScore.IP.latitude | Number | Latitude of IP address if available or “N/A” if unknown. |
| IPQualityScore.IP.longitude | Number | Longitude of IP address if available or “N/A” if unknown. |
| IPQualityScore.IP.is_crawler | Boolean | Is this IP associated with being a confirmed crawler from a mainstream search engine such as Googlebot, Bingbot, Yandex, etc. based on hostname or IP address verification. |
| IPQualityScore.IP.timezone | String | Timezone of IP address if available or “N/A” if unknown. |
| IPQualityScore.IP.mobile | Boolean | Is this user agent a mobile browser? (will always be false if the user agent is not passed in the API request) |
| IPQualityScore.IP.host | String | Hostname of the IP address if one is available. |
| IPQualityScore.IP.proxy | Boolean | Is this IP address suspected to be a proxy? (SOCKS, Elite, Anonymous, VPN, Tor, etc.) |
| IPQualityScore.IP.vpn | Boolean | Is this IP suspected of being a VPN connection? This can include data center ranges which can become active VPNs at any time. The “proxy” status will always be true when this value is true. |
| IPQualityScore.IP.tor | Boolean | Is this IP suspected of being a TOR connection? This can include previously active TOR nodes and exits which can become active TOR exits at any time. The “proxy” status will always be true when this value is true. |
| IPQualityScore.IP.active_vpn | Boolean | Premium Account Feature - Identifies active VPN connections used by popular VPN services and private VPN servers. |
| IPQualityScore.IP.active_tor | Boolean | Premium Account Feature - Identifies active TOR exits on the TOR network. |
| IPQualityScore.IP.recent_abuse | Boolean | This value will indicate if there has been any recently verified abuse across our network for this IP address. Abuse could be a confirmed chargeback, compromised device, fake app install, or similar malicious behavior within the past few days. |
| IPQualityScore.IP.bot_status | Boolean | Premium Account Feature - Indicates if bots or non-human traffic has recently used this IP address to engage in automated fraudulent behavior. Provides stronger confidence that the IP address is suspicious. |
| IPQualityScore.IP.connection_type | String | Classification of the IP address connection type as “Residential”, “Corporate”, “Education”, “Mobile”, or “Data Center”. |
| IPQualityScore.IP.abuse_velocity | String | Premium Account Feature - How frequently the IP address is engaging in abuse across the IPQS threat network. Values can be “high”, “medium”, “low”, or “none”. Can be used in combination with the Fraud Score to identify bad behavior. |
| IPQualityScore.IP.request_id | String | A unique identifier for this request that can be used to lookup the request details or send a postback conversion notice. |
| IPQualityScore.IP.address | String | The IP address that was queried. |
| DBotScore.Indicator | String | The indicator that was tested. |
| DBotScore.Score | Number | The actual score. |
| DBotScore.Type | String | The indicator type. |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
| IP.Address | string | IP address |
| IP.ASN | string | The autonomous system name for the IP address, for example: “AS8948”. |
| IP.Hostname | string | The hostname that is mapped to this IP address. |
| IP.Geo.Country | string | The country in which the IP address is located. |
| IP.Geo.Description | string | Additional information about the location. |
| IP.Malicious.Vendor | string | The vendor reporting the IP address as malicious. |
| IP.Malicious.Description | string | A description explaining why the IP address was reported as malicious. |
Command Example
!ipqs-ip-reputation ip="8.8.8.8"
Context Example
{
"DBotScore": {
"Indicator": "8.8.8.8",
"Reliability": "A - Completely reliable",
"Score": 3,
"Type": "ip",
"Vendor": "IPQualityScore"
},
"IP": {
"ASN": 15169,
"Address": "8.8.8.8",
"Geo": {
"Country": "US",
"Location": "37.39:-122.07"
},
"Hostname": "dns.google",
"Malicious": {
"Description": null,
"Vendor": "IPQualityScore"
}
},
"IPQualityScore": {
"IP": {
"ASN": 15169,
"ISP": "Google",
"Malicious": {
"Vendor": "IPQualityScore"
},
"abuse_velocity": "high",
"active_tor": false,
"active_vpn": false,
"address": "8.8.8.8",
"bot_status": true,
"city": "Mountain View",
"connection_type": "Data Center",
"country_code": "US",
"fraud_score": 100,
"host": "dns.google",
"is_crawler": false,
"latitude": 37.39,
"longitude": -122.07,
"message": "Success",
"mobile": false,
"organization": "Google",
"proxy": true,
"recent_abuse": true,
"region": "California",
"request_id": "4DpK9WpOZGPFSPg",
"success": true,
"timezone": "America/Los_Angeles",
"tor": false,
"vpn": true
}
}
}
Human Readable Output
IPQualityScore Results for 8.8.8.8
success message fraud_score country_code region city ISP ASN organization latitude longitude is_crawler timezone mobile host proxy vpn tor active_vpn active_tor recent_abuse bot_status connection_type abuse_velocity request_id address true Success 100 US California Mountain View 15169 37.39 -122.07 false America/Los_Angeles false dns.google true true false false false true true Data Center high 4DpK9WpOZGPFSPg 8.8.8.8
ip
Deprecated. Use ipqs-ip-reputation instead.
Base Command
ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | IP address(es) to check. Supports comma-separated list. | Required |
Command Example
!ip ip="8.8.8.8"
ipqs-email-reputation
Runs reputation on email addresses.
Base Command
ipqs-email-reputation
Input
| Argument Name | Description | Required |
|---|---|---|
| Email address(es) to check. Supports comma-separated list. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| IPQualityScore.Email.message | String | A generic status message, either success or some form of an error notice. |
| IPQualityScore.Email.success | Boolean | Was the request successful? |
| IPQualityScore.Email.valid | Boolean | Does this email address appear valid? |
| IPQualityScore.Email.disposable | Boolean | Is this email suspected of belonging to a temporary or disposable mail service? Usually associated with fraudsters and scammers. |
| IPQualityScore.Email.smtp_score | Number | Validity score of email server’s SMTP setup. Range: “-1” - “3”. Scores above “-1” can be associated with a valid email. -1 = invalid email address 0 = mail server exists, but is rejecting all mail 1 = mail server exists, but is showing a temporary error 2 = mail server exists, but accepts all email 3 = mail server exists and has verified the email address |
| IPQualityScore.Email.overall_score | Number | Overall email validity score. Range: “0” - “4”. Scores above “1” can be associated with a valid email. 0 = invalid email address 1 = dns valid, unreachable mail server 2 = dns valid, temporary mail rejection error 3 = dns valid, accepts all mail 4 = dns valid, verified email exists |
| IPQualityScore.Email.first_name | String | Suspected first name based on email. Returns “CORPORATE” if the email is suspected of being a generic company email. Returns “UNKNOWN” if the first name was not determinable. |
| IPQualityScore.Email.generic | Boolean | Is this email suspected as being a catch all or shared email for a domain? (“admin@”, “webmaster@”, “newsletter@”, “sales@”, “contact@”, etc.) |
| IPQualityScore.Email.common | Boolean | Is this email from a common email provider? (“gmail.com”, “yahoo.com”, “hotmail.com”, etc.) |
| IPQualityScore.Email.dns_valid | Boolean | Does the email’s hostname have valid DNS entries? Partial indication of a valid email. |
| IPQualityScore.Email.honeypot | Boolean | Is this email believed to be a “honeypot” or “SPAM trap”? Bulk mail sent to these emails increases your risk of being added to block lists by large ISPs & ending up in the spam folder. |
| IPQualityScore.Email.deliverability | String | How likely is this email to be delivered to the user and land in their mailbox. Values can be “high”, “medium”, or “low”. |
| IPQualityScore.Email.frequent_complainer | Boolean | Indicates if this email frequently unsubscribes from marketing lists or reports email as SPAM. |
| IPQualityScore.Email.spam_trap_score | String | Confidence level of the email address being an active SPAM trap. Values can be “high”, “medium”, “low”, or “none”. We recommend scrubbing emails with “high” or “medium” statuses. Avoid “low” emails whenever possible for any promotional mailings. |
| IPQualityScore.Email.catch_all | Boolean | Is this email likely to be a “catch all” where the mail server verifies all emails tested against it as valid? It is difficult to determine if the address is truly valid in these scenarios, since the email’s server will not confirm the account’s status. |
| IPQualityScore.Email.timed_out | Boolean | Did the connection to the mail service provider timeout during the verification? If so, we recommend increasing the “timeout” variable above the default 7 second value. Lookups that timeout with a “valid” result as false are most likely false and should be not be trusted. |
| IPQualityScore.Email.suspect | Boolean | This value indicates if the mail server is currently replying with a temporary error and unable to verify the email address. This status will also be true for “catch all” email addresses as defined below. If this value is true, then we suspect the “valid” result may be tainted and there is not a guarantee that the email address is truly valid. |
| IPQualityScore.Email.recent_abuse | Boolean | This value will indicate if there has been any recently verified abuse across our network for this email address. Abuse could be a confirmed chargeback, fake signup, compromised device, fake app install, or similar malicious behavior within the past few days. |
| IPQualityScore.Email.fraud_score | Number | The overall Fraud Score of the user based on the email’s reputation and recent behavior across the IPQS threat network. Fraud Scores >= 75 are suspicious, but not necessarily fraudulent. |
| IPQualityScore.Email.suggested_domain | String | Default value is “N/A”. Indicates if this email’s domain should in fact be corrected to a popular mail service. This field is useful for catching user typos. For example, an email address with “gmai.com”, would display a suggested domain of “gmail.com”. This feature supports all major mail service providers. |
| IPQualityScore.Email.leaked | Boolean | Was this email address associated with a recent database leak from a third party? Leaked accounts pose a risk as they may have become compromised during a database breach. |
| IPQualityScore.Email.domain_age.human | Date | A human description of when this domain was registered. (Ex: 3 months ago) |
| IPQualityScore.Email.domain_age.timestamp | Number | The unix time since epoch when this domain was first registered. (Ex: 1568061634) |
| IPQualityScore.Email.domain_age.iso | Date | The time this domain was registered in ISO8601 format (Ex: 2019-09-09T16:40:34-04:00) |
| IPQualityScore.Email.first_seen.human | Date | A human description of the email address age, using an estimation of the email creation date when IPQS first discovered this email address. (Ex: 3 months ago) |
| IPQualityScore.Email.first_seen.timestamp | Number | The unix time since epoch when this email was first analyzed by IPQS. (Ex: 1568061634) |
| IPQualityScore.Email.first_seen.iso | Date | The time this email was first analyzed by IPQS in ISO8601 format (Ex: 2019-09-09T16:40:34-04:00) |
| IPQualityScore.Email.sanitized_email | String | Sanitized email address with all aliases and masking removed, such as multiple periods for Gmail.com. |
| IPQualityScore.Email.request_id | String | A unique identifier for this request that can be used to lookup the request details or send a postback conversion notice. |
| IPQualityScore.Email.address | String | The email address that was queried |
| DBotScore.Indicator | String | The indicator that was tested. |
| DBotScore.Score | Number | The actual score. |
| DBotScore.Type | String | The indicator type. |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
Command Example
!ipqs-email-reputation email="noreply@example.com"
Context Example
{
"DBotScore": {
"Indicator": "noreply@ipqualityscore.com",
"Reliability": "A - Completely reliable",
"Score": 3,
"Type": "email",
"Vendor": "IPQualityScore"
},
"Email": {
"Address": "noreply@ipqualityscore.com",
"Domain": "ipqualityscore.com"
},
"IPQualityScore": {
"Email": {
"Malicious": {
"Vendor": "IPQualityScore"
},
"address": "noreply@ipqualityscore.com",
"catch_all": true,
"common": false,
"deliverability": "low",
"disposable": false,
"dns_valid": true,
"domain_age": {
"human": "10 years ago",
"iso": "2011-04-14T23:26:37-04:00",
"timestamp": 1302837997
},
"first_name": "Corporate",
"first_seen": {
"human": "2 years ago",
"iso": "2019-05-06T02:09:06-04:00",
"timestamp": 1557122946
},
"fraud_score": 95,
"frequent_complainer": false,
"generic": true,
"honeypot": true,
"leaked": false,
"message": "Success.",
"overall_score": 3,
"recent_abuse": true,
"request_id": "4DpK9WpOZGQDnr7",
"sanitized_email": "noreply@ipqualityscore.com",
"smtp_score": 2,
"spam_trap_score": "medium",
"success": true,
"suggested_domain": "N/A",
"suspect": true,
"timed_out": false,
"valid": true
}
}
}
Human Readable Output
IPQualityScore Results for noreply@ipqualityscore.com
message success valid disposable smtp_score overall_score first_name generic common dns_valid honeypot deliverability frequent_complainer spam_trap_score catch_all timed_out suspect recent_abuse fraud_score suggested_domain leaked domain_age first_seen sanitized_email request_id address Success. true true false 2 3 Corporate true false true true low false medium true false true true 95 N/A false human: 10 years ago
timestamp: 1302837997
iso: 2011-04-14T23:26:37-04:00human: 2 years ago
timestamp: 1557122946
iso: 2019-05-06T02:09:06-04:00noreply@ipqualityscore.com 4DpK9WpOZGQDnr7 noreply@ipqualityscore.com
Deprecated. Use ipqs-email-reputation instead.
Base Command
email
Input
| Argument Name | Description | Required |
|---|---|---|
| Email address(es) to check. Supports comma-separated list. | Required |
Command Example
!email email="noreply@example.com"
ipqs-url-reputation
Runs reputation on URLs.
Notice: Submitting indicators using this command might make the indicator data publicly available. See the vendor’s documentation for more details.
Base Command
ipqs-url-reputation
Input
| Argument Name | Description | Required |
|---|---|---|
| url | URL(s) to check. Supports comma-separated list. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| IPQualityScore.Url.message | String | A generic status message, either success or some form of an error notice. |
| IPQualityScore.Url.success | Boolean | Was the request successful? |
| IPQualityScore.Url.unsafe | Boolean | Is this domain suspected of being unsafe due to phishing, malware, spamming, or abusive behavior? View the confidence level by analyzing the “risk_score”. |
| IPQualityScore.Url.domain | String | Domain name of the final destination URL of the scanned link, after following all redirects. |
| IPQualityScore.Url.ip_address | String | The IP address corresponding to the server of the domain name. |
| IPQualityScore.Url.server | String | The server banner of the domain’s IP address. For example: “nginx/1.16.0”. Value will be “N/A” if unavailable. |
| IPQualityScore.Url.content_type | String | MIME type of URL’s content. For example “text/html; charset=UTF-8”. Value will be “N/A” if unavailable. |
| IPQualityScore.Url.status_code | Number | HTTP Status Code of the URL’s response. This value should be “200” for a valid website. Value is “0” if URL is unreachable. |
| IPQualityScore.Url.page_size | Number | Total number of bytes to download the URL’s content. Value is “0” if URL is unreachable. |
| IPQualityScore.Url.domain_rank | Number | Estimated popularity rank of website globally. Value is “0” if the domain is unranked or has low traffic. |
| IPQualityScore.Url.dns_valid | Boolean | The domain of the URL has valid DNS records. |
| IPQualityScore.Url.parking | Boolean | Is the domain of this URL currently parked with a for sale notice? |
| IPQualityScore.Url.spamming | Boolean | Is the domain of this URL associated with email SPAM or abusive email addresses? |
| IPQualityScore.Url.malware | Boolean | Is this URL associated with malware or viruses? |
| IPQualityScore.Url.phishing | Boolean | Is this URL associated with malicious phishing behavior? |
| IPQualityScore.Url.suspicious | Boolean | Is this URL suspected of being malicious or used for phishing or abuse? Use in conjunction with the “risk_score” as a confidence level. |
| IPQualityScore.Url.risk_score | Number | The IPQS risk score which estimates the confidence level for malicious URL detection. Risk Scores 85+ are high risk, while Risk Scores = 100 are confirmed as accurate. |
| IPQualityScore.Url.request_id | String | A unique identifier for this request that can be used to lookup the request details or send a postback conversion notice. |
| IPQualityScore.Url.url | String | The URL being queried. |
| IPQualityScore.Url.adult | Boolean | Is this URL or domain hosting dating or adult content? |
| IPQualityScore.Url.domain_age.human | Date | A human description of when this domain was registered. (Ex: 3 months ago) |
| IPQualityScore.Url.domain_age.timestamp | Number | The unix time since epoch when this domain was first registered. (Ex: 1568061634) |
| IPQualityScore.Url.domain_age.iso | Date | The time this domain was registered in ISO8601 format (Ex: 2019-09-09T16:40:34-04:00) |
| DBotScore.Indicator | String | The indicator that was tested. |
| DBotScore.Score | Number | The actual score. |
| DBotScore.Type | String | The indicator type. |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
| URL.Data | String | The URL |
| URL.Malicious.Vendor | String | The vendor reporting the URL as malicious. |
Command Example
!ipqs-url-reputation url="https://www.example.com"
Context Example
{
"DBotScore": {
"Indicator": "https://google.com",
"Reliability": "A - Completely reliable",
"Score": 0,
"Type": "url",
"Vendor": "IPQualityScore"
},
"IPQualityScore": {
"Url": {
"adult": false,
"content_type": "text/html; charset=UTF-8",
"dns_valid": true,
"domain": "google.com",
"domain_age": {
"human": "24 years ago",
"iso": "1997-09-15T00:00:00-04:00",
"timestamp": 874296000
},
"domain_rank": 1,
"ip_address": "172.217.11.142",
"malware": false,
"message": "Success.",
"page_size": 39242,
"parking": false,
"phishing": false,
"request_id": "4DpK9WpOZGQFSPi",
"risk_score": 0,
"server": " gws\r\n",
"spamming": false,
"status_code": 200,
"success": true,
"suspicious": false,
"unsafe": false,
"url": "https://google.com"
}
},
"URL": {
"Data": "https://google.com"
}
}
Human Readable Output
IPQualityScore Results for https://google.com
message success unsafe domain ip_address server content_type status_code page_size domain_rank dns_valid parking spamming malware phishing suspicious adult risk_score domain_age request_id url Success. true false google.com 172.217.11.142 gws text/html; charset=UTF-8 200 39242 1 true false false false false false false 0 human: 24 years ago
timestamp: 874296000
iso: 1997-09-15T00:00:00-04:004DpK9WpOZGQFSPi https://google.com
url
Deprecated. Use ipqs-url-reputation instead.
Base Command
url
Input
| Argument Name | Description | Required |
|---|---|---|
| url | URL(s) to check. Supports comma-separated list. | Required |
Command Example
!url url="https://www.example.com"
ipqs-phone-reputation
Runs validation and reputation on phone numbers.
Base Command
ipqs-phone-reputation
Input
| Argument Name | Description | Required |
|---|---|---|
| phone | Phone number(s) to check in international format (e.g. +14155552671). Supports comma-separated list. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| IPQualityScore.Phone.success | Boolean | Was the request successful? |
| IPQualityScore.Phone.message | String | A generic status message. |
| IPQualityScore.Phone.formatted | String | Internationally formatted phone number. |
| IPQualityScore.Phone.local_format | String | Local format of the phone number. |
| IPQualityScore.Phone.valid | Boolean | Is the phone number valid? |
| IPQualityScore.Phone.fraud_score | Number | Overall fraud score (0–100). |
| IPQualityScore.Phone.recent_abuse | Boolean | Has there been recent abuse reported for this number? |
| IPQualityScore.Phone.VOIP | Boolean | Is the number a VOIP number? |
| IPQualityScore.Phone.prepaid | Boolean | Is this a prepaid phone number? |
| IPQualityScore.Phone.risky | Boolean | Is this number considered risky? |
| IPQualityScore.Phone.active | Boolean | Is this number currently active? |
| IPQualityScore.Phone.carrier | String | Carrier name. |
| IPQualityScore.Phone.line_type | String | Type of phone line (e.g., Landline, Mobile). |
| IPQualityScore.Phone.country | String | Country code or name associated with the number. |
| IPQualityScore.Phone.city | String | City associated with the number. |
| IPQualityScore.Phone.zip_code | String | Zip code associated with the number. |
| IPQualityScore.Phone.region | String | Region associated with the number. |
| IPQualityScore.Phone.dialing_code | Number | Country dialing code. |
| IPQualityScore.Phone.active_status | String | Active status description. |
| IPQualityScore.Phone.sms_domain | String | SMS domain for the number. |
| IPQualityScore.Phone.associated_email_addresses.status | String | Status of associated email addresses. |
| IPQualityScore.Phone.associated_email_addresses.emails | Unknown | List of email addresses associated with the number. |
| IPQualityScore.Phone.user_activity | String | User activity status. |
| IPQualityScore.Phone.mnc | String | Mobile Network Code. |
| IPQualityScore.Phone.mcc | String | Mobile Country Code. |
| IPQualityScore.Phone.leaked | Boolean | Was this number found in a known data leak? |
| IPQualityScore.Phone.spammer | Boolean | Is this number a known spammer? |
| IPQualityScore.Phone.do_not_call | Boolean | Is this number on a do-not-call list? |
| IPQualityScore.Phone.name | String | Name associated with the number. |
| IPQualityScore.Phone.timezone | String | Timezone for the number. |
| IPQualityScore.Phone.tcpa_blacklist | Boolean | Is the number on a TCPA blacklist? |
| IPQualityScore.Phone.accurate_country_code | Boolean | Is the country code accurate? |
| IPQualityScore.Phone.sms_email | String | SMS email address for the number. |
| IPQualityScore.Phone.number_recycling.message | String | Number recycling status message. |
| IPQualityScore.Phone.number_recycling.recently_recycled | Boolean | Was the number recently recycled? |
| IPQualityScore.Phone.number_recycling.last_ported_date | String | Last ported date for the number. |
| IPQualityScore.Phone.number_recycling.ported | Boolean | Was the number ported? |
| IPQualityScore.Phone.identity_data | String | Identity data associated with the number. |
| IPQualityScore.Phone.request_id | String | Unique request identifier. |
| IPQualityScore.Phone.phone | String | The phone number that was queried. |
| DBotScore.Indicator | String | The indicator that was tested. |
| DBotScore.Score | Number | The actual DBot score. |
| DBotScore.Type | String | The indicator type. |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
Command Example
!ipqs-phone-reputation phone="+15555555555"
Context Example
{
"DBotScore": {
"Indicator": "+916300445124",
"Reliability": "A - Completely reliable",
"Score": 0,
"Type": "phone",
"Vendor": "IPQualityScore"
},
"IPQualityScore": {
"Phone": {
"message": "Phone is valid.",
"success": true,
"formatted": "+916300445124",
"local_format": "063004 45124",
"valid": true,
"fraud_score": 0,
"recent_abuse": false,
"VOIP": false,
"prepaid": false,
"risky": false,
"active": true,
"carrier": "Reliance (JIO)",
"line_type": "Wireless",
"country": "IN",
"city": "N/A",
"zip_code": "N/A",
"region": "India",
"dialing_code": 91,
"active_status": "N/A",
"sms_domain": "N/A",
"associated_email_addresses": {
"status": "No associated emails found.",
"emails": []
},
"user_activity": "high",
"mnc": "854",
"mcc": "405",
"leaked": true,
"spammer": false,
"request_id": "gOODapX9Cj",
"name": "CHETHAN SWAROOP",
"timezone": "Asia/Kolkata",
"do_not_call": false,
"tcpa_blacklist": false,
"accurate_country_code": false,
"sms_email": "N/A",
"number_recycling": {
"message": "Number recycling feature disabled. Please contact support to activate.",
"recently_recycled": null,
"last_ported_date": null,
"ported": null
},
"identity_data": []
}
},
"Phone": {
"Number": "+916300445124"
}
}
Human Readable Output
IPQualityScore Results for +15555555555
fraud_score valid active VOIP prepaid risky carrier line_type country phone 20 true true false false false AT&T Mobile US +15555555555
ipqs-username-leaked
Checks if a username has been exposed in known data breaches.
Base Command
ipqs-username-leaked
Input
| Argument Name | Description | Required |
|---|---|---|
| username | Username(s) to check for leaks. Supports comma-separated list. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| IPQualityScore.Username.success | Boolean | Was the request successful? |
| IPQualityScore.Username.message | String | Status message. |
| IPQualityScore.Username.request_hash | String | Unique hash for the request. |
| IPQualityScore.Username.source | Unknown | List of sources where the username was found. |
| IPQualityScore.Username.exposed | Boolean | Was the username found in a known leak? |
| IPQualityScore.Username.first_seen.human | String | Human-readable time when the leak was first seen. |
| IPQualityScore.Username.first_seen.timestamp | Number | Unix epoch when the leak was first seen. |
| IPQualityScore.Username.first_seen.iso | String | ISO8601 timestamp when the leak was first seen. |
| IPQualityScore.Username.request_id | String | Unique request identifier. |
| IPQualityScore.Username.username | String | The username that was queried. |
| DBotScore.Indicator | String | The indicator that was tested. |
| DBotScore.Score | Number | The actual DBot score. |
| DBotScore.Type | String | The indicator type. |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
Command Example
!ipqs-username-leaked username="user@123"
Context Example
{
"DBotScore": {
"Indicator": "testuser123",
"Reliability": "A - Completely reliable",
"Score": 0,
"Type": "username",
"Vendor": "IPQualityScore"
},
"IPQualityScore": {
"Username": {
"success": true,
"message": "Success",
"request_hash": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",
"source": [],
"exposed": false,
"first_seen": {
"human": "just now",
"timestamp": 1776841128,
"iso": "2026-04-22T02:58:48-04:00"
},
"request_id": "gOOSmfym49"
}
},
"Account": {
"Username": "testuser123"
}
}
Human Readable Output
IPQualityScore Results for testuser123
success exposed source request_id username true false gOOSmfym49 testuser123
ipqs-password-leaked
Checks if a password has been exposed in known data breaches.
Base Command
ipqs-password-leaked
Input
| Argument Name | Description | Required |
|---|---|---|
| password | Password(s) to check for leaks. Supports comma-separated list. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| IPQualityScore.Password.success | Boolean | Was the request successful? |
| IPQualityScore.Password.message | String | Status message. |
| IPQualityScore.Password.request_hash | String | Unique hash for the request. |
| IPQualityScore.Password.source | Unknown | List of sources where the password was found. |
| IPQualityScore.Password.exposed | Boolean | Was the password found in a known leak? |
| IPQualityScore.Password.first_seen.human | String | Human-readable time when the leak was first seen. |
| IPQualityScore.Password.first_seen.timestamp | Number | Unix epoch when the leak was first seen. |
| IPQualityScore.Password.first_seen.iso | String | ISO8601 timestamp when the leak was first seen. |
| IPQualityScore.Password.request_id | String | Unique request identifier. |
| IPQualityScore.Password.password | String | The password that was queried. |
| DBotScore.Indicator | String | The indicator that was tested. |
| DBotScore.Score | Number | The actual DBot score. |
| DBotScore.Type | String | The indicator type. |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
Command Example
!ipqs-password-leaked password="user123"
Context Example
{
"DBotScore": {
"Indicator": "abc@123",
"Reliability": "A - Completely reliable",
"Score": 3,
"Type": "password",
"Vendor": "IPQualityScore"
},
"IPQualityScore": {
"Password": {
"success": true,
"message": "Success",
"request_hash": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",
"source": ["Exploit Antipublic", "Dark Web Leaks"],
"exposed": true,
"first_seen": {
"human": "3 years ago",
"timestamp": 1681115849,
"iso": "2023-04-10T04:37:29-04:00"
},
"request_id": "gOOeuEg77u"
}
}
}
Human Readable Output
IPQualityScore Results for provided password
success exposed source request_id true true Exploit Antipublic, Dark Web Leaks gOOeuEg77u
ipqs-email-leaked
Checks if an email address has been exposed in known data breaches.
Base Command
ipqs-email-leaked
Input
| Argument Name | Description | Required |
|---|---|---|
| Email address(es) to check for leaks. Supports comma-separated list. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| IPQualityScore.LeakedEmail.success | Boolean | Was the request successful? |
| IPQualityScore.LeakedEmail.message | String | Status message. |
| IPQualityScore.LeakedEmail.request_hash | String | Unique hash for the request. |
| IPQualityScore.LeakedEmail.source | Unknown | List of sources where the email was found. |
| IPQualityScore.LeakedEmail.exposed | Boolean | Was the email found in a known leak? |
| IPQualityScore.LeakedEmail.first_seen.human | String | Human-readable time when the leak was first seen. |
| IPQualityScore.LeakedEmail.first_seen.timestamp | Number | Unix epoch when the leak was first seen. |
| IPQualityScore.LeakedEmail.first_seen.iso | String | ISO8601 timestamp when the leak was first seen. |
| IPQualityScore.LeakedEmail.plain_text_password | String | Plain text password if found in the leak. |
| IPQualityScore.LeakedEmail.request_id | String | Unique request identifier. |
| IPQualityScore.LeakedEmail.email | String | The email address that was queried. |
| IPQualityScore.Email.exposed | Boolean | Was the email found in a known leak? |
| IPQualityScore.Email.success | Boolean | Was the request successful? |
| IPQualityScore.Email.plain_text_password | String | Plain text password if found in the leak. |
| IPQualityScore.Email.first_seen.human | String | Human-readable time when the leak was first seen. |
| IPQualityScore.Email.first_seen.iso | String | ISO8601 timestamp when the leak was first seen. |
| IPQualityScore.Email.first_seen.timestamp | Number | Unix epoch when the leak was first seen. |
| IPQualityScore.Email.request_hash | String | Unique hash for the request. |
| IPQualityScore.Email.source | Unknown | List of sources where the email was found. |
| IPQualityScore.Email.request_id | String | Unique request identifier. |
| IPQualityScore.Email.message | String | Status message. |
| DBotScore.Indicator | String | The indicator that was tested. |
| DBotScore.Score | Number | The actual DBot score. |
| DBotScore.Type | String | The indicator type. |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
Command Example
!ipqs-email-leaked email="user@example.com"
Context Example
{
"DBotScore": {
"Indicator": "user@example.com",
"Reliability": "A - Completely reliable",
"Score": 3,
"Type": "email",
"Vendor": "IPQualityScore"
},
"IPQualityScore": {
"LeakedEmail": {
"success": true,
"message": "Success",
"request_hash": "d2b6ff2e611edf5bb40014608a84d75a5b30773272248c28fa79db3bd5739677",
"source": ["Dark Web Leaks"],
"exposed": true,
"first_seen": {
"human": "6 years ago",
"timestamp": 1591641120,
"iso": "2020-06-08T14:32:00-04:00"
},
"plain_text_password": false,
"request_id": "gOOsrUX4lK",
"email": "user@example.com"
}
},
"Email": {
"Address": "user@example.com"
}
}
Human Readable Output
IPQualityScore Results for user@example.com
success exposed plain_text_password source request_id true true false Dark Web Leaks gOOsrUX4lK user@example.com
ipqs-file-scan
Uploads a file and scans it using the IPQualityScore Malware File Scanner API. First attempts a cached lookup by file hash; if not cached, the file is submitted for scanning. Polls for results automatically if the scan is pending.
Base Command
ipqs-file-scan
Input
| Argument Name | Description | Required |
|---|---|---|
| entry_id | The entry id of the uploaded file in the War Room. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| IPQualityScore.FileScan.success | Boolean | Was the request successful? |
| IPQualityScore.FileScan.message | String | Status message. |
| IPQualityScore.FileScan.file_name | String | The file name that was scanned. |
| IPQualityScore.FileScan.file_hash | String | SHA256 hash of the file. |
| IPQualityScore.FileScan.type | String | Type of operation performed: “scan” or “lookup”. |
| IPQualityScore.FileScan.status | String | Scan status: “pending” or “complete”. |
| IPQualityScore.FileScan.detected | Boolean | Was the file detected as malicious? |
| IPQualityScore.FileScan.detected_scans | Number | Number of engines that detected the file as malicious. |
| IPQualityScore.FileScan.total_scans | Number | Total number of scan engines used. |
| IPQualityScore.FileScan.result | String | Detailed scan engine results. |
| IPQualityScore.FileScan.file_size | Number | File size in bytes. |
| IPQualityScore.FileScan.file_type | String | MIME type of the file. |
| IPQualityScore.FileScan.sha1 | String | SHA1 hash of the file. |
| IPQualityScore.FileScan.md5 | String | MD5 hash of the file. |
| IPQualityScore.FileScan.update_url | String | URL to poll for updated scan results. |
| IPQualityScore.FileScan.request_id | String | Unique request identifier. |
| File.SHA256 | String | SHA256 hash of the file. |
| File.SHA1 | String | SHA1 hash of the file. |
| File.MD5 | String | MD5 hash of the file. |
| File.Size | Number | File size in bytes. |
| File.Type | String | File MIME type. |
| File.Malicious.Count | Number | Number of detections. |
| DBotScore.Indicator | String | The indicator that was tested. |
| DBotScore.Score | Number | The actual DBot score. |
| DBotScore.Type | String | The indicator type (file). |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
Command Example
!ipqs-file-scan entry_id="212@_13"
Human Readable Output
IPQualityScore File Scan Result
file_name file_hash detected detected_scans total_scans status file_type file_size sha1 md5 request_id ipks.exe a35e17ca3d1c0d7363ad4a9c358260d1343033212299ac37cd43471aaa3478b9 false 0 7 done text/html 61049 6751c12507d83f850edaceea8bd51031d27ee363 942af291e78ae29787c0069611ec58e3 fmXlsRjJ4x
ipqs-url-file-scan
Submits a URL to the IPQualityScore Malware File Scanner API. First attempts a cached lookup; if not cached, the URL is submitted for scanning. Polls for results automatically if the scan is pending.
Base Command
ipqs-url-file-scan
Input
| Argument Name | Description | Required |
|---|---|---|
| url | URL(s) to scan for malware. Supports comma-separated list. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| IPQualityScore.URLFileScan.success | Boolean | Was the request successful? |
| IPQualityScore.URLFileScan.message | String | Status message. |
| IPQualityScore.URLFileScan.file_name | String | The URL that was scanned. |
| IPQualityScore.URLFileScan.file_hash | String | SHA256 hash of the scanned content. |
| IPQualityScore.URLFileScan.type | String | Type of operation performed: “scan” or “lookup”. |
| IPQualityScore.URLFileScan.status | String | Scan status: “pending” or “complete”. |
| IPQualityScore.URLFileScan.detected | Boolean | Was the URL detected as malicious? |
| IPQualityScore.URLFileScan.detected_scans | Number | Number of engines that detected the URL as malicious. |
| IPQualityScore.URLFileScan.total_scans | Number | Total number of scan engines used. |
| IPQualityScore.URLFileScan.result | String | Detailed scan engine results. |
| IPQualityScore.URLFileScan.file_size | Number | Size of the scanned content in bytes. |
| IPQualityScore.URLFileScan.file_type | String | MIME type of the scanned content. |
| IPQualityScore.URLFileScan.sha1 | String | SHA1 hash of the scanned content. |
| IPQualityScore.URLFileScan.md5 | String | MD5 hash of the scanned content. |
| IPQualityScore.URLFileScan.update_url | String | URL to poll for updated scan results. |
| IPQualityScore.URLFileScan.scan_date | Unknown | Date and time when the scan was performed. |
| IPQualityScore.URLFileScan.request_id | String | Unique request identifier. |
| URL.Data | String | The URL that was scanned. |
| URL.Malicious.Description | String | Description of why the URL was flagged as malicious. |
| URL.Malicious.Vendor | String | The vendor that identified the URL as malicious. |
| DBotScore.Indicator | String | The indicator that was tested. |
| DBotScore.Score | Number | The actual DBot score. |
| DBotScore.Type | String | The indicator type (URL). |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
Command Example
!ipqs-url-file-scan url="http://www.example.com/"
Human Readable Output
IPQualityScore URL Scan Result
file_name file_hash detected detected_scans total_scans status file_type file_size sha1 md5 request_id M94scZj.exe a35e17ca3d1c0d7363ad4a9c358260d1343033212299ac37cd43471aaa3478b9 false 0 7 done text/html 61049 6751c12507d83f850edaceea8bd51031d27ee363 942af291e78ae29787c0069611ec58e3 fmXlsRjJ4x
Scoring Thresholds
| Indicator Type | Suspicious (default) | Malicious (default) | Score Field |
|---|---|---|---|
| IP | 75 | 90 | fraud_score |
| 75 | 90 | fraud_score |
|
| URL (reputation) | 75 | 90 | risk_score |
| Phone | 75 | 90 | fraud_score |
| File (malware scan) | 1 detection | 5 detections | detected_scans |
| URL (malware scan) | 1 detection | 5 detections | detected_scans |
Configuration parameters
apikey— API Key (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsip_suspicious_score_threshold— IP Suspicious Score Thresholdip_malicious_score_threshold— IP Malicious Score Thresholdemail_suspicious_score_threshold— Email Suspicious Score Thresholdemail_malicious_score_threshold— Email Malicious Score Thresholdurl_suspicious_score_threshold— Url Suspicious Score Thresholdurl_malicious_score_threshold— Url Malicious Score Thresholdphone_suspicious_score_threshold— Phone Suspicious Score Thresholdphone_malicious_score_threshold— Phone Malicious Score ThresholdfeedReliability— Source Reliability (required)
Commands (12)
-
emailDeprecatedDeprecated. Use ipqs-email-reputation instead.
-
ipDeprecatedDeprecated. Use ipqs-ip-reputation instead.
-
ipqs-email-leakedChecks if an email has been leaked in known breaches.
-
ipqs-email-reputationRuns reputation on email addresses.
-
ipqs-file-scanUploads and scans a file using IPQualityScore Malware File Scanner API.
-
ipqs-ip-reputationRuns reputation on IPs.
-
ipqs-password-leakedChecks if a password has been leaked in known breaches.
-
ipqs-phone-reputationRuns validation and reputation on phone numbers.
-
ipqs-url-file-scanScans a URL using IPQualityScore Malware File Scanner API.
-
ipqs-url-reputationRuns reputation on URLs.
-
ipqs-username-leakedChecks if a username has been leaked in known breaches.
-
urlDeprecatedDeprecated. Use ipqs-url-reputation instead.
# bandit: skip-file """ IPQualityScore Integration - Comprehensive Test Suite This module provides comprehensive tests for the IPQualityScore XSOAR integration, covering reputation lookups (IP, email, URL, phone), leak detection, and malware scanning. Tests are organized into logical sections for better maintainability. """ import importlib import json import sys import types import uuid from pathlib import Path from typing import Any from unittest.mock import MagicMock, patch import pytest def _install_fake_demisto_modules() -> None: """ Install lightweight fake XSOAR modules before importing the integration. This function sets up mock implementations of demistomock, urllib3, CommonServerPython, and CommonServerUserPython modules to allow testing without a full XSOAR environment. """ # Setup demistomock module demisto_module = types.ModuleType("demistomock") demisto_module.getFilePath = MagicMock() demisto_module.params = MagicMock(return_value={}) demisto_module.args = MagicMock(return_value={}) demisto_module.command = MagicMock(return_value="") demisto_module.results = MagicMock() demisto_module.debug = MagicMock() demisto_module.error = MagicMock() demisto_module.getIntegrationContext = MagicMock(return_value={}) demisto_module.setIntegrationContext = MagicMock() # Setup urllib3 module urllib3_module = types.ModuleType("urllib3") class InsecureRequestWarning(Warning): """Mock warning class for insecure requests.""" urllib3_module.disable_warnings = MagicMock() urllib3_module.exceptions = types.SimpleNamespace( InsecureRequestWarning=InsecureRequestWarning, ) # Setup CommonServerPython module csp_module = types.ModuleType("CommonServerPython") class DemistoException(Exception): """Mock exception from XSOAR.""" class ContentClient: """Mock ContentClient base class for API interactions.""" def __init__( self, base_url: str | None = None, headers: dict[str, str] | None = None, verify: bool = True, proxy: bool = False, ok_codes: list[int] | None = None, ) -> None: self.base_url = base_url self.headers = headers self._verify = verify self.proxy = proxy self.ok_codes = ok_codes def _http_request(self, method: str, url_suffix: str | None = None, **kwargs: Any) -> Any: """Raise NotImplementedError to be mocked in tests.""" raise NotImplementedError("_http_request should be mocked in tests") class BaseClient: """Mock BaseClient for backward compatibility.""" def __init__( self, base_url: str | None = None, headers: dict[str, str] | None = None, verify: bool = True, proxy: bool = False, ok_codes: list[int] | None = None, ) -> None: self.base_url = base_url self.headers = headers self._verify = verify self.proxy = proxy self.ok_codes = ok_codes def _http_request(self, method: str, url_suffix: str | None = None, **kwargs: Any) -> Any: """Raise NotImplementedError to be mocked in tests.""" raise NotImplementedError("_http_request should be mocked in tests") def argToList(value: Any, separator: str = ",") -> list[str]: """Convert various types to list.""" if value is None: return [] if isinstance(value, list): return value if isinstance(value, str): return [item.strip() for item in value.split(separator) if item.strip()] return [value] def arg_to_number(value: Any) -> int | None: """Convert value to integer.""" if value is None or isinstance(value, bool): return None try: return int(value) except (TypeError, ValueError): return None def tableToMarkdown(title: str, data: Any, headers: list[str] | None = None) -> str: """Convert table data to markdown format.""" return f"{title}: {data}" class CommandResults: """Mock CommandResults for test assertions.""" def __init__( self, readable_output: str | None = None, outputs_prefix: str | None = None, outputs_key_field: str | None = None, outputs: Any = None, indicator: Any = None, raw_response: Any = None, scheduled_command: Any = None, ) -> None: self.readable_output = readable_output self.outputs_prefix = outputs_prefix self.outputs_key_field = outputs_key_field self.outputs = outputs self.indicator = indicator self.raw_response = raw_response self.scheduled_command = scheduled_command class ScheduledCommand: """Mock ScheduledCommand for polling operations.""" def __init__( self, command: str | None = None, next_run_in_seconds: int | None = None, args: dict[str, Any] | None = None, ) -> None: self.command = command self.next_run_in_seconds = next_run_in_seconds self.args = args or {} def return_results(value: Any) -> Any: """Return results mock.""" return value def return_error(message: str) -> None: """Raise exception on error.""" raise Exception(message) def LOG(message: str) -> str: """Log message mock.""" return message class DBotScoreReliability: """Mock DBotScore reliability constants.""" A_PLUS = "A+" A = "A" B = "B" C = "C" D = "D" E = "E" F = "F" class DBotScoreType: """Mock DBotScore type constants.""" IP = "IP" EMAIL = "Email" URL = "URL" FILE = "File" PHONE = "Phone" ACCOUNT = "Account" GENERIC = "Generic" CUSTOM = "Custom" class Common: """Mock Common namespace for indicators.""" class DBotScore: """Mock DBotScore indicator.""" BAD = 3 SUSPICIOUS = 2 NONE = 0 def __init__( self, indicator: str, indicator_type: str, score: int, integration_name: str, reliability: str, ) -> None: self.indicator = indicator self.indicator_type = indicator_type self.score = score self.integration_name = integration_name self.reliability = reliability class IP: """Mock IP indicator.""" def __init__(self, **kwargs: Any) -> None: self.__dict__.update(kwargs) class EMAIL: """Mock EMAIL indicator.""" def __init__(self, **kwargs: Any) -> None: self.__dict__.update(kwargs) class URL: """Mock URL indicator.""" def __init__(self, **kwargs: Any) -> None: self.__dict__.update(kwargs) class File: """Mock File indicator.""" def __init__(self, **kwargs: Any) -> None: self.__dict__.update(kwargs) # Register all mock modules csp_module.BaseClient = BaseClient csp_module.ContentClient = ContentClient csp_module.DemistoException = DemistoException csp_module.argToList = argToList csp_module.arg_to_number = arg_to_number csp_module.tableToMarkdown = tableToMarkdown csp_module.CommandResults = CommandResults csp_module.ScheduledCommand = ScheduledCommand csp_module.return_results = return_results csp_module.return_error = return_error csp_module.LOG = LOG csp_module.DBotScoreReliability = DBotScoreReliability csp_module.DBotScoreType = DBotScoreType csp_module.Common = Common csu_module = types.ModuleType("CommonServerUserPython") sys.modules["demistomock"] = demisto_module sys.modules["urllib3"] = urllib3_module sys.modules["CommonServerPython"] = csp_module sys.modules["CommonServerUserPython"] = csu_module @pytest.fixture(scope="module") def integration_module() -> types.ModuleType: """ Fixture to load the IPQualityScore integration module. Sets up mock XSOAR modules before importing, ensuring the integration can be tested without a full XSOAR environment. Module-scoped to optimize performance across multiple tests. Returns: types.ModuleType: The loaded IPQualityScore module. """ _install_fake_demisto_modules() if "IPQualityScore" in sys.modules: del sys.modules["IPQualityScore"] module = importlib.import_module("IPQualityScore") return module @pytest.fixture def client(integration_module: types.ModuleType) -> Any: """ Fixture to create a test Client instance. Creates an IPQualityScore Client with test configuration and disabled SSL verification for testing purposes. Args: integration_module: The loaded IPQualityScore integration module. Returns: Any: An initialized Client instance ready for testing. """ return integration_module.Client( base_url="https://ipqualityscore.com/api/json", headers={"IPQS-KEY": "test-key"}, verify=False, proxy=False, ) @pytest.fixture def sample_password() -> str: """ Fixture to generate a random password for testing. Generates a unique UUID-based password string for password leak tests. This ensures test isolation and prevents hardcoded test values. Returns: str: A randomly generated password string. """ return uuid.uuid4().hex def load_test_data(filename: str) -> dict[str, Any]: """ Load JSON test data from the test_data directory. Reads and parses JSON test data files stored alongside the test module. This allows for realistic API response testing without network calls. Args: filename: Name of the JSON file to load from test_data directory. Returns: dict[str, Any]: Parsed JSON content from the test data file. Raises: FileNotFoundError: If the specified test data file doesn't exist. json.JSONDecodeError: If the file is not valid JSON. """ test_data_dir = Path(__file__).parent / "test_data" with open(test_data_dir / filename, encoding="utf-8") as fh: return json.load(fh) # ============================================================================ # Test Module Validation Tests # ============================================================================ def test_test_module_success(integration_module: types.ModuleType, client: Any) -> None: """Test that test_module succeeds with valid API response.""" with patch.object(client, "reputation_request", return_value={"success": True}): assert integration_module.test_module(client) == "ok" def test_test_module_failure(integration_module: types.ModuleType, client: Any) -> None: """Test that test_module raises exception when API response indicates failure.""" with ( patch.object( client, "reputation_request", return_value={"success": False, "message": "Invalid API key"}, ), pytest.raises(integration_module.DemistoException, match="Invalid API key"), ): integration_module.test_module(client) # ============================================================================ # Response Validation Tests # ============================================================================ def test_ensure_dict_response_valid(integration_module: types.ModuleType) -> None: """Test that ensure_dict_response accepts valid dictionary responses.""" response = {"success": True} assert integration_module.ensure_dict_response(response, "test") == response def test_ensure_dict_response_invalid(integration_module: types.ModuleType) -> None: """Test that ensure_dict_response rejects non-dictionary responses.""" with pytest.raises(integration_module.DemistoException, match="expected dict"): integration_module.ensure_dict_response(["bad"], "test") # ============================================================================ # Score Calculation Tests # ============================================================================ @pytest.mark.parametrize( ("value", "expected"), [ (10, 0), # Below suspicious threshold (75, 2), # At suspicious threshold (90, 3), # At malicious threshold ], ) def test_calculate_score(integration_module: types.ModuleType, value: int, expected: int) -> None: """Test score calculation against various thresholds.""" assert integration_module.calculate_score(value, 75, 90) == expected @pytest.mark.parametrize( ("result", "expected"), [ ({"leaked": True}, True), ({"exposed": True}, True), ({"leaked": False, "exposed": False}, False), ({}, False), ], ) def test_was_leaked(integration_module: types.ModuleType, result: dict[str, Any], expected: bool) -> None: """Test leak detection from API response fields.""" assert integration_module.was_leaked(result) is expected # ============================================================================ # Threshold Configuration Tests # ============================================================================ def test_get_thresholds_default(integration_module: types.ModuleType) -> None: """Test that get_thresholds returns default values when not configured.""" assert integration_module.get_thresholds({}, "ip") == (75, 90) def test_get_thresholds_from_params(integration_module: types.ModuleType) -> None: """Test that get_thresholds properly retrieves custom parameters.""" params = { "ip_suspicious_score_threshold": "60", "ip_malicious_score_threshold": "95", } assert integration_module.get_thresholds(params, "ip") == (60, 95) def test_get_file_thresholds_default(integration_module: types.ModuleType) -> None: """Test that get_file_thresholds returns correct default values.""" assert integration_module.get_file_thresholds({}) == (1, 4) # ============================================================================ # Input Validation Tests # ============================================================================ def test_validate_ip_valid(integration_module: types.ModuleType) -> None: """Test that valid IP addresses pass validation.""" assert integration_module.validate_ip("8.8.8.8") == "8.8.8.8" def test_validate_ip_invalid(integration_module: types.ModuleType) -> None: """Test that invalid IP addresses raise DemistoException.""" with pytest.raises(integration_module.DemistoException): integration_module.validate_ip("999.999.999.999") def test_validate_email_valid(integration_module: types.ModuleType) -> None: """Test that valid email addresses pass validation.""" assert integration_module.validate_email("test@example.com") == "test@example.com" def test_validate_email_invalid(integration_module: types.ModuleType) -> None: """Test that invalid email addresses raise DemistoException.""" with pytest.raises(integration_module.DemistoException): integration_module.validate_email("bad-email") def test_validate_phone_valid(integration_module: types.ModuleType) -> None: """Test that valid phone numbers pass validation.""" assert integration_module.validate_phone("+14155552671") == "+14155552671" def test_validate_phone_invalid(integration_module: types.ModuleType) -> None: """Test that invalid phone numbers raise DemistoException.""" with pytest.raises(integration_module.DemistoException): integration_module.validate_phone("bad-phone") @pytest.mark.parametrize( "value", [ "https://example.com/path", "example.com", ], ) def test_validate_url_or_domain_valid(integration_module: types.ModuleType, value: str) -> None: """Test that valid URLs and domains pass validation.""" assert integration_module.validate_url_or_domain(value) == value def test_validate_url_or_domain_invalid(integration_module: types.ModuleType) -> None: """Test that invalid URLs and domains raise DemistoException.""" with pytest.raises(integration_module.DemistoException): integration_module.validate_url_or_domain("not valid url") def test_validate_non_empty(integration_module: types.ModuleType) -> None: """Test that validate_non_empty strips whitespace and validates content.""" assert integration_module.validate_non_empty(" user ", "Username") == "user" with pytest.raises(integration_module.DemistoException, match="Username cannot be empty"): integration_module.validate_non_empty(" ", "Username") # ============================================================================ # IP Reputation Command Tests # ============================================================================ def test_ip_command_with_test_data(integration_module: types.ModuleType, client: Any) -> None: """Test IP reputation lookup with realistic API response.""" response = load_test_data("ip_response.json") with patch.object(client, "reputation_request", return_value=response): result = integration_module.ip_command( client, {"ip": "15.99.160.255"}, 75, 85, "A - Completely reliable", ) assert len(result) == 1 assert result[0].outputs_prefix == "IPQualityScore.IP" assert result[0].outputs["address"] == "15.99.160.255" assert result[0].indicator.dbot_score.score == 3 assert result[0].indicator.geo_country == "US" # ============================================================================ # Email Reputation Command Tests # ============================================================================ def test_email_command_with_test_data(integration_module: types.ModuleType, client: Any) -> None: """Test email reputation lookup with realistic API response.""" response = load_test_data("email_response.json") with patch.object(client, "reputation_request", return_value=response): result = integration_module.email_command( client, {"email": "example@gmail.com"}, 75, 85, "A - Completely reliable", ) assert len(result) == 1 assert result[0].outputs_prefix == "IPQualityScore.Email" assert result[0].outputs["address"] == "example@gmail.com" assert result[0].indicator.dbot_score.score == 3 # ============================================================================ # URL Reputation Command Tests # ============================================================================ def test_url_command_with_test_data(integration_module: types.ModuleType, client: Any) -> None: """Test URL reputation lookup with realistic API response.""" response = load_test_data("url_response.json") with patch.object(client, "reputation_request", return_value=response): result = integration_module.url_command( client, {"url": "https://www.example.com"}, 75, 85, "A - Completely reliable", ) assert len(result) == 1 assert result[0].outputs_prefix == "IPQualityScore.Url" assert result[0].outputs["url"] == "https://www.example.com" assert result[0].indicator.dbot_score.score in (0, 2, 3) # ============================================================================ # Phone Reputation Command Tests # ============================================================================ def test_phone_command_with_test_data(integration_module: types.ModuleType, client: Any) -> None: """Test phone reputation lookup with realistic API response.""" response = load_test_data("phone_response.json") with patch.object(client, "reputation_request", return_value=response): result = integration_module.phone_command( client, {"phone": "+14155552671"}, 75, 85, "A - Completely reliable", ) assert len(result) == 1 assert result[0].outputs_prefix == "IPQualityScore.Phone" assert result[0].outputs["phone"] == "+14155552671" assert result[0].indicator.score in (0, 2, 3) # ============================================================================ # Leak Detection Command Tests # ============================================================================ def test_leaked_username_command(integration_module: types.ModuleType, client: Any) -> None: """Test username leak detection.""" with patch.object(client, "leaked_request", return_value={"success": True, "leaked": True}): result = integration_module.leaked_username_command( client, {"username": "admin"}, "A - Completely reliable", ) assert len(result) == 1 assert result[0].outputs_prefix == "IPQualityScore.Username" assert result[0].outputs["username"] == "admin" assert result[0].indicator.score == 3 def test_leaked_password_command(integration_module: types.ModuleType, client: Any, sample_password: str) -> None: """Test password leak detection returns no score when password is not exposed.""" with patch.object(client, "leaked_request", return_value={"success": True, "exposed": False}): result = integration_module.leaked_password_command( client, {"password": sample_password}, "A - Completely reliable", ) assert len(result) == 1 assert result[0].outputs_prefix == "IPQualityScore.Password" assert result[0].outputs["password"] == sample_password assert result[0].indicator.score == 0 def test_leaked_email_command(integration_module: types.ModuleType, client: Any) -> None: """Test email leak detection.""" with patch.object(client, "leaked_request", return_value={"success": True, "exposed": True}): result = integration_module.leaked_email_command( client, {"email": "user@example.com"}, "A - Completely reliable", ) assert len(result) == 1 assert result[0].outputs_prefix == "IPQualityScore.LeakedEmail" assert result[0].outputs["email"] == "user@example.com" assert result[0].indicator.dbot_score.score == 3 # ============================================================================ # Malware Scan Engine Result Processing Tests # ============================================================================ def test_flatten_engine_results_with_valid_result(integration_module: types.ModuleType) -> None: """Test that engine results are properly flattened for display.""" scan_result = { "status": "cached", "result": [ {"name": "EngineA", "detected": True, "error": False}, {"name": "EngineB", "detected": False, "error": False}, ], } flattened = integration_module.flatten_engine_results(scan_result) assert "result" not in flattened assert flattened["EngineA"] == {"detected": True, "error": False} assert flattened["EngineB"] == {"detected": False, "error": False} def test_flatten_engine_results_with_non_list_result(integration_module: types.ModuleType) -> None: """Test that flatten_engine_results handles non-list result fields.""" scan_result = {"status": "cached", "result": {"bad": "format"}} flattened = integration_module.flatten_engine_results(scan_result) assert flattened == {"status": "cached"} @pytest.mark.parametrize( ("scan_result", "expected"), [ ( { "result": [ {"name": "A", "detected": True}, {"name": "B", "detected": False}, ], }, 1, ), ({"detected_scans": "3"}, 3), ({}, 0), ], ) def test_extract_detected_scans_variants( integration_module: types.ModuleType, scan_result: dict[str, Any], expected: int, ) -> None: """Test detection count extraction from various result formats.""" assert integration_module.extract_detected_scans(scan_result) == expected def test_normalize_scan_result(integration_module: types.ModuleType) -> None: """Test that scan results are properly normalized for XSOAR.""" normalized = integration_module.normalize_scan_result( { "file_size": "123", "update_url": "https://example.com/update", "result": [ {"name": "EngineA", "detected": True}, {"name": "EngineB", "detected": False}, ], } ) assert "update_url" not in normalized assert normalized["file_size"] == 123 assert normalized["detected_scans"] == 1 # ============================================================================ # File Scan Command Result Building Tests # ============================================================================ def test_build_file_scan_command_result(integration_module: types.ModuleType) -> None: """Test file scan command result construction.""" result = integration_module.build_file_scan_command_result( scan_result={ "status": "cached", "file_hash": "abc123", "file_size": 10, "detected_scans": 4, "result": [{"name": "EngineA", "detected": True, "error": False}], }, file_name="sample.exe", suspicious_threshold=1, malicious_threshold=4, parsed_reliability="F", ) assert result.outputs_prefix == "IPQualityScore.FileScan" assert result.outputs_key_field == "file_name" assert result.outputs["file_name"] == "sample.exe" assert result.indicator.name == "sample.exe" assert result.indicator.dbot_score.score == 3 def test_build_url_file_scan_command_result(integration_module: types.ModuleType) -> None: """Test URL file scan command result construction.""" result = integration_module.build_url_file_scan_command_result( scan_result={ "status": "cached", "detected_scans": 1, "url": "https://evil.example", }, url_value="https://evil.example", suspicious_threshold=1, malicious_threshold=4, parsed_reliability="F", ) assert result.outputs_prefix == "IPQualityScore.URLFileScan" assert result.outputs_key_field == "url" assert result.indicator.url == "https://evil.example" assert result.indicator.dbot_score.score == 2 # ============================================================================ # File Scanning Command Tests # ============================================================================ def test_file_command_cached_lookup(integration_module: types.ModuleType, client: Any) -> None: """Test file command with cached scan result.""" integration_module.demisto.getFilePath.return_value = { "path": "/tmp/sample.exe", "name": "sample.exe", } with patch.object( client, "malware_file_request", return_value={ "status": "cached", "file_hash": "hash123", "file_size": "50", "detected_scans": "0", "result": [], }, ) as mocked_request: result = integration_module.file_command( client, {"entry_id": "123"}, 1, 4, "A - Completely reliable", ) # file_command returns a single CommandResults when cached assert result.outputs_prefix == "IPQualityScore.FileScan" assert result.outputs["file_name"] == "sample.exe" mocked_request.assert_called_once_with(is_lookup=True, file_path="/tmp/sample.exe") def test_file_command_missing_entry_id(integration_module: types.ModuleType, client: Any) -> None: """Test file command raises error when entry_id is missing.""" with pytest.raises(integration_module.DemistoException, match="entry_id is required"): integration_module.file_command( client, {}, 1, 4, "A - Completely reliable", ) def test_file_command_pending_returns_scheduled_command(integration_module: types.ModuleType, client: Any) -> None: """Test file command returns scheduled command when scan is pending.""" context = {} integration_module.demisto.getFilePath.return_value = { "path": "/tmp/sample.exe", "name": "sample.exe", } integration_module.demisto.command.return_value = "ipqs-file-scan" integration_module.demisto.getIntegrationContext.side_effect = lambda: context integration_module.demisto.setIntegrationContext.side_effect = lambda updated: context.update(updated) with patch.object( client, "malware_file_request", side_effect=[ {"status": "not_found"}, {"status": "pending", "request_id": "req-123"}, ], ): result = integration_module.file_command( client, {"entry_id": "123"}, 1, 4, "A - Completely reliable", ) assert result.scheduled_command.command == "ipqs-file-scan" assert result.scheduled_command.args["request_id"] == "req-123" assert context["ipqs_retry_count_req-123"] == 1 def test_file_command_poll_completed(integration_module: types.ModuleType, client: Any) -> None: """Test file command polling returns results when scan completes.""" context = {"ipqs_retry_count_req-123": 2} integration_module.demisto.getIntegrationContext.side_effect = lambda: context integration_module.demisto.setIntegrationContext.side_effect = lambda updated: context.clear() or context.update(updated) with patch.object( client, "poll_result", return_value={ "status": "complete", "request_id": "req-123", "file_name": "sample.exe", "file_hash": "hash123", "file_size": "100", "detected_scans": "0", "result": [], }, ): result = integration_module.file_command( client, {"request_id": "req-123"}, 1, 4, "A - Completely reliable", ) assert result.outputs_prefix == "IPQualityScore.FileScan" assert result.outputs["file_name"] == "sample.exe" assert "ipqs_retry_count_req-123" not in context # ============================================================================ # URL File Scanning Command Tests # ============================================================================ def test_url_file_command_cached_lookup(integration_module: types.ModuleType, client: Any) -> None: """Test URL file command with cached scan result.""" with patch.object( client, "malware_url_request", return_value={ "status": "cached", "file_size": "20", "detected_scans": "0", "result": [], }, ): result = integration_module.url_file_command( client, {"url": "https://example.com"}, 1, 4, "A - Completely reliable", ) # url_file_command returns a single CommandResults when cached assert result.outputs_prefix == "IPQualityScore.URLFileScan" assert result.indicator.url == "https://example.com" def test_url_file_command_missing_url(integration_module: types.ModuleType, client: Any) -> None: """Test URL file command raises error when URL is missing.""" with pytest.raises(integration_module.DemistoException, match="url is required"): integration_module.url_file_command( client, {}, 1, 4, "A - Completely reliable", ) def test_url_file_command_pending_returns_scheduled_command(integration_module: types.ModuleType, client: Any) -> None: """Test URL file command returns scheduled command when scan is pending.""" context = {} integration_module.demisto.command.return_value = "ipqs-url-file-scan" integration_module.demisto.getIntegrationContext.side_effect = lambda: context integration_module.demisto.setIntegrationContext.side_effect = lambda updated: context.update(updated) with patch.object( client, "malware_url_request", side_effect=[ {"status": "not_found"}, {"status": "pending", "request_id": "req-url-123"}, ], ): result = integration_module.url_file_command( client, {"url": "https://example.com"}, 1, 4, "A - Completely reliable", ) assert result.scheduled_command.command == "ipqs-url-file-scan" assert result.scheduled_command.args["request_id"] == "req-url-123" assert context["ipqs_retry_count_req-url-123"] == 1 def test_url_file_command_poll_completed(integration_module: types.ModuleType, client: Any) -> None: """Test URL file command polling returns results when scan completes.""" context = {"ipqs_retry_count_req-url-123": 2} integration_module.demisto.getIntegrationContext.side_effect = lambda: context integration_module.demisto.setIntegrationContext.side_effect = lambda updated: context.clear() or context.update(updated) with patch.object( client, "poll_result", return_value={ "status": "complete", "request_id": "req-url-123", "url": "https://example.com", "file_size": "100", "detected_scans": "0", "result": [], }, ): result = integration_module.url_file_command( client, {"request_id": "req-url-123", "url": "https://example.com"}, 1, 4, "A - Completely reliable", ) assert result.outputs_prefix == "IPQualityScore.URLFileScan" assert result.indicator.url == "https://example.com" assert "ipqs_retry_count_req-url-123" not in context # ============================================================================ # Retry Count Management Tests # ============================================================================ def test_get_retry_count_accepts_string(integration_module: types.ModuleType) -> None: """Test get_retry_count correctly handles string-formatted retry counts.""" integration_module.demisto.getIntegrationContext.side_effect = None integration_module.demisto.getIntegrationContext.return_value = { "ipqs_retry_count_req-1": "2", } assert integration_module.get_retry_count("req-1") == 2 def test_get_retry_count_rejects_bad_string(integration_module: types.ModuleType) -> None: """Test get_retry_count raises error for invalid string values.""" integration_module.demisto.getIntegrationContext.side_effect = None integration_module.demisto.getIntegrationContext.return_value = { "ipqs_retry_count_req-1": "bad", } with pytest.raises(integration_module.DemistoException, match="Invalid retry count"): integration_module.get_retry_count("req-1")