Skip to content

secops_mcp_get_ioc_matches_formatter_bug #264

Description

@barnabys-drew

get_ioc_matches only outputs the first artifactIndicator field and drops match metadata

secops_mcp 0.1.3. secops_mcp/tools/ioc_matches.py::get_ioc_matches builds the output with next(iter(...)) on artifactIndicator, so when a match has multiple indicator fields (common: domain + url, or hashSha256 + fileName) only the first is reported. The formatter also writes only Type / Value / Sources — first/last seen, associated entity, category, severity, and confidence are all dropped.

A tier-1 SOC agent that uses this tool gets (type, value, sources) and cannot tell when the IoC fired, who/what triggered it, or how confident the source is.

Current code (ioc_matches.py lines 102–118):

if 'artifactIndicator' in match and isinstance(match['artifactIndicator'], dict):
    # Get the first key-value pair from artifactIndicator
    indicator_dict = match.get('artifactIndicator', {})
    if indicator_dict:
        indicator_type = next(iter(indicator_dict.keys()), 'Unknown')
        indicator_value = next(iter(indicator_dict.values()), 'Unknown')

sources = match.get('sources', [])
# ...
result += f'Type: {indicator_type}\n'
result += f'Value: {indicator_value}\n'
result += f'Sources: {sources_str}\n\n'

Fix — iterate all indicator fields and surface the missing metadata:

-if 'artifactIndicator' in match and isinstance(match['artifactIndicator'], dict):
-    indicator_dict = match.get('artifactIndicator', {})
-    if indicator_dict:
-        indicator_type = next(iter(indicator_dict.keys()), 'Unknown')
-        indicator_value = next(iter(indicator_dict.values()), 'Unknown')
-
-sources = match.get('sources', [])
-
-result += f'Type: {indicator_type}\n'
-result += f'Value: {indicator_value}\n'
-result += f'Sources: {sources_str}\n\n'
+indicators = []
+if isinstance(match, dict):
+    for k, v in (match.get('artifactIndicator') or {}).items():
+        indicators.append(f'{k}={v}')
+
+sources = match.get('sources', []) if isinstance(match, dict) else []
+first_seen = match.get('firstSeenTime', 'Unknown')
+last_seen  = match.get('lastSeenTime',  'Unknown')
+category   = match.get('category',      'Unknown')
+severity   = match.get('severity',      'Unknown')
+confidence = match.get('confidence',    'Unknown')
+associated = match.get('associatedEntity', 'Unknown')
+
+result += f'Indicator(s): {", ".join(indicators) or "Unknown"}\n'
+result += f'First Seen: {first_seen}\n'
+result += f'Last Seen:  {last_seen}\n'
+result += f'Category:   {category}\n'
+result += f'Severity:   {severity}\n'
+result += f'Confidence: {confidence}\n'
+result += f'Associated: {associated}\n'
+result += f'Sources:    {", ".join(sources) or "Unknown"}\n\n'

Verify the precise field names against secops/chronicle/ioc.py / the live API response before merging — some keys may use snake_case in the SDK shape.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions