CybleThreatIntel

Cyble Threat Intelligence for Vision Users. Must have access to Cyble TAXII Feed to access the threat intelligence.

Data Enrichment & Threat Intelligence · Cyble Threat Intel · Feed

Details

IDCybleThreatIntel
ProviderCyble
CategoryData Enrichment & Threat Intelligence
From Version6.2.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Cyble Threat Intelligence – Cortex XSOAR Integration

This integration enables Cortex XSOAR to ingest and query Indicators of
Compromise (IOCs) from the Cyble Vision API.
It supports two capabilities:

  1. IOC Lookup (Interactive command for analysts)
  2. IOC Fetching (Fetch Indicators)

Overview

The Cyble Vision platform provides enriched, high-fidelity threat
intelligence including malware associations, threat actor links,
behaviour tags, risk scoring, and more.
This integration allows XSOAR to:

  • Pull fresh IOCs at scheduled intervals
  • Tag, score, and store indicators in the Cortex XSOAR indicator store
  • Support analyst lookups for a single IOC via the command line or
    playbooks

Configuration

Required Parameters

Parameter Description Example
Base URL Cyble Vision API endpoint https://api.cyble.ai/engine/api/v4
API Key (Access Token) Cyble Vision API Bearer token (stored securely in XSOAR)
First fetch time (hours) Number of hours to fetch backward on first run 2
  (1–3 hours allowed)  
Indicator Fetch Limit Maximum indicators per API page 100

Fetch Behavior

  • Fetch is performed in 1-hour chunks until the full range is covered.
  • Each page of IOCs is inserted immediately using
    demisto.createIndicators.
  • Fetch uses a retry mechanism (up to 5 attempts per page).
  • last_run is updated after every chunk.
  • Supported fetch window: 1–3 hours (anything outside is
    automatically corrected).

Commands

📌 1. cyble-vision-ioc-lookup

Lookup a single IOC using the Cyble Vision API.

Command

!cyble-vision-ioc-lookup ioc=<IOC_VALUE>

Arguments

Name Required Description
ioc Yes IOC string (IP / Domain / URL / Hash)

Outputs

Prefix: CybleIntel.IOCLookup

Field Description
IOC IOC value
IOC Type Type (IP / Domain / URL / Hash)
First Seen UTC timestamp
Last Seen UTC timestamp
Risk Score 0–100
Sources Reporting sources
Behaviour Tags Tags assigned by Cyble
Confidence Rating Low / Medium / High
Target Countries Target geography
Target Regions Regions affected
Target Industries Target verticals
Related Malware Linked malware families
Related Threat Actors Associated threat actors

Example

!cyble-vision-ioc-lookup ioc=45.67.23.9


📌 2. fetch-indicators

Fetch IOCs from Cyble Vision and insert them into XSOAR’s indicator store.

Execution

This command is not run manually.
It is used by the XSOAR engine when Fetches Indicators is enabled.

Behavior

  • Builds indicators with:

    • cybleverdict
    • cybleriskscore
    • cyblefirstseen
    • cyblelastseen
    • cyblebehaviourtags
    • cyblesources
    • cybletargetcountries
    • cybletargetregions
    • cybletargetindustries
    • cyblerelatedmalware
    • cyblerelatedthreatactors
  • Automatically maps each IOC into XSOAR Indicator fields.
  • Updates last_run after each successful chunk.

Known Limitations

  • Fetching supports hours only (days are not supported).
  • Maximum initial backfill is 3 hours.

Support

For issues, contact support@cyble.com
or your assigned Cyble Technical Advisor.

Configuration parameters

  • tlp_color — Traffic Light Protocol Color
  • feedFetchInterval — Feed Fetch Interval
  • feed — Fetch indicators
  • feedReliability — Source Reliability (required)
  • feedReputation — Indicator Reputation
  • feedTags — Tags
  • feedExpirationPolicy
  • feedExpirationInterval
  • feedBypassExclusionList — Bypass exclusion list
  • base_url — Base URL
  • credentials — Access Token
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • collection — Collection Name
  • discovery_service — Discovery Service
  • initial_interval — First fetch time (by days) (required)
  • limit — Indicator Fetch Limit

Commands (3)

  • cyble-vision-fetch-taxii Deprecated

    Deprecated command. Do not use.

  • cyble-vision-get-collection-names Deprecated

    Deprecated command. Do not use.

  • cyble-vision-ioc-lookup

    Lookup a single IOC using the Cyble Vision API.

import pytest
from unittest.mock import patch, MagicMock
from datetime import datetime

from CybleThreatIntel import (
    Client,
    calculate_verdict,
    get_time_range,
    epoch_to_iso,
    fmt_date,
    cyble_ioc_lookup_command,
    fetch_indicators_command,
    VerdictEnum,
)


# -------------------------------------------------------------------
#   HTTP POST – SUCCESS
# -------------------------------------------------------------------
@patch("CybleThreatIntel.requests.post")
def test_http_post_success(mock_post):
    resp = MagicMock()
    resp.status_code = 200
    resp.json.return_value = {"ok": True}
    mock_post.return_value = resp

    client = Client({"base_url": "https://example.com", "access_token": {"password": "a"}})
    r = client.http_post("/y/iocs", {"x": 1})

    assert r == {"ok": True}
    mock_post.assert_called_once()


# -------------------------------------------------------------------
#   HTTP POST – FAILURE
# -------------------------------------------------------------------
@patch("CybleThreatIntel.requests.post")
def test_http_post_failure(mock_post):
    resp = MagicMock()
    resp.status_code = 500
    resp.raise_for_status.side_effect = Exception("Server Error")
    resp.text = "error"
    mock_post.return_value = resp

    client = Client({"base_url": "https://example.com", "access_token": {"password": "a"}})

    with pytest.raises(Exception):
        client.http_post("/y/iocs", {})


# -------------------------------------------------------------------
#   DATE RANGE LOGIC
# -------------------------------------------------------------------
def test_get_time_range_without_last_run():
    now = datetime.utcnow()
    gte, lte = get_time_range(5, {})

    g = datetime.fromisoformat(gte)
    assert (now - g).seconds <= 5 * 3600 + 5  # small tolerance


def test_get_time_range_with_last_run():
    now = datetime.utcnow().isoformat()
    gte, lte = get_time_range(6, {"last_fetch": now})

    assert gte == now


# -------------------------------------------------------------------
#   EPOCH TO ISO
# -------------------------------------------------------------------
def test_epoch_to_iso():
    ts = 1700000000
    result = epoch_to_iso(ts)
    assert result.endswith("Z")


# -------------------------------------------------------------------
#   VERDICT MATRIX
# -------------------------------------------------------------------
@pytest.mark.parametrize(
    "risk,conf,expected",
    [
        (10, "Low", VerdictEnum.UNKNOWN.value),
        (10, "Medium", VerdictEnum.SUSPICIOUS.value),
        (10, "High", VerdictEnum.NOT_MALICIOUS.value),
        (30, "Low", VerdictEnum.UNKNOWN.value),
        (30, "Medium", VerdictEnum.SUSPICIOUS.value),
        (70, "High", VerdictEnum.MALICIOUS.value),
        (80, "Low", VerdictEnum.SUSPICIOUS.value),
        (80, "High", VerdictEnum.MALICIOUS.value),
    ],
)
def test_calculate_verdict_values(risk, conf, expected):
    assert calculate_verdict(risk, conf) == expected


# -------------------------------------------------------------------
#   IOC LOOKUP COMMAND – NO RESULTS
# -------------------------------------------------------------------
@patch("CybleThreatIntel.return_error")
@patch("CybleThreatIntel.Client.ioc_lookup")
def test_ioc_lookup_no_results(mock_lookup, mock_return):
    mock_lookup.return_value = {"data": {"iocs": []}}

    c = Client({"base_url": "x", "access_token": {"password": "a"}})
    result = cyble_ioc_lookup_command(c, {"ioc": "1.1.1.1"})

    assert "No results found" in result.readable_output


# -------------------------------------------------------------------
#   IOC LOOKUP – WITH RESULTS
# -------------------------------------------------------------------


@patch("CybleThreatIntel.Client.ioc_lookup")
def test_ioc_lookup_success(mock_lookup):
    mock_lookup.return_value = {"data": {"iocs": [{"ioc": "SAMPLE_IOC", "ioc_type": "custom", "first_seen": 1700000000}]}}

    c = Client({"base_url": "x", "access_token": {"password": "a"}})
    result = cyble_ioc_lookup_command(c, {"ioc": "SAMPLE_IOC"})

    assert result.outputs["IOC"] == "SAMPLE_IOC"


# -------------------------------------------------------------------
#   FETCH INDICATORS – MAIN LOGIC
# -------------------------------------------------------------------


# -------------------------------------------------------------------
#   FETCH INDICATORS – RETRY FAILURE
# -------------------------------------------------------------------
@patch("CybleThreatIntel.demisto")
def test_fetch_indicators_retry_fail(mock_demisto):
    mock_demisto.args.return_value = {}
    mock_demisto.getLastRun.return_value = {}

    client = Client({"base_url": "x", "access_token": {"password": "a"}})

    client.fetch_iocs = MagicMock(side_effect=Exception("fail"))

    params = {"first_fetch": 1, "max_fetch": 50}

    count = fetch_indicators_command(client, params)

    assert count == 0


from unittest.mock import Mock


def test_calculate_verdict_invalid_inputs():
    assert calculate_verdict("bad", "weird") == "Unknown"
    assert calculate_verdict(-10, "low") == "Unknown"
    assert calculate_verdict(200, "high") == "Malicious"


def test_ioc_lookup_missing_argument(mocker):
    client = Mock()
    mocker.patch("CybleThreatIntel.return_error", side_effect=Exception("Missing required argument: ioc"))

    with pytest.raises(Exception) as e:
        cyble_ioc_lookup_command(client, {})
    assert "Missing required argument: ioc" in str(e.value)


def test_client_init_empty(mocker):
    params = {}
    client = Client(params)
    assert client.base_url == ""
    assert client.access_token == ""
    assert client.headers["Authorization"] == "Bearer "


def test_client_http_post_failure(mocker):
    client = Client({"base_url": "http://test.com", "access_token": {"password": "token"}})
    mock_resp = mocker.Mock()
    mock_resp.status_code = 400
    mock_resp.text = "Bad request"
    mock_resp.raise_for_status.side_effect = Exception("HTTP error")
    mocker.patch("requests.post", return_value=mock_resp)

    try:
        client.http_post("/endpoint", {"key": "value"})
    except Exception as e:
        assert "HTTP error" in str(e)


def test_epoch_to_iso_invalid():
    assert epoch_to_iso("invalid") is None


def test_fmt_date_none_and_invalid():
    assert fmt_date(None) == "None"
    assert "invalid" in fmt_date("invalid")


def test_client_init_edge_case(mocker):
    # edge case: empty access_token dict and trailing slash in URL
    client = Client({"base_url": "https://api.test.com/", "access_token": {"password": ""}})
    assert client.base_url == "https://api.test.com"
    assert client.access_token == ""


def test_epoch_to_iso_invalid_timestamp():
    # invalid timestamp should return None
    assert epoch_to_iso("not_a_timestamp") is None


def test_calculate_verdict_edge_cases():
    # risk_score None, confidence_rating None
    assert calculate_verdict(None, None) == "Unknown"
    # extreme low/high values beyond 0-100
    assert calculate_verdict(-50, "Low") == "Unknown"
    assert calculate_verdict(150, "High") == "Malicious"


def test_get_time_range_first_run_with_last_fetch():
    last_run = {"last_fetch": "2025-12-01T00:00:00"}
    gte, lte = get_time_range(5, last_run)
    assert gte == "2025-12-01T00:00:00"