Rule Format¶
TL;DR
- Rules are JSON files following
pkg/rules/schema.json. - Each file has a
schema_versionand arulesarray. - A rule combines package detection, dropper identification, host IOCs, and remediation.
- Version expressions use npm semver syntax.
Who is this for?
Audience: Anyone writing, reviewing, or consuming rules. Reading time: ~8 minutes.
Structure¶
{
"schema_version": "1.0.0",
"rules": [
{
"id": "SSC-2025-001",
"title": "Human-readable title",
"kind": "compromised-release",
"ecosystem": "npm",
"severity": "critical",
"summary": "Brief description.",
"aliases": [],
"references": [],
"package_rules": [],
"dropper_packages": [],
"host_indicators": [],
"remediation": {},
"metadata": {}
}
]
}
Required Fields¶
| Field | Type | Description |
|---|---|---|
id |
string | Unique identifier. Convention: SSC-YYYY-NNN. |
title |
string | Human-readable title. |
kind |
enum | Incident type (see below). |
ecosystem |
enum | Currently only "npm". |
severity |
enum | low, medium, high, critical. |
package_rules |
array | At least one package rule entry. |
Kind Values¶
| Value | Use when |
|---|---|
compromised-release |
Legitimate package had malicious versions published. |
malicious-package |
Purpose-built malicious package (typosquat, etc.). |
vulnerability |
Traditional CVE-style vulnerability. |
dropper |
Package whose sole purpose is payload delivery. |
suspicious-artifact |
Artifact that warrants investigation. |
Package Rules¶
{
"package_name": "axios",
"affected_versions": ["=1.7.8", "=1.7.9", "=1.8.1"],
"lockfile_ecosystems": ["npm", "pnpm", "yarn", "bun"],
"notes": "Optional context."
}
Version Expressions¶
| Format | Example | Meaning |
|---|---|---|
| Exact | =1.7.8 |
Only version 1.7.8. |
| Bare | 1.7.8 |
Same as =1.7.8. |
| Wildcard | * |
Any version. |
| Range | >=1.0.0 <2.0.0 |
Versions satisfying the semver constraint. |
| Caret | ^1.7.0 |
Compatible versions (same major). |
| Tilde | ~1.7.0 |
Patch-level versions (same major.minor). |
Semver range matching is implemented using Masterminds/semver v3. VersionSet.Matches() checks exact match first, then evaluates semver constraints. VersionSet.RangeCoversVersion() checks if a package.json range expression could resolve to a compromised version. Both sides are compiled into intervals with optional lower/upper bounds (e.g., <1.0.0 → (-∞, 1.0.0)), and overlap is structural: two intervals overlap unless one ends strictly before the other starts. Disjunctive constraints (||) are a union of intervals.
Dropper Packages¶
{
"package_name": "plain-crypto-js",
"notes": "Installed by compromised axios postinstall script."
}
Dropper packages are indexed with wildcard matching — any version is a finding.
Host Indicators¶
{
"type": "file",
"path": "/tmp",
"file_name": "ld.py",
"oses": ["linux"],
"hashes": [
{ "algorithm": "sha256", "value": "abcdef..." }
],
"confidence": "high",
"notes": "RAT payload dropped by postinstall script."
}
Indicator Types¶
| Type | Description | Status |
|---|---|---|
file |
File existence check (path and/or file_name); hash verification when hashes are present. |
Done |
process |
Running process name. | Schema only |
registry |
Windows registry key. | Schema only |
network |
Network connection indicator. | Schema only |
environment |
Environment variable check. | Schema only |
Hash Arrays¶
One file indicator can have multiple hashes for variant builds:
"hashes": [
{ "algorithm": "sha256", "value": "aaa..." },
{ "algorithm": "sha256", "value": "bbb..." }
]
Validated lengths: md5=32, sha1=40, sha256=64, sha512=128 hex characters.
Path Expansion¶
%PROGRAMDATA%→ Windows ProgramData directory.%APPDATA%→ Windows AppData/Roaming directory.~→ Current user's home directory.
Aliases, References, Remediation, Metadata¶
See the full JSON Schema at pkg/rules/schema.json in the repository for all optional fields including aliases, references, remediation, and metadata.
Check your understanding
- Can you write a minimal valid rule from memory?
- Do you know the difference between a package rule and a dropper package?
- Can you explain why host indicator hashes are arrays?
Next Steps¶
- Write a real rule → Writing Rules
- See existing rules → browse the
rules/directory - Validate rules → check against
pkg/rules/schema.json