{"slug": "i-built-an-offline-threat-hunting-cli-in-python-because-spinning-up-a-siem-for", "title": "I built an offline threat-hunting CLI in python because spinning up a SIEM for one log file is overkill", "summary": "A developer built ThreatLens, a Python CLI tool that scans log files for suspicious activity mapped to MITRE ATT&CK without requiring a server or internet connection. The tool processes approximately 70,000 events per second on a single core and can detect threats including privilege escalation, lateral movement, and defense evasion across JSON, EVTX, syslog, and CEF formats. ThreatLens correlates related events into attack chains and supports custom YAML, Sigma, or Python detection rules, with the ability to exit with a non-zero code when high-severity findings are present for pipeline integration.", "body_md": "so here's the situation i kept running into while studying for security+ and messing with sample log sets. i'd have a single evtx export or a json dump from some lab, and i wanted to know \"is there anything bad in here\" without standing up elastic or splunk or wazuh just to look at one file.\n\nevery time the answer was the same. spin up infra, ingest, write a query, wait. for one file. it's a lot.\n\nso i wrote threatlens. it's a python cli that reads a log file (or a folder) and tells you what looks suspicious, mapped to mitre att&ck. no server, no agent, no internet. you point it at a file and it scans.\n\n```\nthreatlens scan sample_data/sample_security_log.json\n```\n\nthat's it. on a 26-event sample it pulls out 1 critical (sam registry access), 8 high (lateral movement, a certutil download, scheduled task creation, an actual multi-stage chain), a couple medium brute-force hits. takes a fraction of a second.\n\nmy first version was just a big pile of regex and if-statements. it worked for like three detections and then became unmaintainable instantly. every new rule meant editing the core scanner. bad idea.\n\nso i split it. there's a detection engine, and rules live separately. you get the built-in modules (13 of them right now: brute force, priv esc, defense evasion, persistence, dns tunneling, kerberos stuff, etc) but you can also drop in your own yaml rules or sigma rules or even a python plugin if you need real logic.\n\na custom rule looks like this:\n\n```\n- id: suspicious-certutil-download\n  title: certutil used to download a file\n  severity: high\n  technique: T1059.003\n  match:\n    - field: process.command_line\n      contains: \"certutil\"\n    - field: process.command_line\n      contains: \"-urlcache\"\n```\n\ntwelve operators total (contains, regex, equals, in, gt, that kind of thing). nothing fancy but it covers most of what i actually needed.\n\nit correlates. a single \"new service created\" event isn't that interesting on its own. but new service, then a privilege escalation, then lateral movement, in order, from the same host? that's a chain. threatlens groups those and flags the chain as its own high-severity finding instead of three disconnected blips. that was the hardest part to get right and it's still kind of naive but it works on the samples i throw at it.\n\nit's pure python. i fully expected it to be slow. it's not great, but it's faster than i thought. single core, python 3.11, on my windows laptop:\n\nso roughly 70k events/sec. for python with no c extensions i'll take it. i'm not going to pretend it competes with the rust tools though. hayabusa and chainsaw do millions of events a minute and i'm not close. if you've got terabytes of logs, use those. threatlens is for when you've got one weird file and you want an answer now.\n\ninput formats it reads: json/ndjson, evtx (native windows event log via python-evtx), syslog (rfc 3164 and 5424), and cef. output goes to the terminal by default but you can dump json/csv, an html report with a severity donut, an interactive timeline, an att&ck navigator layer, or a stix bundle.\n\nyou can run it with `--fail-on high`\n\nand it exits with code 2 if anything high-or-above shows up. so you can drop it in a pipeline as a gate.\n\n```\nthreatlens scan logs/ --fail-on high\n```\n\ni didn't plan that one. someone suggested it and it was a 10 minute change.\n\nif i rebuilt it i'd probably do the hot parsing loop in rust and keep the rule engine in python. best of both. maybe later.\n\nit's MIT, it's on pypi as `threatlens-cli`\n\n, source is here: [https://github.com/TiltedLunar123/ThreatLens](https://github.com/TiltedLunar123/ThreatLens)\n\nit works. not perfect but it works. if you try it on real logs i'd genuinely love to know what it misses.", "url": "https://wpnews.pro/news/i-built-an-offline-threat-hunting-cli-in-python-because-spinning-up-a-siem-for", "canonical_source": "https://dev.to/tiltedlunar123/i-built-an-offline-threat-hunting-cli-in-python-because-spinning-up-a-siem-for-one-log-file-is-1n2k", "published_at": "2026-06-12 09:14:34+00:00", "updated_at": "2026-06-12 09:42:45.826805+00:00", "lang": "en", "topics": ["ai-tools"], "entities": ["threatlens"], "alternates": {"html": "https://wpnews.pro/news/i-built-an-offline-threat-hunting-cli-in-python-because-spinning-up-a-siem-for", "markdown": "https://wpnews.pro/news/i-built-an-offline-threat-hunting-cli-in-python-because-spinning-up-a-siem-for.md", "text": "https://wpnews.pro/news/i-built-an-offline-threat-hunting-cli-in-python-because-spinning-up-a-siem-for.txt", "jsonld": "https://wpnews.pro/news/i-built-an-offline-threat-hunting-cli-in-python-because-spinning-up-a-siem-for.jsonld"}}