RelayShield
RelayShield real-time identity-compromise and agent-security threat intelligence. Implements the generic `domain`/`ip`/`email` reputation commands (auto-invoked by existing enrichment playbooks) plus RelayShield-specific commands for MCP server registry risk, certificate expiry, and supply-chain vendor risk. "No known finding" from any command means nothing was flagged in the sources RelayShield actually queried. It is deliberately never mapped to a "Good" DBotScore, since that would claim more certainty than the data supports. See the README's DBotScore section for the exact mapping.
Data Enrichment & Threat Intelligence · RelayShield
Details
| ID | RelayShield |
|---|---|
| Provider | RelayShield |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 6.8.0 |
| Docker Image | demisto/python3:3.12.14.12343672 |
README
RelayShield real-time identity-compromise and agent-security threat intelligence.
This integration implements the generic domain, ip, and email reputation commands,
which are automatically invoked by any existing enrichment playbook that calls generic
reputation commands, with no playbook changes needed. It adds three RelayShield-specific
commands for MCP server registry risk, certificate expiry, and supply-chain vendor risk.
This integration was integrated and tested with version 1.0 of the RelayShield API.
Configure RelayShield in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL | The RelayShield API base URL. | True |
| API Key | The RelayShield API key, sent as the X-RS-API-KEY header. |
True |
| Trust any certificate (not secure) | Whether to trust any TLS certificate. | False |
| Use system proxy settings | Whether to route requests through the system proxy. | False |
| Source Reliability | Reliability of the source providing the intelligence data. | False |
DBotScore mapping
A clean result (“no known finding”) maps to DBotScore Unknown (0), never Good (1).
“No known finding” means nothing was flagged in the sources RelayShield actually queried,
which is not a verified-safe guarantee.
| RelayShield verdict | DBotScore |
|---|---|
| CRITICAL | 3 (Bad) |
| HIGH | 3 (Bad) |
| MEDIUM | 2 (Suspicious) |
| LOW | 2 (Suspicious) |
| No known finding | 0 (Unknown) |
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.
domain
Check a domain for phishing-lookalike/typosquat risk, presence in RelayShield’s criminal IOC corpus, and set a DBotScore.
Base Command
domain
Input
| Argument Name | Description | Required |
|---|---|---|
| domain | A comma-separated list of domains to check. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| RelayShield.Domain.queried | String | The domain queried. |
| RelayShield.Domain.verdict | String | The RelayShield verdict (CRITICAL/HIGH/MEDIUM/LOW), absent if no known finding. |
| RelayShield.Domain.findings | Unknown | The list of findings, if any. |
| Domain.Name | String | The domain name. |
| DBotScore.Indicator | String | The indicator that was tested. |
| DBotScore.Type | String | The indicator type. |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
| DBotScore.Score | Number | The actual score. |
| DBotScore.Reliability | String | The reliability of the source providing the intelligence data. |
ip
Check an IP address for reputation, malicious/suspicious votes, and set a DBotScore.
Base Command
ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | A comma-separated list of IP addresses to check. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| RelayShield.IP.queried | String | The IP address queried. |
| RelayShield.IP.reputation | Number | The community reputation score. |
| RelayShield.IP.malicious_votes | Number | The malicious vote count. |
| IP.Address | String | The IP address. |
| DBotScore.Indicator | String | The indicator that was tested. |
| DBotScore.Type | String | The indicator type. |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
| DBotScore.Score | Number | The actual score. |
| DBotScore.Reliability | String | The reliability of the source providing the intelligence data. |
Check an email address for breach exposure and active stolen-session risk, and set a DBotScore.
Base Command
email
Input
| Argument Name | Description | Required |
|---|---|---|
| A comma-separated list of email addresses to check. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| RelayShield.Email.queried | String | The email address queried. |
| RelayShield.Email.breach_found | Boolean | Whether the email address appears in a known breach. |
| RelayShield.Email.breach_sources | Unknown | The list of breach sources the email address was found in. |
| RelayShield.Email.session_risk_found | Boolean | Whether an active stolen session was found. |
| RelayShield.Email.sessions | Unknown | The list of stolen sessions found, if any. |
| DBotScore.Indicator | String | The indicator that was tested. |
| DBotScore.Type | String | The indicator type. |
| DBotScore.Vendor | String | The vendor used to calculate the score. |
| DBotScore.Score | Number | The actual score. |
| DBotScore.Reliability | String | The reliability of the source providing the intelligence data. |
relayshield-mcp-registry-risk
Assess an MCP server URL or package name for typosquat/supply-chain/registry risk before an agent connects to it.
Base Command
relayshield-mcp-registry-risk
Input
| Argument Name | Description | Required |
|---|---|---|
| server_url | The full URL of the MCP server to check. Provide this or package_name. | Optional |
| package_name | The package name of the MCP server if no server_url is available. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| RelayShield.MCPRegistryRisk.queried | String | The server URL or package name queried. |
| RelayShield.MCPRegistryRisk.verdict | String | The RelayShield verdict, absent if no known finding. |
| RelayShield.MCPRegistryRisk.findings | Unknown | The list of findings, if any. |
relayshield-cert-expiry
Check a domain’s TLS certificate expiry risk.
Base Command
relayshield-cert-expiry
Input
| Argument Name | Description | Required |
|---|---|---|
| domain | The domain to check. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| RelayShield.CertExpiry.domain | String | The domain checked. |
| RelayShield.CertExpiry.days_remaining | Number | The days until certificate expiry. |
| RelayShield.CertExpiry.risk_level | String | The risk level (CRITICAL/HIGH/MEDIUM/LOW). |
relayshield-supply-chain
Check up to 10 vendor domains (or emails) for combined breach/infostealer risk.
Base Command
relayshield-supply-chain
Input
| Argument Name | Description | Required |
|---|---|---|
| vendor_domains | A comma-separated list of up to 10 vendor domains to check. Provide this or vendor_emails. | Optional |
| vendor_emails | A comma-separated list of vendor email addresses. The domain portion is extracted automatically. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| RelayShield.SupplyChain.domains_checked | Number | The number of vendor domains checked. |
| RelayShield.SupplyChain.highest_risk | String | The highest risk level found across all vendors checked. |
| RelayShield.SupplyChain.critical_vendors | Unknown | The list of vendors flagged as critical risk. |
Configuration parameters
url— Server URL (required)credentials— API Key (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsintegrationReliability— Source Reliability
Commands (6)
-
domainCheck a domain for phishing-lookalike/typosquat risk, presence in RelayShield's criminal IOC corpus, and set a DBotScore.
-
emailCheck an email address for breach exposure and active stolen-session risk, and set a DBotScore.
-
ipCheck an IP address for reputation, malicious/suspicious votes, and set a DBotScore.
-
relayshield-cert-expiryCheck a domain's TLS certificate expiry risk.
-
relayshield-mcp-registry-riskAssess an MCP server URL or package name for typosquat/supply-chain/registry risk before an agent connects to it.
-
relayshield-supply-chainCheck up to 10 vendor domains (or emails) for combined breach/infostealer risk.
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401,F403 from CommonServerUserPython import * # noqa: F401,F403 from typing import Any """ CONSTANTS """ # CRITICAL/HIGH findings are treated as Bad; MEDIUM/LOW findings (still real # findings, just lower severity) are treated as Suspicious. no_known_finding # is never mapped to Good -- RelayShield's own docs are explicit that a clean # result means "nothing was found in the sources and scope actually queried," # not "verified safe." Consistent with the same principle already applied in # relayshield_smolagents_tool.py and the TI demo. DBOT_SCORE_MAP = { "CRITICAL": 3, # Common.DBotScore.BAD "HIGH": 3, "MEDIUM": 2, # Common.DBotScore.SUSPICIOUS "LOW": 2, } def score_for(verdict: str | None, found: bool) -> int: """Maps a RelayShield verdict + found flag to a DBotScore integer. Deliberately never returns Good(1) for a clean result -- only Unknown(0) or higher, per the no_known_finding-is-not-verified-safe principle. """ if not found or verdict is None or verdict in ("NONE", "CLEAN"): return 0 # Common.DBotScore.NONE (Unknown) return DBOT_SCORE_MAP.get(verdict, 2) """ CLIENT CLASS """ class Client(BaseClient): """Thin wrapper over RelayShield's metered API. All commands POST a payload and get back {"ok": bool, "data": {...}} or {"ok": false, "error": "..."} -- _http_request already raises DemistoException on a non-2xx response, so callers only need to unwrap "data".""" def __init__(self, base_url: str, api_key: str, verify: bool, proxy: bool): super().__init__(base_url=base_url, verify=verify, proxy=proxy) self._api_key = api_key def call(self, path: str, payload: dict) -> dict: response = self._http_request( method="POST", url_suffix=path, json_data=payload, headers={"Content-Type": "application/json", "X-RS-API-KEY": self._api_key}, ) if not response.get("ok", True): raise DemistoException(response.get("error", "RelayShield API returned an error")) return response.get("data", {}) """ COMMAND FUNCTIONS """ def test_module(client: Client) -> str: client.call("/v1/metered/domain", {"domain": "relayshield.net"}) return "ok" def domain_command(client: Client, args: dict[str, Any], reliability: str) -> list[CommandResults]: domains = argToList(args.get("domain")) if not domains: raise ValueError("domain is required") results = [] for domain in domains: data = client.call("/v1/metered/domain", {"domain": domain}) findings = data.get("findings", []) verdict = data.get("verdict") score = score_for(verdict, bool(findings)) dbot_score = Common.DBotScore( indicator=domain, indicator_type=DBotScoreType.DOMAIN, integration_name="RelayShield", score=score, reliability=reliability, ) indicator = Common.Domain(domain=domain, dbot_score=dbot_score) readable = tableToMarkdown( f"RelayShield Domain Reputation: {domain}", { "Verdict": verdict or "no_known_finding", "Findings": len(findings), "Checked At": data.get("checked_at"), }, ) results.append( CommandResults( outputs_prefix="RelayShield.Domain", outputs_key_field="queried", outputs=data, indicator=indicator, readable_output=readable, raw_response=data, ) ) return results def ip_command(client: Client, args: dict[str, Any], reliability: str) -> list[CommandResults]: ips = argToList(args.get("ip")) if not ips: raise ValueError("ip is required") results = [] for ip in ips: data = client.call("/v1/metered/ip-intel", {"ip": ip}) reputation = data.get("reputation", 0) malicious = data.get("malicious_votes", 0) found = bool(malicious) or reputation not in (0, None) verdict = "HIGH" if malicious else ("MEDIUM" if reputation and reputation > 0 else None) score = score_for(verdict, found) dbot_score = Common.DBotScore( indicator=ip, indicator_type=DBotScoreType.IP, integration_name="RelayShield", score=score, reliability=reliability, ) indicator = Common.IP( ip=ip, dbot_score=dbot_score, asn=data.get("as_owner"), geo_country=data.get("country"), ) readable = tableToMarkdown( f"RelayShield IP Reputation: {ip}", { "Reputation": reputation, "Malicious Votes": malicious, "AS Owner": data.get("as_owner"), "Country": data.get("country"), }, ) results.append( CommandResults( outputs_prefix="RelayShield.IP", outputs_key_field="queried", outputs=data, indicator=indicator, readable_output=readable, raw_response=data, ) ) return results def email_command(client: Client, args: dict[str, Any], reliability: str) -> list[CommandResults]: emails = argToList(args.get("email")) if not emails: raise ValueError("email is required") results = [] for email in emails: breach_data = client.call("/v1/metered/breach", {"email": email}) session_data = client.call("/v1/metered/session-risk", {"email": email}) breach_found = bool(breach_data.get("found")) session_found = bool(session_data.get("found")) found = breach_found or session_found severities = [s.get("severity") for s in session_data.get("sessions", []) if s.get("severity")] highest = max(severities, key=lambda s: DBOT_SCORE_MAP.get(s, 0), default=None) if breach_found and highest is None: highest = "MEDIUM" score = score_for(highest, found) dbot_score = Common.DBotScore( indicator=email, indicator_type=DBotScoreType.EMAIL, integration_name="RelayShield", score=score, reliability=reliability, ) indicator = Common.EMAIL(address=email, dbot_score=dbot_score) combined = { "queried": email, "breach_found": breach_found, "breach_sources": breach_data.get("sources", []), "session_risk_found": session_found, "sessions": session_data.get("sessions", []), } readable = tableToMarkdown( f"RelayShield Identity Exposure: {email}", { "Breach Found": breach_found, "Active Session Exposure": session_found, }, ) results.append( CommandResults( outputs_prefix="RelayShield.Email", outputs_key_field="queried", outputs=combined, indicator=indicator, readable_output=readable, raw_response=combined, ) ) return results def mcp_registry_risk_command(client: Client, args: dict[str, Any]) -> CommandResults: server_url = args.get("server_url") package_name = args.get("package_name") if not server_url and not package_name: raise ValueError("server_url or package_name is required") payload = {k: v for k, v in {"server_url": server_url, "package_name": package_name}.items() if v} data = client.call("/v1/metered/mcp-registry-risk", payload) findings = data.get("findings", []) verdict = data.get("verdict") readable = tableToMarkdown( f"RelayShield MCP Registry Risk: {data.get('queried', server_url or package_name)}", { "Verdict": verdict or "no_known_finding", "Findings": [f.get("type", "unknown") for f in findings], }, ) return CommandResults( outputs_prefix="RelayShield.MCPRegistryRisk", outputs_key_field="queried", outputs=data, readable_output=readable, raw_response=data, ) def cert_expiry_command(client: Client, args: dict[str, Any]) -> CommandResults: domain = args.get("domain") if not domain: raise ValueError("domain is required") data = client.call("/v1/metered/cert-expiry", {"domain": domain}) readable = tableToMarkdown( f"RelayShield Certificate Expiry: {domain}", { "Days Remaining": data.get("days_remaining"), "Risk Level": data.get("risk_level"), }, ) return CommandResults( outputs_prefix="RelayShield.CertExpiry", outputs_key_field="domain", outputs=data, readable_output=readable, raw_response=data, ) def supply_chain_command(client: Client, args: dict[str, Any]) -> CommandResults: vendor_domains = argToList(args.get("vendor_domains")) vendor_emails = argToList(args.get("vendor_emails")) if not vendor_domains and not vendor_emails: raise ValueError("vendor_domains or vendor_emails is required") payload: dict[str, Any] = {} if vendor_domains: payload["vendor_domains"] = vendor_domains if vendor_emails: payload["vendor_emails"] = vendor_emails data = client.call("/v1/metered/supply-chain", payload) readable = tableToMarkdown( "RelayShield Supply Chain Risk", { "Domains Checked": data.get("domains_checked", 0), "Highest Risk": data.get("highest_risk"), "Critical Vendors": data.get("critical_vendors", []), }, ) return CommandResults( outputs_prefix="RelayShield.SupplyChain", outputs_key_field="checked_at", outputs=data, readable_output=readable, raw_response=data, ) """ MAIN FUNCTION """ def main() -> None: params = demisto.params() args = demisto.args() command = demisto.command() demisto.debug(f"Command being called is {command}") try: # `or {}` rather than a .get() default: XSOAR stores an unset encrypted # parameter as an explicit None, and params.get("api_key", {}) returns # that None rather than the default, so chaining .get() on it raises # AttributeError before the friendlier check below can run. # Parsed inside the try so a misconfigured instance surfaces through # return_error like every other failure, instead of escaping main(). api_key = (params.get("credentials") or {}).get("password") or (params.get("api_key") or {}).get("password") if not api_key: raise DemistoException("API Key is required. Set it in the integration instance configuration.") base_url = (params.get("url") or "https://api.relayshield.net").rstrip("/") verify_certificate = not params.get("insecure", False) proxy = params.get("proxy", False) reliability = params.get("integrationReliability") or DBotScoreReliability.B if not DBotScoreReliability.is_valid_type(reliability): raise DemistoException("RelayShield error: Please provide a valid value for the Source Reliability parameter.") client = Client(base_url=base_url, api_key=api_key, verify=verify_certificate, proxy=proxy) if command == "test-module": return_results(test_module(client)) elif command == "domain": return_results(domain_command(client, args, reliability)) elif command == "ip": return_results(ip_command(client, args, reliability)) elif command == "email": return_results(email_command(client, args, reliability)) elif command == "relayshield-mcp-registry-risk": return_results(mcp_registry_risk_command(client, args)) elif command == "relayshield-cert-expiry": return_results(cert_expiry_command(client, args)) elif command == "relayshield-supply-chain": return_results(supply_chain_command(client, args)) else: raise NotImplementedError(f"{command} is not an implemented command.") except Exception as e: return_error(f"Failed to execute {command} command.\nError:\n{str(e)}") if __name__ in ("__main__", "__builtin__", "builtins"): main()