secops_mcp_get_ioc_matches_formatter_bug
- Dominant language
- Python
- Stars
- 528
- Forks
- 141
- Avg merge
- 3d 2h
- Merged PRs (30d)
- 6
Description
`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):
```python
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:
```diff
-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.
Contributor guide
Assessment
This issue has not been assessed yet.