Rules are the engine
The design series' central inversion: rules are not configuration on top of a fixed engine — rules are the engine. The core is a small, stable machine for executing them; every block, every allow, every log action is a rule. The MVP ships a deliberately tiny core: six deterministic rules, all deterministic-certain, all at Layer 1.
The six rules of the tiny core
| Rule | Name | Catches | ATT&CK | Action |
|---|---|---|---|---|
0001 | capture-all | every request — assembles the log fields; pure observation, and the allow fallthrough | — | pass |
0003 | banned-ip | source IP in the embedded list (the canonical embedded-data example) | T1595 | drop_403 |
0007 | malformed-request | structurally invalid request (no method, no path, path not starting /) | T1190 | drop_403 |
0012 | path-never-valid | /etc/passwd and path traversal (..) | T1190 | drop_403 |
0014 | hidden-file-probe | /.env, /.git/… | T1083 | deflect_404 |
0018 | wp-scan-on-static | /wp-login.php, /wp-admin/…, /xmlrpc.php on a static site | T1595 | deflect_404 |
Each rule is a pure function (captured) → {verdict, reason, rule_id, action} | null. The engine runs them in order; first block wins; otherwise allow. Every one of them, verbatim from the deployed engine:
function rule_0007_malformed(c){ if(!c.method||!c.path||c.path.charAt(0)!=='/') return {verdict:'block',reason:'malformed request',rule_id:'0007',action:'drop_403'}; } function rule_0003_banned_ip(c){ if(BANNED_IPS.indexOf(c.source_ip)>=0) return {verdict:'block',reason:'banned ip',rule_id:'0003',action:'drop_403'}; } function rule_0012_path_never_valid(c){ var p=c.path.toLowerCase(); if(p.indexOf('/etc/passwd')>=0||p.indexOf('..')>=0) return {verdict:'block',reason:'path never valid',rule_id:'0012',action:'drop_403'}; } function rule_0014_hidden_file(c){ if(/^\/\.(env|git)(\/|$)/.test(c.path)) return {verdict:'block',reason:'hidden file probe',rule_id:'0014',action:'deflect_404'}; } function rule_0018_wp_scan(c){ var p=c.path.toLowerCase(); if(p==='/wp-login.php'||p==='/xmlrpc.php'||p.indexOf('/wp-admin')===0) return {verdict:'block',reason:'wordpress scan on static site',rule_id:'0018',action:'deflect_404'}; }
That is the entire security logic of the MVP, readable in five lines-per-rule. Smallness is the point: the tiny core proves the spine — engine, signal, actor, sink, parity — on rules whose correctness is obvious, before any clever rule is added.
Rules are data: metadata from day one
The rule logic lives in the JS engine; the rule metadata lives in a Python registry (Sentinel__Rule__Registry.py) that backs sg sentinel rules list/show. Each entry carries the schema the design series specified — present from day one even though the MVP engine is a flat ordered list:
| Field | Purpose | Example |
|---|---|---|
rule_id | stable identity, traceable in every signal and log record | 0012 |
layer | the altitude the rule runs at | L1 |
attack_tag | MITRE ATT&CK technique the rule defends against | T1190 |
confidence | where on the deterministic-to-opinion spectrum | deterministic-certain |
action | the HTTP-level block action | drop_403 · deflect_404 |
Actions are defined at the HTTP level because the tabletop demanded it (GAP 6.1 — "drop silently" is not a thing HTTP can do): drop_403 returns an empty 403; deflect_404 pretends the path does not exist; wild-goose-chase / tarpit responses are a designed, deferred deception action.
Where this goes: the fractal rule graph
The design series is explicit that a flat list does not scale — every request would pay for every rule. The designed structure is a fractal graph: rules within rule sets, rule packs that activate only when a triggering rule fires, rules that select which rules run next. The cost of a request becomes proportional to the rules it actually traverses, not the total rule count. Rules carry rich metadata — semantic-graph position, compliance-standard mapping, attack-tree mapping, next-rule connections — so one rule is a node in several graphs at once.
Alongside the graph, the series specifies (all deferred from this MVP, all documented in the briefs):
- The deterministic-to-opinion spectrum — opinion rules carry confidence and combine through CRS-style anomaly scoring rather than binary verdicts.
- Rules-as-vault — every rule version-controlled (RFD-style versioning), the active rule-set a manifest of pinned versions, the production bundle containing only the rules production runs.
- Dangerous dev-only rules — e.g. a rule that enables/disables other rules for differential testing; phase-gated so they exist in dev and are excluded from production bundles by construction.
- LLMs as rule authors, never inline — heavy LLM use to build, test and threat-model rules at development time; in production, no LLM ever runs in the request path.
- Replay-from-S3 — production traffic in the sink is the regression corpus: before deploying a new rule, replay it against last week's real traffic and ask "would this have blocked anything legitimate?" The log record schema is designed replayable for exactly this.