Gunnar

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`](guide.md) instead — this one spells out every keystroke.

![Three steps, from a cold terminal](assets/modgunn-idiot-steps.svg)

## What you need first

- A terminal (the black window where you type commands).
- Rust installed. To check, type this and press **Enter**:
  ```
  cargo --version
  ```
  If it prints a version you are good. If it says "command not found", install Rust from
  <https://rustup.rs> first.
- The modgunn repo on disk. In the terminal, type this and press **Enter**:
  ```
  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](https://securityscorecards.dev) 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:

- **Engine A** = "is this one package safe to let through?" (a Pass/Warn/Block **verdict**).
- **Engine B** = "did anything in my dependency world quietly change?" (a **behaviour-delta**).
- **The warehouse** = "remember every advisory and every snapshot so we can time-travel."

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:

- `modgunn about` — "who is this?" It prints the name, version, and the two engines. (Works
  even in the tiny build.)
- `modgunn tools` and `modgunn demo advisory-feed --out somedir` — the **Tools menu**. It starts
  a small container (using the shared job runner **jera**, never a hand-written `docker` command)
  that writes a pretend advisory into `somedir`, so you have real data to try `warehouse import`
  and `warehouse scan` on.
- `modgunn service --unit` — prints the "how to run me as a background service" file. It runs as
  its **own** user called `modgunn`, written by the shared **skidbladnir** ship — not a
  hand-made file.

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

- To learn to *use* one part, open its own for-idiots or guide file — e.g.
  [`verdict-guide.md`](verdict-guide.md) for the Pass/Warn/Block engine, or
  [`framework-guide.md`](framework-guide.md) for the About / Tools / service buttons.
- To learn *why* it is built this way, open [`reasoning.md`](reasoning.md).