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:

ClassMeaningOn its own
evidenceFactual 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.
triggerA 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.
contextA 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 evidence finding. 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)

CapabilityWhat it catches
manifest-patternsDownload-and-execute, reverse shells, eval/base64 and other payload patterns in the primary build/manifest script.
source-url-patternsSuspicious declared download URLs — paste sites, IP literals, shorteners.
gtfobinsLiving-off-the-land binary invocations used to execute, escalate or exfiltrate.
shell-obfuscationVariable-concatenated exec, obfuscation, and the high-entropy heredoc payload trigger.
install-scriptRuns the pattern + shell detectors over lifecycle/install hooks (npm pre/post-install, setup.py, .install, init.ps1, …).
onion-c2A Tor .onion address in the manifest, install scripts or latest diff.
homographIDN homograph / mixed-script hostnames in source URLs (CWE-1007).
name-typosquatBrand-impersonation and look-alike package names.
bin-sourceAn AUR -bin package whose download host differs from its declared upstream.
manifest-diffNewly introduced network calls or changed checksum/source between manifest revisions.
orphan-takeoverSubmitter ≠ maintainer plus a new git author on an established package.
git-historySingle-commit history, package age, and author-change signals.
checksumMissing, all-SKIP, or weak (md5/sha1) checksum arrays.
metadata-reputationVotes/popularity, missing licence/URL and orphan flags.
maintainer-batchA maintainer pushing many packages at once (mass-upload).
github-starsLow-star or 404 upstream GitHub repositories.
registry-commentsWarning signals in registry comment bodies (AUR).

Supporting modules (invoked outside detect.Detect)

CapabilityModuleRole
ownership-hijackdetect (OwnershipTriggers)Cross-registry ownership-transfer / orphan-adoption / new-maintainer / known-bad-owner triggers for the combination gate.
badhashbadhashCase-insensitive known-bad artifact-hash set (embedded seed + the platform’s MalwareIoc hash rows). A hash hit is evidence.
ioc-extractiocExtracts domains, URLs, IPs, file hashes, install commands, wallets and exfil endpoints from a package for retention.
goodkeysgoodkeysAllowlist 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-scaniocscanMatches 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:

  1. Committed defaultsconfig/defaults/<ecosystem>.json, embedded into the engine binary, authored in the engine’s Rust egui frontend and regenerated with just gen-defaults.
  2. System overrides — an optional <ecosystem>.json an 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.