Imported from Snausage0x45/ClaudeSkills (
hashcheck/SKILL.md). Install upstream withnpx skills add Snausage0x45/ClaudeSkills --skill hashcheck. Copyright stays with the author.
hashcheck
Investigate one file — identified by its hash — and give a malware analyst a briefing they can act on in under a minute. If the user gives you a file instead of a hash, compute its SHA-256 first (see "Accept files and batches"). A verdict of benign, suspicious, or malicious is the point; get there and justify it.
Don't dump raw API output. Synthesize. Pull detections, code-signing, file structure, and sandbox behavior into one picture, then make a defensible call about how much to worry.
This is passive reconnaissance. You look up what public malware databases already know about a hash. You never detonate the sample, download and run it, submit it anywhere without saying so, or reach out to attacker infrastructure. The hash is a fingerprint; treat this as a records check on that fingerprint. If a user asks hashcheck to execute or actively probe a sample, decline that part and explain that hashcheck is read-only.
What the verdict rests on
A hash lookup answers four questions. Weight them in this order.
- Consensus detection. How many reputable engines flag this file, and what do they call it? A high, consistent detection count with an agreed family name (e.g. "Emotet", "AgentTesla") is the strongest single signal. A handful of generic "heuristic" or "ML" hits on an otherwise clean file is weak.
- Code-signing and provenance. A file signed by a named, reputable vendor with a currently-valid certificate that VirusTotal verifies is far more trustworthy than an unsigned one. A broken, expired, revoked, or mismatched signature is worse than no signature at all, because it signals tampering or impersonation. Read who signed it, not just whether it is signed.
- File structure. For Windows PE files, section entropy and layout betray
packing and obfuscation. A section with entropy above ~7.2 is likely packed
or encrypted — common in malware, but also in legitimate installers, so it is
a flag, not a conviction. An unusual section name (
.UPX0,.themida), a tiny import table, or a mismatch between the file type and its extension all deserve a mention. - Sandbox behavior and corroboration. Dynamic-analysis verdicts, dropped files, network indicators, and YARA/Sigma matches turn "some engines flag it" into "here is what it does." Reach for these when the static picture is ambiguous or the user is working an active incident. Absence of evidence is not evidence of safety. A hash no database has seen is unknown, not clean — say so, and lean on structure and signing to reason about it.
Network access: pick the working path first
hashcheck depends on reaching public malware-intel APIs. How you reach them varies by environment, and choosing wrong produces a page of connection errors instead of a report.
- The bundled script
scripts/hashcheck.py. This runs every lookup at once and prints a single compact JSON object. It works where the shell has internet and the API keys are set as environment variables, such as Claude Code on a workstation. It queries the richer, authenticated endpoints — VirusTotal v3 (pe_info,signature_info, sandbox verdicts), MalwareBazaar, Hybrid Analysis, and Joe Sandbox — that need custom headers the WebFetch tool cannot send. Prefer this path whenever a shell with egress exists:python3 scripts/hashcheck.py <hash-or-file>. - The WebFetch tool (
mcp__workspace__web_fetchorWebFetch) plusWebSearch. Use this in Cowork and other sandboxes where the shell has no outbound internet. WebFetch can only issue GET requests and cannot set theAuth-Key,x-apikey, orapi-keyheaders the deep endpoints require, so on this path you get VirusTotal's v2 endpoint (which accepts the key as a query parameter) for the multi-engine verdict, plusWebSearchfor corroboration. You will usually not get PE sections, entropy, or signing detail on this path — say so in the report rather than inventing it. Try the script first when a shell exists. If its output containsTunnel connection failed,network unreachable,name resolution, or every source reports a connection error, the shell has no egress — switch to the WebFetch path and don't retry the script.
Every source degrades independently. A dead, rate-limited, or key-less source is a gap you report, not a reason to stop. Collect what you can, then state what you missed and why. The verdict must reflect the evidence you actually gathered.
Accept files, hashes, and batches
A bare hash is the common case. Normalize it: strip whitespace and any
sha256: / 0x prefix, lowercase the hex. Classify by length — 32 hex chars is
MD5, 40 is SHA-1, 64 is SHA-256. All three are valid lookup keys; SHA-256 is
preferred because it is collision-resistant and every source keys on it. If the
string is not valid hex of one of those lengths, say so instead of guessing.
A file — uploaded or named by path — means the user wants you to identify
what they have. Compute all three digests before looking anything up; the script
does this automatically when handed a path, or run
python3 scripts/hashcheck.py /path/to/file. Report the SHA-256 you computed so
the user can confirm it matches their file. Never execute the file to inspect it.
Multiple hashes in one request means triage a set. Run each through the same workflow, but keep each write-up tight — lead with a one-line summary table (hash, verdict, family) so the analyst can scan the batch, then give the full briefing only for the ones that warrant it. Don't produce five identical full-length reports for five clean files.
Workflow
1. Identify the file
From the sources, establish what the file is before judging it: file type and
"magic" (PE, ELF, Mach-O, PDF, Office, script, archive), size, common names it
has been seen under, and first-seen date. VirusTotal's type_description,
meaningful_name, names[], size, and first_submission_date cover this;
MalwareBazaar's file_type, file_name, and first_seen corroborate. A recent
first-seen date on a file already carrying detections suggests a fresh campaign.
2. Read the detection consensus
This is the headline. From VirusTotal, read last_analysis_stats
(malicious / suspicious / undetected / harmless) for the score, and
last_analysis_results{} for what engines call it. Name two or three reputable
engines (Microsoft, Kaspersky, ESET-NOD32, Malwarebytes) and the family they
assign rather than listing all ~70. popular_threat_classification.suggested_ threat_label gives VirusTotal's rolled-up family guess — quote it when present.
On the WebFetch path, the v2 file/report endpoint gives positives/total
and per-engine scans{} instead; read the same signal from those.
Interpret honestly. Ten engines agreeing on "Trojan:Win32/Wacatac" is a conviction. Two engines with generic "ML.Attribute.HighConfidence" or "gen:variant" labels on a file nothing else flags is a lead to verify, not a verdict. Say which situation you are in.
3. Check code-signing and provenance
Pull the digital signature. VirusTotal signature_info carries verified
(the plain-English status — "Signed", "Invalid signature", "This file is not
signed"), subject / signers (who signed it), product and description,
and the signing/counter-signing dates. MalwareBazaar's code_sign[] gives the
certificate subject_cn, issuer_cn, valid_from / valid_to, and
thumbprint.
Judge four things and say what each means:
- Signed vs. unsigned. Unsigned is normal for scripts and open-source tools, unremarkable on its own, but removes a trust anchor for a Windows binary that would normally ship signed.
- Verification status. "Signed" and verified is reassuring. "Invalid", "expired", "revoked", or "unverifiable" is a red flag — a tampered or spoofed signature is worse than none, because it is an attempt to borrow trust.
- Who signed it. A signer that matches the software's claimed publisher (Microsoft, Google, a known vendor) supports legitimacy. A signer that is a random company, a mismatched name, or a recently-issued certificate on a suspicious file is a strong negative — stolen and abused code-signing certificates are a known attacker technique.
- Certificate age and validity window. A certificate issued days before the file first appeared, or one already expired at signing time, deserves scrutiny.
4. Examine file structure (PE sections and entropy)
For Windows PE files, read VirusTotal pe_info: sections[] (each with name,
virtual_address, raw_size, and entropy), imphash, import_list[]
(the DLLs and APIs it pulls in), entry_point, and any resource_details.
What to look for and why:
- High section entropy (above ~7.2 on a 0–8 scale) means the section is compressed or encrypted. Packers (UPX, Themida, ASPack) and malware droppers do this to hide code from static scanners. Legitimate installers pack too, so treat high entropy as a flag that raises the bar for a "benign" call, not as proof of malice.
- Telltale section names —
UPX0/UPX1,.themida,.aspack,.vmp0, or random-looking names — name the packer outright. - A minimal import table (a handful of APIs, often just
LoadLibrary/GetProcAddress) is classic for a packed stub that unpacks the real payload at runtime. A rich, coherent import set is more consistent with normal software. - Type/extension mismatch — a file served as
invoice.pdfwhose magic says PE executable — is itself a finding worth leading with. On the WebFetch path you will usually not havepe_info. Note that section and entropy analysis was unavailable rather than omitting the topic silently.
5. Pull sandbox behavior and corroboration
When static signals are ambiguous or the user is investigating an active incident, add dynamic and community evidence:
- Hybrid Analysis (
/api/v2/search/hash) returnsverdict,threat_score,vx_family, andmitre_attcks[]— behavior mapped to the MITRE ATT&CK framework (a catalog of adversary techniques). Read the verdict and the top techniques. - Joe Sandbox search returns analyses with a maliciousness score and detection; report the score and whether it flagged the sample.
- VirusTotal
sandbox_verdicts{},crowdsourced_yara_results[](named detection rules that matched — often carry a precise family), andsigma_analysis_results[](suspicious-behavior log rules) add behavioral weight without a separate sandbox call. - MalwareBazaar
vendor_intel{},tags[], anddelivery_methodtell you how the sample is distributed (spam attachment, drive-by, etc.). WebSearchthe raw hash, e.g."44d88612fea8a8f36de82e1278abb02f" malware, to surface vendor writeups, sandbox reports, and blocklist entries. Rely on it entirely when no API keys are set. Distinguish what the file is from what merely mentions it. A blog that lists a hash in a table of indicators is corroboration; it is not independent analysis. Prefer engine detections, signing facts, and sandbox verdicts over raw search-hit counts.
Write the summary in Google technical writing style
The summary is the part an analyst actually reads. Follow Google's technical writing standards, because they produce prose a reader scans quickly and cannot misread:
- State the conclusion first. Open with what the file is and the risk call. Don't build to it.
- Use active voice and present tense. Write "Microsoft flags this as Wacatac," not "this is flagged as Wacatac by Microsoft."
- Keep sentences short. One idea per sentence. Aim under 25 words.
- Address the reader as "you." Write "quarantine this on your endpoints," not "one might consider quarantining it."
- Define terms and expand abbreviations on first use. Write "imphash (import hash, a fingerprint of the file's import table)."
- Cut filler. Delete "basically," "very," "it should be noted that," and "in order to." Replace vague words with specific ones.
- Avoid ambiguous pronouns. Write "this certificate," not "this."
- Never hedge to sound safe. If the data is thin, say which data is missing. Hold the summary to one paragraph of three to five sentences. Be technical and concrete: name the family, the engine count, the signer, the packer, the entropy. Prefer a specific fact over an adjective. An analyst triaging a queue reads the verdict and the summary; everything else is reference. If a sentence doesn't change what the reader does next, cut it.
Before (verbose, passive, hedged):
It should be noted that this file appears to possibly be detected by a number of different antivirus engines, and it seems that it may have been packed using some kind of packing software, which is often associated with malware.
After (Google style):
This is AgentTesla, an info-stealer. 58 of 72 engines flag it, with Microsoft, ESET, and Kaspersky agreeing on the family. The file is an unsigned Windows PE packed with UPX (its
.UPX1section reads 7.9 entropy) and imports onlyLoadLibrary/GetProcAddress— a classic unpack-at-runtime stub. Quarantine it and hunt for the%AppData%drop it creates.
Output format
Produce these parts in this order. Lead with the verdict, because an analyst reads top-down and wants the conclusion first.
## hashcheck: <sha256 or the hash given> — Verdict: <Benign | Suspicious | Malicious | Unknown>
<Three to five sentences in Google technical writing style. What the file is,
its detection consensus, its signing and structure, and why you assigned that
verdict.>
### Facts
| Field | Value |
|---|---|
| Hash (query) | <the hash the user gave, with its type> |
| SHA-256 | |
| SHA-1 | |
| MD5 | |
| File type | <PE32 exe, ELF, PDF, Office macro, script, ...> |
| File size | |
| Common names | <names it has been seen under> |
| First seen | <date> |
| Detections | <e.g. 58/72 malicious; or "0/70"; or "not in any database"> |
| Threat label | <VT suggested_threat_label / agreed family> |
| Notable engines | <2–3 reputable engines and what they call it> |
| Signature | <Signed by <signer>, verified; or Unsigned; or Invalid/Expired/Revoked> |
| Signing cert | <issuer, validity window; omit if unsigned> |
| Packing / entropy | <packer name and/or high-entropy sections, or "no packing indicators"> |
| Imphash | |
| Sandbox verdict | <Hybrid Analysis / Joe Sandbox score and behavior, or "not reached"> |
| YARA / Sigma | <named rule matches, or omit> |
| MITRE ATT&CK | <top techniques observed, or omit> |
| Distribution | <delivery method / tags, or omit> |
| Sources reached | <list; name any that failed or lacked a key> |
### Notes
<Up to five short bullets. Caveats, gaps, or leads worth a human's follow-up.
Omit this section if there are none.>
Include a row only when you have a value or a meaningful "not reached" for it.
Keep a row when its emptiness is informative, such as "Detections: not in any
database (file unknown to all sources)." Drop signing/PE rows entirely for file
types where they don't apply (a PDF has no pe_info; a shell script has no
signature).
Keep Notes to five bullets at most, and put only decision-relevant material there — a caveat that changes how to read a fact, a gap that limits confidence, or a lead worth chasing. Notes are not a place to restate the table or narrate your process.
For a batch, print a one-line summary table first:
| Hash (short) | Verdict | Family / note |
|---|---|---|
| 44d88612… | Malicious | AgentTesla, 58/72 |
| a1b2c3d4… | Benign | Signed Microsoft, 0/72 |
Then give the full briefing only for hashes that are Suspicious, Malicious, or that the user should look at closely.
Assign the verdict
The verdict is a judgment call, not a formula. Explain your reasoning in the
summary so a human can disagree. Use exactly one of Benign, Suspicious,
Malicious, or Unknown in the header. Don't qualify it, hyphenate it, or
invent a compound label — downstream readers and tooling key off that single
word.
Malicious— a meaningful count of reputable engines agree it is malware, especially with a consistent family name; or a sandbox reports malicious behavior; or the file combines strong structural red flags (packing, a spoofed or revoked signature, type/extension mismatch) with any detections.Suspicious— the picture is mixed or concerning but not conclusive: a few detections without consensus, heavy packing or high entropy on an unsigned binary with no clean provenance, a broken or mismatched signature, or a file that behaves oddly in a sandbox but engines haven't caught up. Also use this when data is partial enough to leave real doubt — "Suspicious — limited data" tells the analyst to look closer.Benign— zero or only trivial generic detections, and where applicable a valid signature from a named reputable vendor and an unremarkable structure. A widely-seen, long-known file with clean results across sources earns this. Say "no negative reputation found across ," not "clean," and name the sources.Unknown— no source has a record of the hash and you can't compute structure (e.g. you have only the hash, not the file). This is common and honest. Don't upgrade it to Benign; an unseen file is unproven, not safe. Tell the user that submitting the file itself (which hashcheck won't do automatically) would resolve it. A clean, signed file can still do something the user should think twice about — a legitimate remote-access tool, say. The verdict rates the file's maliciousness; if there's a separate "legitimate but risky" angle, put it in the summary's last sentence or the first Note and keep the header word clean.
When data is thin, don't overclaim. State what would change your assessment — "if you can share the file, submitting it to VirusTotal would confirm."
Reference
Read references/sources.md for the full endpoint catalog, authentication
details, exact response fields, environment-variable names, and per-source
failure behavior. Consult it when a lookup returns something unfamiliar, you need
an alternate source, or you're wiring up which keys are present.