Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

mikronova/modgunn

.nornir/for-idiots.md

Móðguðr — for idiots (f0, the whole)

Class: idiot. Every step, nothing assumed. Goal: go from a cold shell to a green test run of modgunn, then read what it does. If you already know Rust, read guide.md instead — this one spells out every keystroke.

What you need first

cargo --version

If it prints a version you are good. If it says "command not found", install Rust from https://rustup.rs first.

cd /run/media/rickard/T9/git/modgunn

Step 1 — build it (the lean engine)

Type this and press Enter:

cargo build

This builds Engine A — the part that looks at one package and says Pass, Warn, or Block. It is the light version: no database, no network. Wait for it to finish (it prints Finished at the end).

Step 2 — run the tests

Type this and press Enter:

cargo test

You should see lines ending in test result: ok. That means every check passed — Engine A's verdict logic and the feed parsers all work. If you see FAILED, copy the message and ask someone; nothing you did is wrong.

Step 3 — build the heavy engine too (optional)

Engine B watches your whole dependency world and tells you what changed between two points in time. It is heavier. To build it, type and press Enter:

cargo build --features scip

And to build the warehouse (the time-travelling database) and the modgunn command:

cargo build --features warehouse

Step 3b — turn a real project into a package list (try it!)

modgunn can read a project's lockfile and list every package it depends on — no database, no network. Build the command with the manifest reader turned on:

cargo build --features warehouse,manifests

Then point it at any lockfile. For example, modgunn's own Cargo.lock:

cargo run --features warehouse,manifests -- ingest --path Cargo.lock

It prints one line per package (cargo serde 1.0.203) and a count at the end. Point --path at a folder instead and it finds every lockfile inside (Rust, Python, npm, Java, Go, .NET, Ruby). Add --sbom to print a standard CycloneDX SBOM (a machine-readable bill of materials) instead of the list. That is the "who is crossing my bridge?" list, straight from the project's own lockfile.

Step 3c — read a project's OpenSSF "Scorecard"

The OpenSSF Scorecard tool grades a repository's habits out of 10 (Is it maintained? Is the main branch protected? Does it sign releases? Does its CI do anything dangerous?). modgunn can read that grade offline: provenance::parse_scorecard(json) folds a raw scorecard --format json report into a small ScorecardSignals, and .findings() turns the worrying bits into warnings — a dangerous CI workflow becomes a High finding, a low overall score or a project that looks abandoned becomes a warning, and a branch with no protection a small note. One gotcha to remember: Scorecard is "higher is better", so for the dangerous-workflow check a low score is the bad one (0 = it found something nasty, 10 = all clear).

You can do all that from the command line without writing any Rust: modgunn scorecard --path report.json prints the grade and the warnings, and adding --warehouse <root> files the grade away so modgunn warehouse provenance-log --warehouse <root> can show it again later.

Step 3d — sniff a package for "install-time" mischief

Some malware doesn't have a CVE yet — it's brand-new and nasty. The oldest trick in the npm book: hide a script that runs the moment you type npm install (a postinstall hook), have it download a second program from the internet, and run that. modgunn can smell this without running anything: behavior::parse_npm_package(json) reads a package's package.json text and looks only at the scripts npm runs automatically (preinstall/install/postinstall/prepare — a test or build script you choose to run doesn't count). It notes: does an install script reach out to the network? how many web links does it have? does it use scary tools like child_process or eval? and is there a long, gibberish-looking blob of text (a hidden, encoded payload)? Then .findings() turns those into warnings — a network fetch inside an install hook is a High "dropper" finding, scary tools or an encoded blob are Medium, and simply having an install script at all is a small Low note. It reuses the exact same "how random is this text?" measuring stick (shannon_entropy) the heavy engine uses to spot obfuscation.

The same trick exists in Python packages, so there's a matching sniffer for those: behavior::parse_pypi_setup(setup_py). A Python package's setup.py file is a little program that runs entirely the moment you pip install it — so this one reads the whole file and looks for the same red flags (does it download something? does it call os.system or hide a base64 blob?). It gives you back the exact same kind of warnings, so the rest of modgunn doesn't care whether the package came from npm or PyPI. A normal, boring setup.py (just a name and a list of dependencies) stays completely quiet — no false alarms.

Newer Python packages use a pyproject.toml instead, and that one needs care: it's a settings file, not a program — most of it is just a list of the libraries the package needs and its homepage link. If you naively scanned the whole thing, a perfectly honest package that simply depends on a library called urllib3, or lists a https:// homepage, would look like it "reaches out to the network" — a false alarm on nearly everyone. So behavior::parse_pyproject_toml is smarter: it only reads the few sections that actually run code when you install (the build-system backend, a custom build-hook, a cmdclass override) and ignores the plain dependency and homepage lists. An honest project stays quiet; a sneaky build-hook that downloads and runs a payload still gets the same High warning.

Older Python packages keep those same settings in a setup.cfg file instead (same idea, a different notebook format — INI instead of TOML), so there's a fourth sniffer, behavior::parse_setup_cfg, built exactly the same careful way: it only reads the few settings that genuinely fetch or run something at install time (a dependency_links download list, a cmdclass custom-install command, a build-time plugin) and ignores the ordinary install_requires dependency list and the url = homepage. So the same honest package that just needs urllib3 stays quiet — no false alarm — while a setup.cfg that quietly downloads an extra package from a shady URL still gets the High warning.

You can do all this from the command line, no Rust needed:

modgunn behavior --path ./suspicious/package.json
modgunn behavior --path ./suspicious/setup.py
modgunn behavior --path ./suspicious/pyproject.toml
modgunn behavior --path ./suspicious/setup.cfg

It figures out npm-vs-Python from the filename and prints the warnings. Add --fail-on-finding and it will refuse (exit with an error) the moment it finds anything suspicious — handy for a checklist that has to stop a bad package from being let through.

Step 4 — read what it actually does

The full explanation is a PDF. If someone has already built the docs, open:

docs/manual.pdf

That is the manual: what the verdict means, what the two engines are, and how the warehouse remembers history. The short version:

That is the whole idea: Móðguðr guards the bridge and demands to know who crosses.

Step 5 — the shared buttons every nordisk app has

Every app in the family (korp, nornir, holger, … and Móðguðr) has the same three "shared buttons", like every JetBrains IDE has the same About box and Tools menu. In modgunn they are just commands:

The point: these are borrowed, not re-built. modgunn does not own a copy of the container runner or the service maker — it uses the family's shared ones.

Where to go next