IPQualityScore

Proactively Prevent Fraud.

Data Enrichment & Threat Intelligence · IPQualityScore (IPQS) Threat Risk Scoring

Details

IDIPQualityScore
ProviderIPQualityScore
CategoryData Enrichment & Threat Intelligence
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10230415
Supported ModulesAgentix 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

  1. Register for a free account at https://www.ipqualityscore.com/create-account. Free accounts include 5,000 API lookups per month.
  2. 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 Google 15169 Google 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 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:00
human: 2 years ago
timestamp: 1557122946
iso: 2019-05-06T02:09:06-04:00
noreply@ipqualityscore.com 4DpK9WpOZGQDnr7 noreply@ipqualityscore.com

email


Deprecated. Use ipqs-email-reputation instead.

Base Command

email

Input

Argument Name Description Required
email 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:00
4DpK9WpOZGQFSPi 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 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 email
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
Email 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 settings
  • ip_suspicious_score_threshold — IP Suspicious Score Threshold
  • ip_malicious_score_threshold — IP Malicious Score Threshold
  • email_suspicious_score_threshold — Email Suspicious Score Threshold
  • email_malicious_score_threshold — Email Malicious Score Threshold
  • url_suspicious_score_threshold — Url Suspicious Score Threshold
  • url_malicious_score_threshold — Url Malicious Score Threshold
  • phone_suspicious_score_threshold — Phone Suspicious Score Threshold
  • phone_malicious_score_threshold — Phone Malicious Score Threshold
  • feedReliability — Source Reliability (required)

Commands (12)

  • email Deprecated

    Deprecated. Use ipqs-email-reputation instead.

  • ip Deprecated

    Deprecated. Use ipqs-ip-reputation instead.

  • ipqs-email-leaked

    Checks if an email has been leaked in known breaches.

  • ipqs-email-reputation

    Runs reputation on email addresses.

  • ipqs-file-scan

    Uploads and scans a file using IPQualityScore Malware File Scanner API.

  • ipqs-ip-reputation

    Runs reputation on IPs.

  • ipqs-password-leaked

    Checks if a password has been leaked in known breaches.

  • ipqs-phone-reputation

    Runs validation and reputation on phone numbers.

  • ipqs-url-file-scan

    Scans a URL using IPQualityScore Malware File Scanner API.

  • ipqs-url-reputation

    Runs reputation on URLs.

  • ipqs-username-leaked

    Checks if a username has been leaked in known breaches.

  • url Deprecated

    Deprecated. 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")