CLI tool
Neo23x0/munin avatar
Neo23x0/munin

munin: one hash, nine services, one report

Online hash checker for Virustotal and other services

858 stars156 forksPythonApache-2.0

At a glance

What is it?
Munin is a Python script by Florian Roth that takes a file of hashes or a folder of samples, queries VirusTotal, Hybrid Analysis, Any.Run, CAPE, Malshare, URLhaus, MISP and Valhalla for each one, caches the answers and writes a CSV report. Its dependencies and its four operation modes are simple, but its release history and its maintenance badge disagree with each other.
Who is it for?
Adopt munin if you are doing bulk triage and want one place that queries every service you have keys for, because the cache, the CSV output and the four operation modes cover the batch, interactive and web uses without much setup.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 31 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One hash in, everything the services know about it out

Munin is an online hash checker utility that retrieves information from various online sources, and the README's ASCII banner gives it the subtitle Online Hash Checker for Virustotal and Other Services. The author is named in the banner itself, Florian Roth, which dates the project to a well known name in the malware analysis world.

The list of services is the substance of the tool. It queries VirusTotal, HybridAnalysis, Any.Run, URLhaus, MISP, CAPE, Malshare, Valhalla and Hashlookup, and the extra checks section repeats the same list with the detail of what each is asked for. That spread matters for a triage workflow, because a hash that is unknown to one service is frequently known to another, and a hash that is known to a sandbox is known with a behavioural report attached.

The features list also describes the plumbing, and it is a sensible design for the job. Munin retrieves information from VirusTotal through its API with a JSON response and other information through the permalink, which means HTML parsing, so it gets what the API does not expose. It keeps a history in a cache so that the services are queried only once for a hash that appears several times in the input, and the cached objects are stored as JSON. It creates a CSV file with the findings for post-processing and reporting, and it appends results to a previous CSV if one is available, which is what makes it possible to accumulate a report over days.

The licence is Apache-2.0, the primary language is Python, the default branch is master, and the last push was on 2026-09-22.

The maintenance badge and the release dates disagree

The first line of the README is a badge that says Maintenance Level: Actively Maintained, in green, and the release history says something else.

The published releases are 0.6.0 on 2018-04-25 and v0.2 on 2017-10-28. The newest release is therefore more than eight years old. And the README's own command line section refers to the command line interface mode as new in v0.14, a version that is not in the release list at all, which suggests the numbering scheme changed at some point, or that some releases were made outside the platform's release mechanism, or that the numbers simply drifted.

The last push on 2026-09-22 shows that somebody is still touching the repository, and it is consistent with a project that gets occasional fixes rather than continuous work. But for a tool whose entire value depends on the current behaviour of nine external services, that is the thing to check before you rely on it.

The practical advice follows directly. Run a single hash by hand first, against the services you have keys for, and compare what you get with what munin reports. If the parsers have drifted, you will see it immediately on one hash, and you will not see it at all if you trust a batch of a thousand. The README does not document a service compatibility matrix, and with a permalink-parsing approach to some of the services, breakage is a matter of when rather than whether.

Four ways to run it

The operation modes section lists four, and they cover the range from a one-off triage to a service on a box.

The default mode takes an input file with hashes, or a sample folder, with -f and -s. The file is read line by line, or as CSV with a hash in each line, and the position of the hash and the comment are auto-detected. The query mode searches hashes from Valhalla by keyword, tags, YARA rule name, ATT&CK software identifier such as S0154, ATT&CK technique such as T1023, or threat group such as G0049, using the --vh and --vhrule options. The command line interface mode is selected with --cli, and the web service mode with --web and a port given by -w.

The web mode is the one to think about twice, because the README does not describe any authentication for it. A service that queries VirusTotal and the sandboxes on your behalf, with your API keys, and that accepts input from a network, is worth keeping on the loopback interface unless you have put something in front of it.

Two of the other options are worth knowing because they are about cost and volume. The --vtwaitquota option says to not continue if the VirusTotal quota is exceeded but wait for the next day, which is what you want when a job is scheduled and the free tier runs out mid-file. And --limit caps how many new hashes are handled in a batch mode, with the cache ignored, which is the tool you reach for when you want a bounded run rather than a complete one.

The typical command lines in the README are short:

bash
python3 munin.py -i my.ini -f ~/Downloads/retro_hunt

which processes a VirusTotal Retrohunt result and sorts the lines so that matched signatures are checked in blocks, and:

bash
python3 munin.py -i my.ini -s ~/malware/case34

which processes a directory of samples and checks their hashes online.

The cache, the CSV and the API keys

The getting started section is four steps, and the third one is the one people usually get wrong.

Download or clone the repository, install the required packages with pip3 install -r requirements.txt, with --user on macOS, then set the API keys for the different services in your own ini file by copying munin.ini to my.ini, and finally run the demo file for a first run:

bash
python3 munin.py -i my.ini -f munin-demo.txt

The copy step is deliberate and good practice, because the shipped ini file is the template for the keys you need, and your copy is the one that holds them. The -i option names that file, and it appears in every command line in the README.

The cache is the other half of the working model. Its name is given by -c with a default of vt-hash-db.json, and --nocache turns it off. The purpose is stated in the features list, to query the services only once for a hash that may appear multiple times in the text file, which matters because the services are rate limited and quota bound. The CSV output is controlled by -o, and --nocsv disables it. The --sort option sorts the input lines, and --rescan triggers a rescan of each analysed file, which is the flag you want after a new detection appears in VirusTotal.

There is also --download, which enables sample download from Hybrid Analysis, and the help text notes that the SHA256 of the sample is needed, with -d giving the output path and the requirement that the folder must exist. That is a notable capability, because it turns a hash checker into a fetcher of the files themselves.

The requirements are Python 3.7 and higher and an internet connection, with proxy support and a note that SSL and TLS interception can be a problem. In a corporate network that last point is the one that bites, and --verifycert is the option that governs certificate checking.

What the report contains, and what the extra checks add

The displays section describes the fields of the report, and it is a list of the things an analyst looks for first.

There is the hash and the comment, where the comment is the rest of the line the hash was extracted from, which is how the tool keeps a note attached to a finding from a text file. There are the antivirus vendor matches based on a user defined list, so you decide which vendors count. There are the filenames used in the wild, the PE information such as the description, the original file name and the copyright statement, the signer of a signed portable executable, the result based on the VirusTotal ratio, the first and last submission, and tags for certain indicators: Harmless, Signed, Expired, Revoked and MSSoftware.

The extra checks are where the tool earns its keep, because they cover a different set of questions. It queries Malshare for sample uploads, Hybrid Analysis for reports, multiple MISP instances for available events, the Any.Run sandbox for reports, the CAPE sandbox for reports, URLhaus for reports, and Valhalla for YARA rule matches.

Two of the extra checks are local rather than remote, and they are the clever ones. Imphash duplicates in the current batch allow you to spot overlaps in import table hashes, and PE signature duplicate checks do the same for signed executables. That turns a pile of hashes into a picture of a campaign: files that share an import hash or a signature are probably the same malware or the same toolchain, and finding that is often more useful than another verdict from another sandbox.

The dependency list supports the picture of a wide but conventional Python toolkit: requests and urllib3 with an upper bound below 2, colorama for terminal output, future for compatibility, configparser for the ini file, pymisp for the MISP instances, flask with flask_caching for the web mode, cfscrape, which is how a Cloudflare challenge is handled when a query has no API, pyzipper for the sample download, dnspython and IPy for the host related scripts, and valhallaAPI for the Valhalla queries.

The repository, the host scripts, and the state of the whole thing

The top-level listing shows a script collection rather than a package. There is munin.py, the main program, and beside it three siblings with narrower jobs: munin-host.py, hugin.py and munin-json-parse.py, plus a lib/ directory with the shared code. There is munin.ini, the API key template, munin-demo.txt, a demo input file, munin-hosts-demo.txt for the host scripts, and fin7-1.txt, which is a hash list for a well known intrusion set. There is a screens/ directory with the screenshots the README shows, and a requirements.txt.

A tool like this is used by people who already know the field, and the presence of fin7-1.txt says so. The scripts are named after their functions rather than being packaged for installation, which is why the getting started instructions are a clone and a pip install rather than a package name.

The version story is the one to keep in mind. There is a 0.6.0 release from April 2018, a v0.2 from October 2017, a README that refers to a v0.14 feature, and commits as recent as September 2026. A tool whose output depends on other people's APIs, whose permalink parsing will break when a service changes its HTML, and whose last tagged release predates most of those services' current interfaces, is worth using with the source open next to you.

What it does have is a shape that has held up. The cache, the CSV, the four modes, the separation of API queries from HTML parsing, and the duplicate detection on top. Those are good decisions, and they are the reason the tool is still worth reading even if the services it queries have moved on.

Editorial conclusion

Adopt munin if you are doing bulk triage and want one place that queries every service you have keys for, because the cache, the CSV output and the four operation modes cover the batch, interactive and web uses without much setup. Do not adopt it as a maintained tool, because the newest published release is 0.6.0 from 2018 while the README still carries an Actively Maintained badge, and several of the services it queries have changed their APIs since then, so check a single hash by hand before you trust a batch. Before you run it, put the API keys in your own ini file rather than the shipped one, expect the first run to be slow while the cache is cold, and remember that the web service mode has no authentication described in the README, so do not expose it beyond your own machine.

Frequently asked questions

Which services does munin query?

VirusTotal, Hybrid Analysis, Any.Run, URLhaus, MISP, CAPE, Malshare, Valhalla and Hashlookup. Information from VirusTotal comes through its API as JSON, and other information is retrieved through the permalink by parsing HTML.

How do I run munin for the first time?

Clone the repository, install the requirements with pip3 install -r requirements.txt, copy munin.ini to my.ini and put your API keys in the copy, then run python3 munin.py -i my.ini -f munin-demo.txt against the bundled demo file. The README asks for the ini copy specifically so that your keys are not written back into the shipped file.

What are munin's operation modes?

Four: the default mode, which takes an input file of hashes with -f or a sample folder with -s, a query mode that searches Valhalla by keyword, tags, YARA rule name or ATT&CK identifier, a command line interface mode with --cli, and a web service mode with --web and a port set by -w.

Is munin still maintained?

The README carries an Actively Maintained badge, but the published releases are 0.6.0 from 2018-04-25 and v0.2 from 2017-10-28, while the last push was on 2026-09-22. Check a single hash by hand before relying on a batch, since the tool parses some service pages as HTML.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. Neo23x0/munin on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/neo23x0-munin.svg)](https://hysenlabs.com/projects/neo23x0-munin)

Was this useful?