Malware Detection Engine
Every registry processor runs the same detection engine (github.com/vulnetix/malscan-engine) over the package it just fetched. The engine is a pure, stateless analyser: each detector emits Findings, and a combination gate decides whether the package is malicious.
The 16 package-ecosystem scanners — npm, pypi, cargo, rubygems, go, nuget, packagist, maven, hex, pubdev, cran, julia, conan, alpine-apk, aur and homebrew — each map their package into a common PackageContext (primary manifest/build script, install hooks, git history, reputation metadata) and call the engine. A malicious verdict mints a GCVE-110-{ECOSYSTEM}-YYYY-N advisory and triggers threat-actor attribution and IOC retention.
Finding classes
Every detector emits Findings carrying a class that decides how much weight it pulls:
| Class | Meaning | On its own |
|---|---|---|
evidence | Factual malicious code or behaviour — download-and-execute, reverse shell, exfil endpoint, a Tor .onion C2 source, a known-bad artifact hash, a reference to a known-bad domain/IP. | Mints the advisory. |
trigger | A weak corroborating signal — a high-entropy embedded payload, or a supply-chain metadata change (new/changed maintainer, ownership transfer, orphan adoption). | Never mints alone; combines via the gate. |
context | A reputation/risk signal — low votes/popularity, few GitHub stars, missing licence, package age, weak checksums. | Recorded as metadata only; never mints. |
This is deliberately conservative: reputation alone never produces an advisory, and a brand-new legitimate package (which necessarily has a “new maintainer”) is never flagged on newness alone.
The combination gate
detect.CombinedVerdict turns the finding set into a verdict. A package is malicious when any of these hold:
- Evidence — at least one
evidencefinding. A single factual detection is sufficient. - Known-bad owner — the current maintainer matches a confirmed-bad threat actor in the DB (the strongest ownership signal).
- Payload + identity — a high-entropy embedded payload and at least one ownership/identity change (e.g. an ownership transfer or orphan adoption). Neither is proof alone; together they describe a takeover that injects code.
- Correlated identity change — two independent identity families change at once (for example an ownership transfer and a maintainer-email swap), with at least one being a genuine change rather than mere newness.
Everything else — a single ownership signal, pure newness, or a high-entropy blob with no corroboration — is recorded as correlation context and does not mint.
Processors additionally pass the verdict through an AI false-positive gate (internal/aimalgate) before minting: a model is asked whether the flagged package is genuinely malicious, and only a confident benign opinion drops the detection (the gate fails open, so it can only remove false positives, never add them).
Capabilities
The engine exposes 22 capabilities. A per-ecosystem capability config can disable any of them for a given registry; absent/unknown keys default to on, so a newly added detector runs everywhere until someone turns it off.
Content detectors (run inside detect.Detect)
| Capability | What it catches |
|---|---|
manifest-patterns | Download-and-execute, reverse shells, eval/base64 and other payload patterns in the primary build/manifest script. |
source-url-patterns | Suspicious declared download URLs — paste sites, IP literals, shorteners. |
gtfobins | Living-off-the-land binary invocations used to execute, escalate or exfiltrate. |
shell-obfuscation | Variable-concatenated exec, obfuscation, and the high-entropy heredoc payload trigger. |
install-script | Runs the pattern + shell detectors over lifecycle/install hooks (npm pre/post-install, setup.py, .install, init.ps1, …). |
onion-c2 | A Tor .onion address in the manifest, install scripts or latest diff. |
homograph | IDN homograph / mixed-script hostnames in source URLs (CWE-1007). |
name-typosquat | Brand-impersonation and look-alike package names. |
bin-source | An AUR -bin package whose download host differs from its declared upstream. |
manifest-diff | Newly introduced network calls or changed checksum/source between manifest revisions. |
orphan-takeover | Submitter ≠ maintainer plus a new git author on an established package. |
git-history | Single-commit history, package age, and author-change signals. |
checksum | Missing, all-SKIP, or weak (md5/sha1) checksum arrays. |
metadata-reputation | Votes/popularity, missing licence/URL and orphan flags. |
maintainer-batch | A maintainer pushing many packages at once (mass-upload). |
github-stars | Low-star or 404 upstream GitHub repositories. |
registry-comments | Warning signals in registry comment bodies (AUR). |
Supporting modules (invoked outside detect.Detect)
| Capability | Module | Role |
|---|---|---|
ownership-hijack | detect (OwnershipTriggers) | Cross-registry ownership-transfer / orphan-adoption / new-maintainer / known-bad-owner triggers for the combination gate. |
badhash | badhash | Case-insensitive known-bad artifact-hash set (embedded seed + the platform’s MalwareIoc hash rows). A hash hit is evidence. |
ioc-extract | ioc | Extracts domains, URLs, IPs, file hashes, install commands, wallets and exfil endpoints from a package for retention. |
goodkeys | goodkeys | Allowlist of trusted platform signing keys/identities (GitHub web-flow, Dependabot, GitLab CI, the actions bot) so a web-merged commit isn’t mis-attributed to an attacker. |
ioc-scan | iocscan | Matches the package’s source (and, optionally, ELF binaries) against the known-bad domain/IP/URL STIX feed. A hit is evidence. |
Per-ecosystem configuration
Not every detector is meaningful for every registry — bin-source is an AUR convention, registry-comments only exists where a registry has user comments, and install-script only applies to ecosystems with lifecycle hooks. The config package resolves a per-ecosystem capability set from two layers:
- Committed defaults —
config/defaults/<ecosystem>.json, embedded into the engine binary, authored in the engine’s Rust egui frontend and regenerated withjust gen-defaults. - System overrides — an optional
<ecosystem>.jsonan operator drops in the host config dir, overlaid per key.
A resolved config records only the capabilities a human turned off; everything else is on. The processor wires the resolved map into PackageContext.Capabilities (and consults EcosystemConfig.Enabled for the supporting modules), so a detector can be switched off for a noisy registry at runtime, with no rebuild.