#906·mvt

Indicators._process_indicator crashes on any STIX pattern whose value contains "="

Author: DonnchaCCreated Aug 27, 2026Updated Aug 27, 2026

Indicators._process_indicator crashes on any STIX pattern whose value contains =

Summary

_process_indicator() splits the STIX pattern on = and unpacks exactly two parts:

python
# src/mvt/common/indicators.py:119
key, value = indicator.get("pattern", "").strip("[]").split("=")

Any indicator whose value contains an = raises ValueError: too many values to unpack. The exception is not caught in parse_stix2(), load_indicators_files() or _check_stix2_env_variable(), so it propagates out of the CLI: mvt-ios check-iocs / mvt-android check-iocs (and any --iocs run) abort with a traceback and no indicators from any file are loaded, not just the offending one.

The obvious trigger is a url:value indicator with a query string, e.g. [url:value='https://example.com/track?id=1']. url:value is a supported type, and one-click exploit-delivery links with query parameters are exactly the kind of URL people will want to add. file:path values can also legitimately contain =.

Reproduction

bad.stix2:

json
{"type":"bundle","id":"bundle--00000000-0000-4000-8000-000000000000","objects":[
 {"type":"malware","spec_version":"2.1","id":"malware--00000000-0000-4000-8000-000000000001","created":"2026-01-01T00:00:00Z","modified":"2026-01-01T00:00:00Z","name":"Demo","is_family":false},
 {"type":"indicator","spec_version":"2.1","id":"indicator--00000000-0000-4000-8000-000000000002","created":"2026-01-01T00:00:00Z","modified":"2026-01-01T00:00:00Z","indicator_types":["malicious-activity"],"pattern":"[domain-name:value='good-domain.example']","pattern_type":"stix","valid_from":"2026-01-01T00:00:00Z"},
 {"type":"indicator","spec_version":"2.1","id":"indicator--00000000-0000-4000-8000-000000000003","created":"2026-01-01T00:00:00Z","modified":"2026-01-01T00:00:00Z","indicator_types":["malicious-activity"],"pattern":"[url:value='https://example.com/track?id=1']","pattern_type":"stix","valid_from":"2026-01-01T00:00:00Z"},
 {"type":"relationship","spec_version":"2.1","id":"relationship--00000000-0000-4000-8000-000000000004","created":"2026-01-01T00:00:00Z","modified":"2026-01-01T00:00:00Z","relationship_type":"indicates","source_ref":"indicator--00000000-0000-4000-8000-000000000002","target_ref":"malware--00000000-0000-4000-8000-000000000001"},
 {"type":"relationship","spec_version":"2.1","id":"relationship--00000000-0000-4000-8000-000000000005","created":"2026-01-01T00:00:00Z","modified":"2026-01-01T00:00:00Z","relationship_type":"indicates","source_ref":"indicator--00000000-0000-4000-8000-000000000003","target_ref":"malware--00000000-0000-4000-8000-000000000001"}
]}
$ MVT_STIX2=bad.stix2 mvt-ios check-iocs ./results
...
  File ".../mvt/common/indicators.py", line 72, in _check_stix2_env_variable
    self.parse_stix2(path)
  File ".../mvt/common/indicators.py", line 317, in parse_stix2
    self._process_indicator(indicator, collection)
  File ".../mvt/common/indicators.py", line 119, in _process_indicator
    key, value = indicator.get("pattern", "").strip("[]").split("=")
    ^^^^^^^^^^
ValueError: too many values to unpack (expected 2)

Same result via Indicators().load_indicators_files(["bad.stix2"]). Tested on MVT 2026.7.29; the line is unchanged on main.

Impact

Latent today: none of the feeds in indicators.yaml currently contain a value with = (checked mvt-indicators, AmnestyTech pegasus.stix2 / novispy.stix2, and ECHAP stalkerware.stix2). But the first contributor to add a URL with a query string to any published feed will break check-iocs for every user who runs download-iocs, with no way to recover other than deleting the file from the local indicators folder.

Suggested fix

  1. Split only on the first =:

    python
    key, value = indicator.get("pattern", "").strip("[]").split("=", 1)
  2. Make parse_stix2() resilient to a single bad indicator, in the same spirit as the existing JSON-decode handling: wrap the _process_indicator() call so a malformed pattern logs a warning with the indicator id and is skipped, instead of aborting the run.

  3. Add a case to tests/common/test_indicators.py with a url:value pattern containing ?a=b, asserting the URL is loaded intact and the domain indicator in the same file is still loaded.

Related brittleness worth handling in the same change, if the pattern parsing is being touched anyway: _add_indicator() strips every ' from the value, so a value containing an apostrophe would be silently altered. A small regex over the expected shape (^\[(?P<key>[^=\s]+)\s*=\s*'(?P<value>.*)'\]$) would handle both cases and reject anything else explicitly.