Repository model

June 21, 2026 · View on GitHub

How rustinel-rules is organized, how packs reference detections, and how the build turns the source tree into something Rustinel loads.

The two-repo split

RepoRole
rustinelThe engine: collects telemetry, evaluates rules, writes alerts.
rustinel-rulesThe detection content the engine loads (this repo).

They are versioned independently — content evolves faster than the engine — and compatibility is declared explicitly per pack (requires_rustinel). The repository must remain usable by Rustinel users out of the box.

Artifacts: each detection lives once

There are three detection kinds, and they share one model. Each is an artifact with a stable id, stored once under rules/, and referenced from packs by that id.

KindLocationid sourceLoaded by Rustinel as
Sigmarules/sigma/<os>/*.ymlid: (UUID v4)scanner.sigma_rules_path
YARArules/yara/<os>/*.yarmeta: id = "..." (else rule name)scanner.yara_rules_path
IOC setrules/ioc/<os|common>/*.ymlid: (prefixed ioc-)flattened into [ioc] files

Rules are never manually duplicated across packs. A rule appears in exactly one file; packs include it by listing its id. This is enforced by the unique-id check in validate.py.

An IOC set is a typed collection — a campaign or tool yields many indicators (hashes, IPs, domains, path regexes) grouped into one set. The build flattens every referenced set into the per-type flat files the engine consumes.

Packs: cumulative manifests

A pack is a manifest at packs/<os>/<level>/pack.yml. It lists the artifact ids it adds on top of any pack it extends. Packs are cumulative:

Essential  ⊂  Advanced  ⊂  Hunting

So Advanced extends: [windows-essential] and only lists the extra rules — it never re-lists Essential's rules. The build resolves the full transitive membership (lower levels first, de-duplicated). See the pack catalog for the live set, and the pack schema for every field.

Key manifest fields:

FieldMeaning
id / nameStable id (^[a-z0-9]+(-[a-z0-9]+)*$) and human name.
oswindows | linux | macos.
levelessential | advanced | hunting.
pack_schema_versionRequired pack manifest schema version (must be 2).
defaultWhether this pack is enabled by default.
extendsPack ids cumulatively included (rules merged, never duplicated).
rulesOptional dictionary specifying rules directly in this pack (has), or rules to include (includes) / exclude (excludes) from extended packs or automatic sources.
sourcesOptional dictionary of upstream sources categorized by type (manual, sigma, yara).
requires_rustinelEngine version constraint, e.g. ">=1.0.2".
attack_coverageATT&CK technique ids covered (drift-checked against members).
telemetry_requirementsRustinel telemetry channels the pack needs.
expected_false_positive_level / status / test_statusQuality signals surfaced in index.json.

The build pipeline

one folder = one pack = one zipped Rustinel rules folder.

The source repo uses manifests + canonical storage; CI generates the final flat, zipped pack artifacts the engine consumes, plus an index.json catalog.

uv run python tools/validate.py        # gate: structure, metadata, ids, telemetry, IOC sanity
uv run python tools/build_packs.py     # materialize dist/ (folders + zips + index.json)
uv run python tools/build_catalog.py   # materialize dist/catalog.json for the website

Output lands in dist/:

dist/
├── index.json                       # Catalog of all packs (+ engine paths, sha256, counts)
├── catalog.json                     # Website catalog: rules, ATT&CK techniques, packs
├── windows-essential/               # Materialized flat pack folder (engine drop-in)
│   ├── pack.yml                      # Cleaned manifest + build metadata (version, counts)
│   ├── sigma/                        # .yml  -> scanner.sigma_rules_path
│   ├── yara/                         # .yar  -> scanner.yara_rules_path
│   └── ioc/                          # hashes.txt / ips.txt / domains.txt / paths_regex.txt
├── windows-essential-<version>.zip   # Rustinel-compatible artifact
└── ...

What the build does for each pack:

  1. Resolves the cumulative rule list (extends + rules with has/includes/excludes or pack subfolders, de-duplicated).
  2. Copies each Sigma/YARA file into the flat sigma and yara folders.
  3. Flattens every referenced IOC set into the four per-type files in VALUE;COMMENT format, prefixing each line with its source set id ([ioc-…]) so provenance survives into alerts.
  4. Writes a cleaned pack.yml with build metadata and zips the folder.
  5. Records a catalog entry in index.json with engine paths and a sha256.

Website catalog

tools/build_catalog.py produces dist/catalog.json, a richer catalog for the Rustinel website. It contains full rule records, ATT&CK technique metadata, pack memberships and source URLs. The website repository syncs that generated file into its committed src/data/rustinel-rules.json snapshot, then builds from that snapshot without needing this repo checked out.

When detection content changes, refresh the website data in two steps:

# from rustinel-rules
uv run python tools/build_catalog.py

# from ../rustinel-front-final
npm run sync:rules

index.json shape

Each pack entry includes drop-in engine paths so an installer can wire config.toml automatically:

{
  "id": "windows-essential",
  "version": "0.2.0",
  "default": true,
  "rule_count": 14,
  "ioc_count": 3,
  "sha256": "…",
  "artifact": "windows-essential-0.2.0.zip",
  "engine": {
    "sigma_rules_path": "windows-essential/sigma",
    "yara_rules_path": "windows-essential/yara",
    "hashes_path": "windows-essential/ioc/hashes.txt",
    "ips_path": "windows-essential/ioc/ips.txt",
    "domains_path": "windows-essential/ioc/domains.txt",
    "paths_regex_path": "windows-essential/ioc/paths_regex.txt"
  }
}

Where to go next