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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions