Saltar al contenido

Open source / Threat-intel / malware-report-sentinel

malware-report-sentinel

Static malware triage report generator: hashes, file type, entropy, PE/ELF internals, Office macros and auto-exec, PDF actions, LNK, RTF, archives, scripts and HTML smuggling, IOCs, ATT&CK mapping, a verdict and a YARA starter rule, as Markdown, HTML or JSON. Never executes samples. Zero deps.

En npm
$ npm install malware-report-sentinel

README

malware-report-sentinel

npm version CI Node License: MIT

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

FormatChecks
PE / ELFFull 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
PDF/JavaScript, /JS, /OpenAction, /AA, /Launch, /EmbeddedFile, /URI, /AcroForm, /XFA, /SubmitForm, /ObjStm, /Encrypt and hex-escaped name obfuscation (/J#61vaScript)
LNKStringData parsing (arguments, working dir, icon); shells, script hosts and LOLBins; padded arguments
ZIP/JAR/APKEntry 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/SVGHTML smuggling (atob/Blob + download/msSaveOrOpenBlob)
Any fileHashes, 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 dir stream 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 raise office-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-x installed 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" }
  ]
}
  • severity changes the severity of any rule, or turns it off.
  • allow suppresses findings. Each entry needs a rule (globs such as office-* work) and a reason, and can be narrowed with target and match (a glob tested against the target, location and value). Suppressed findings are listed in the JSON report. When expires (YYYY-MM-DD) has passed, the finding comes back together with an allow-expired finding, so exceptions can't be forgotten. Entries that match nothing are reported as allow-unused (info).

Output and exit codes

  • --format text|json|sarif|markdown for stdout, plus --json, --sarif and --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 (default high).
  • 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-rules prints 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>
[![Built with malware-report-sentinel by Louis Wright](https://img.shields.io/badge/built%20with-malware--report--sentinel-blue)](https://louiswright.co.uk)

License

MIT © Louis Wright (Louis Wright)