# Threat intelligence matcher A Python CLI that brings STIX indicators next to the network events they match. The included report uses synthetic data and fixed timestamps. It does not connect to a school network or subscribe to a live threat feed. The useful output is the evidence behind a match. For example, `GATEWAY.SAMPLE.TEST.` and `gateway.sample.test` become the same DNS observable, while `sub.gateway.sample.test` remains a different name. A withdrawn indicator is excluded even when an earlier version would have matched. ## Run the example Requires Python 3.11 or newer. There are no third-party dependencies. ```bash python tip.py \ --bundle fixtures/indicators.stix.json \ --logs fixtures/network-logs.json \ --as-of 2026-10-13T12:00:00Z \ --sample \ --out demo.json python -m unittest -v ``` The fixture produces 3 matching events from 9 log events. Those counts describe this fixture only. `demo.json` contains every decision, including excluded indicators and unmatched traffic. ## What the matcher accepts | Input | Behavior | | --- | --- | | STIX 2.1 bundle or TAXII envelope | Reads the `objects` array. Ignores object types other than indicators | | Several files or feeds | Repeat `--bundle` or `--taxii-url` to aggregate them | | New versions of the same STIX ID | Keeps the greatest `modified` timestamp. Retains sources for identical copies of that version | | Conflicting content for the same ID and version | Stops instead of choosing an arbitrary copy | | A single IPv4, IPv6 or domain equality | Parses a bounded grammar, normalizes the value, then matches exactly | | Compound expressions, CIDR patterns or other object types | Records `unsupported`. It does not evaluate or simplify them | | Revoked or expired indicators | Excludes them from current triage | | An event outside an indicator's validity interval | Does not match it | This is a restricted STIX pattern implementation, not a complete STIX validator. Supporting additional pattern syntax requires an actual parser and explicit matching semantics. The code never calls `eval`. The analysis policy uses the latest supplied indicator version and requires it to be valid at `--as-of`. It then checks each event's timestamp against that indicator's validity interval. This policy suits current triage. It is not a reconstruction of what an analyst would have known at a historical time. ## Use a TAXII collection Supply the final HTTPS `objects` endpoint for a collection you can access. If the provider requires a bearer token, set `TAXII_BEARER_TOKEN` through your shell or secret manager. Also set `TAXII_TOKEN_ORIGIN` to its HTTPS origin, such as `https://feed.example.test`. The CLI refuses to send the token to any other hostname or port, including when several `--taxii-url` options are supplied. It never writes that token to its report. ```bash python tip.py \ --taxii-url https://feed.example.test/api/collections/lab/objects/ \ --logs your-normalized-logs.json \ --out report.json ``` `feed.example.test` is a documentation address. Replace it with your provider's endpoint. The CLI uses TAXII 2.1 content negotiation and requests the latest indicator versions. It follows opaque `next` cursors while preserving query filters. When a server omits `next`, it can advance using `X-TAXII-Date-Added-Last` as specified by TAXII 2.1. The optional `--added-after` parameter filters by when an object was added to the TAXII collection, not its STIX `modified` timestamp. This CLI has no persistent feed cache. A delta fetch alone is not a full inventory. For incremental operation, also supply your retained STIX snapshot through `--bundle`, including prior revocations. HTTPS redirects are refused to avoid sending a feed credential to another endpoint. Responses are bounded to 10 MiB per page and 100 pages. Pagination errors stop the run rather than silently returning a partial feed. Authentication and rate-limit errors are returned to the caller. Provider-specific OAuth flows and retry scheduling belong in an integration around this CLI. No authenticated live provider was used to produce this example. Pagination and error handling were verified against local test doubles. ## Log schema The input is a JSON array. Every event needs a unique `id` and a UTC `timestamp`. Include at least one observable field. Other fields, such as `dst_port`, remain in the report as context. ```json [ { "id": "flow-01", "timestamp": "2026-10-13T09:00:00Z", "src_ip": "192.0.2.10", "dst_ip": "198.51.100.24", "dst_port": 443, "protocol": "tcp" }, { "id": "dns-02", "timestamp": "2026-10-13T09:01:00Z", "src_ip": "192.0.2.11", "domain": "GATEWAY.SAMPLE.TEST.", "protocol": "dns" } ] ``` The supported observable fields are `src_ip`, `dst_ip` and `domain`. Both IP directions are checked. A match keeps the matching field and source indicator ID, so an analyst can return to the original event. ## Reading the report An indicator match is a lead for review, not confirmation of compromise. An IP may be shared or reassigned. A domain match needs context from the originating device. Likewise, `no-match` says only that these indicators did not match these events. STIX `confidence` is copied from the source. It is not a probability calculated by the matcher. The fixture's confidence values are invented test data and should never be presented as measured threat likelihood. | File | Purpose | | --- | --- | | `tip.py` | CLI, restricted pattern parser, TAXII client and matcher | | `fixtures/indicators.stix.json` | Synthetic STIX objects, including an older and revoked version of one indicator | | `fixtures/network-logs.json` | Synthetic flow and DNS records | | `make_fixtures.py` | Recreates those input files deterministically | | `demo.json` | Actual output from running the included fixture | | `test_tip.py` | Offline tests covering matching and TAXII pagination | ## Technical references Primary documentation consulted on October 13, 2026: | Reference | Implementation detail | | --- | --- | | [OASIS STIX 2.1](https://docs.oasis-open.org/cti/stix/v2.1/os/stix-v2.1-os.html) | Indicator versions, revocation, validity and pattern properties | | [OASIS TAXII 2.1](https://docs.oasis-open.org/cti/taxii/v2.1/os/taxii-v2.1-os.html) | Content negotiation, collection objects and pagination | | [RFC 5737](https://datatracker.ietf.org/doc/html/rfc5737) | Documentation-only IPv4 ranges | | [RFC 3849](https://datatracker.ietf.org/doc/html/rfc3849) | Documentation-only IPv6 prefix `2001:db8::/32` | | [RFC 2606](https://datatracker.ietf.org/doc/html/rfc2606) | Reserved `.test` domains |