malware-report-sentinel
Turn a suspicious file into a structured malware triage report (Markdown, HTML or JSON) using static analysis only.
Drop in a phishing attachment, a dropper or an odd binary and malware-report-sentinel produces the report an analyst would otherwise write by hand: sample details and hashes, a verdict and risk score, findings, format-specific internals (PE/ELF, VBA macros, PDF keywords, archive contents, shortcut targets, decoded PowerShell), embedded objects analysed recursively, defanged IOCs, a MITRE ATT&CK mapping, a starter YARA rule and recommendations, with TLP marking, analyst and case fields.
Samples are never executed, opened, rendered or uploaded. Everything is parsed from bytes in pure JavaScript. CLI, library and GitHub Action. Node.js 18+, zero runtime dependencies.
What it analyses
| Format | Checks |
|---|---|
| PE / ELF | Full header, section, entropy, import, imphash, Rich header, overlay and TLS parsing; packer hints (UPX, ASPack, VMProtect, Themida…), RWX sections, entry-point anomalies, injection APIs, capability mapping, hardening (shares its engine with binary-sentinel) |
| Office OOXML (docx/docm/xlsm/pptm) | vbaProject.bin extraction and MS-OVBA decompression of macro source, auto-exec entry points (AutoOpen, Document_Open, Workbook_Open, ActiveX _Painted/_GotFocus…), suspicious macro keywords (Shell, CreateObject, XMLHTTP, ADODB.Stream, Declare … Lib, Chr() chains), remote template injection (external attachedTemplate/oleObject relationships), DDE fields, embedded OLE/ActiveX |
| Office OLE (doc/xls/ppt) | VBA project detection and macro source recovery, Ole10Native objects, DDE |
| RTF | \objdata/\objupdate OLE objects, Equation Editor CLSID (CVE-2017-11882 family), hex-encoded payloads decoded and scanned |
/JavaScript, /JS, /OpenAction, /AA, /Launch, /EmbeddedFile, /URI, /AcroForm, /XFA, /SubmitForm, /ObjStm, /Encrypt and hex-escaped name obfuscation (/J#61vaScript) | |
| LNK | StringData parsing (arguments, working dir, icon); shells, script hosts and LOLBins; padded arguments |
| ZIP/JAR/APK | Entry listing, encrypted entries, executables/scripts inside, double extensions; nested executables and documents analysed recursively (decompression capped at 64 MB) |
| Scripts (ps1, vbs, js, bat, sh, hta…) | -EncodedCommand (decoded), FromBase64String/Gzip, download cradles (IEX + DownloadString, curl | sh), LOLBins (certutil, mshta, regsvr32, rundll32, bitsadmin, wmic, schtasks, msbuild…), obfuscation (char codes, reversal, concatenation, eval, escape characters), AMSI/Defender tampering, shadow-copy deletion; Base64 blobs decoded and scanned for payloads |
| HTML/SVG | HTML smuggling (atob/Blob + download/msSaveOrOpenBlob) |
| Any file | Hashes, magic-byte type, entropy, double extensions and RTLO names, extension/content mismatch, embedded PE files, network IOCs, crypto wallets |
Office depth, YARA testing and the sentinel family
npm i -D malware-report-sentinel @virustotal/yara-x # yara-x is optional: enables YARA rule testing
malware-report-sentinel invoice.doc --report report.md
- VBA stomping: module source is read through the VBA
dirstream and compared with the compiled p-code. Strings that only exist in the p-code (URLs,powershell,Shell...) or an empty source with a large p-code cache raiseoffice-vba-stomping. - Excel 4.0 macros: macro sheets in .xls and .xlsm,
Auto_Open,EXEC/CALL/REGISTER, hidden and very hidden sheets. - Tested YARA: with
@virustotal/yara-xinstalled every starter rule is compiled and run against its own sample. The report says whether it compiles and matches. - Binaries are analysed by binary-sentinel, IOCs come from ioc-sentinel and hashes (including ssdeep/TLSH) from hash-sentinel, so fixes land in one place.
import { analyzeAsync, renderReport } from 'malware-report-sentinel';
const report = await analyzeAsync(['sample.bin']); // analyze() is the sync version without YARA testing
Module formats and types
Ships ESM and CommonJS (import and require both work, via exports), with TypeScript declarations generated from the source at build time. Tests run on Node 18/20/22 in GitHub Actions (test/), including require/import checks against the packed tarball; releases are published with npm provenance.
Verdict and score
Findings are weighted (critical 40, high 20, medium 8, low 2) into a 0–100 static risk score. Any critical finding or a score of 60+ is malicious, 25+ is suspicious, and anything lower is no strong static indicators. The tool never calls a file clean, because static analysis can't prove that.
Install and run
npx malware-report-sentinel invoice.docm --report invoice-report.md --html invoice-report.html \
--tlp AMBER --analyst "Louis Wright" --case IR-2026-042
npx malware-report-sentinel ./quarantine -f report > triage.md # one report covering many samples
npx malware-report-sentinel sample.exe --yara sample.yar --json sample.json
The default stdout output is the findings list (text); -f report prints the full Markdown report and -f html the standalone HTML page. The HTML has no scripts and a strict Content-Security-Policy, so it is safe to open.
Rules
--list-rules prints all of them. Document and script rules: office-autoexec-macro (critical), office-macro, office-macro-suspicious, office-external-template, office-dde, rtf-ole-object (high), rtf-equation-editor (critical), pdf-javascript, pdf-launch (high), pdf-auto-action, pdf-embedded-file (medium), lnk-command, script-encoded-command, script-download-cradle, script-defense-evasion, html-smuggling, double-extension, embedded-pe (high), script-lolbin, script-obfuscation, archive-encrypted, archive-executable, type-mismatch, crypto-wallet (medium), network-indicators, base64-blob, pdf-form-submit (low), plus every binary rule from binary-sentinel.
Library
import { analyze, renderReport, renderHtml } from 'malware-report-sentinel';
const report = analyze(['suspicious.zip'], { tlp: 'AMBER', analyst: 'Louis Wright' });
report.targets[0].data.verdict; // 'malicious' | 'suspicious' | 'no strong static indicators'
report.targets[0].data.attack; // [{ id: 'T1204.002', name: 'User Execution: Malicious File', evidence: [...] }]
report.targets[0].data.yara; // starter YARA rule
const md = renderReport(report);
const html = renderHtml(md);
Safety
- Static only: no execution, no macro evaluation, no PDF rendering, no network access.
- Decompression is size-capped and recursion is limited to two levels.
- IOCs and macro excerpts in the report are defanged.
Configuration
Put a .malware-report-sentinel.json in the working directory (or pass --config <file>). Every key is optional; CLI flags override the file.
{
"failOn": "high",
"severity": { "office-macro": "high" },
"allow": [
{ "rule": "office-macro", "match": "*training-samples/*", "reason": "Accepted risk, ticket SEC-123", "expires": "2027-03-31" }
]
}
severitychanges the severity of any rule, or turns itoff.allowsuppresses findings. Each entry needs arule(globs such asoffice-*work) and areason, and can be narrowed withtargetandmatch(a glob tested against the target, location and value). Suppressed findings are listed in the JSON report. Whenexpires(YYYY-MM-DD) has passed, the finding comes back together with anallow-expiredfinding, so exceptions can't be forgotten. Entries that match nothing are reported asallow-unused(info).
Output and exit codes
--format text|json|sarif|markdownfor stdout, plus--json,--sarifand--markdown <file>to write extra copies in one run. SARIF 2.1.0 works with GitHub code scanning.--fail-on critical|high|medium|low|info|none(defaulthigh).- Exit code 0: passed. 1: at least one finding at or above
--fail-on. 2: usage or config error, or a target could not be checked. --list-rulesprints every rule with its default severity.
GitHub Action
permissions:
contents: read
security-events: write # only for the SARIF upload
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with: { persist-credentials: false }
- uses: MrLouisWright/malware-report-sentinel@v1
with:
targets: 'quarantine/'
fail-on: high
sarif: malware-report-sentinel.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with: { sarif_file: malware-report-sentinel.sarif }
Inputs: targets, fail-on, config, sarif, json, job-summary (Markdown report in the job summary, default true), args (extra CLI flags), report, html, tlp. Outputs: markdown-file, exit-code.
Requirements
Node.js 18 or newer. No runtime dependencies.
Support
If this project is useful to you, you can support its development with a donation: Buy me a coffee. It helps keep these tools maintained, documented and free for everyone.
Credit (optional)
Free to use under MIT, no attribution required. If malware-report-sentinel helps you, a link back is appreciated:
<a href="https://louiswright.co.uk">Built with malware-report-sentinel by Louis Wright</a>
[](https://louiswright.co.uk)
License
MIT © Louis Wright (Louis Wright)