Skip to main content

Rule Syntax Reference

SigmaShake rules use a declarative DSL designed for readability and safety. Each rule defines a condition that matches tool calls and a decision to apply.

Basic structure

rule <rule_id> {
enabled true
priority <number>
severity <error|warning|info>
<DECISION> <target>
IF <condition>
MESSAGE "<text>"
[CONTEXT "<text>"]
[RUN "<command>"]
[WEBHOOK "<url>"]
}

Minimal example

rule no-rm-rf {
enabled true
DENY execution
IF command CONTAINS "rm -rf"
MESSAGE "Destructive command blocked."
}

Default values when omitted:

  • priority50
  • severitywarning
  • enabledtrue

Rule ID

Must be a valid identifier: letters, numbers, hyphens, underscores. Used in audit logs and API responses.

rule my-rule-123 { ... }
rule ts_write_safety { ... }

Decision types

DSL KeywordRuntime DecisionBehavior
DENYblockReject the tool call
ALLOWallowPermit explicitly (overrides lower-priority DENY)
LOGlogAllow but record in audit log
SHADOWshadowAllow silently, log for monitoring
ASKaskPause execution, require human approval via dashboard
FORCEforceBlock and return an error prompting the agent to use a safer substitute command

Not every rule has a decision. Ambient knowledge rules carry standing project context and never gate a tool call, so they omit the decision keyword entirely — AMBIENT is a rule directive, not a seventh decision type. See Ambient knowledge rules below.

ASK decisions

Require a PROMPT field:

rule ask-before-reset {
enabled true
ASK execution
IF command CONTAINS "git reset --hard"
MESSAGE "Hard reset requires approval."
PROMPT "Allow git reset --hard?"
}

FORCE decisions

Require a SUBSTITUTE field:

rule force-exact-pins {
enabled true
FORCE execution
IF command REGEX "npm install\\s+\\S+@\\^"
MESSAGE "Use exact version pins."
SUBSTITUTE "npm install package@1.2.3"
}

CONTEXT — surface text to the AI

CONTEXT "<text>" is an optional directive that injects text into the AI's view when the rule matches. It is orthogonal to the decision — combinable with ALLOW, LOG, SHADOW, BLOCK (DENY), ASK, and FORCE.

Use it to drop a reminder, hint, or runbook link without blocking the tool call:

rule remind-elixir-tests {
enabled true
ALLOW execution
IF command CONTAINS "mix test"
MESSAGE "Heads up."
CONTEXT "OTP 28 hangs mix test on this box. Use `mix help test.<alias>` instead."
}

When the rule fires, Claude Code receives the text and prepends it to the tool result the model sees on the next turn — so the AI can adjust without the user having to intervene.

How it reaches the AI by decision

DecisionWire format
ALLOW, LOG, SHADOWEmitted as Claude Code hookSpecificOutput.additionalContext (PreToolUse). Tool runs; AI sees the context with the result.
DENY (block) / FORCEAppended to the stderr block message. Claude Code surfaces stderr to the model on exit-code 2.
ASKEmitted alongside permissionDecisionReason as additionalContext in the same hookSpecificOutput JSON — the user sees the reason in the approval dialog, the AI sees the context once the call resumes.

When to use CONTEXT vs. MESSAGE

  • MESSAGE is for humans — shown in dashboards, audit logs, and the approval prompt.
  • CONTEXT is for the AI — fed back into the model's reasoning loop so it changes its next action.

They are complementary; most rules will set both.

Constraints

  • Empty strings are rejected at parse time (CONTEXT "" → parse error).
  • One CONTEXT per rule; last write wins if duplicated.
  • Survives audit and soft runtime modes (it never enforces, so it never needs to be downgraded).

Ambient knowledge rules

An ambient rule carries always-on project knowledge instead of gating a tool call. It has no decision and no IF/OR conditions — it is never evaluated against tool calls. Instead, its MESSAGE + CONTEXT are surfaced to the agent at session start by ssg hook session-start. This is the SSG-native equivalent of an AI-agent memory file.

Mark a rule ambient with the standalone AMBIENT directive:

rule mem_otp28_mix_test_hang {
priority 80
severity warning
AMBIENT
MESSAGE "OTP 28 silently hangs `mix test` on this box."
CONTEXT "The erl_child_setup bug hangs the test wrapper on 64+ core machines. Verify changes with `mix compile` or `mix help test.<alias>` instead — the main agent runs `mix test` synchronously when asked."
}

Fields

FieldRequiredPurpose
AMBIENTyesMarks the rule as a knowledge carrier. Takes no decision keyword.
MESSAGEyesOne-line headline. Shown in the session-start index.
CONTEXTyesThe knowledge body. Pulled on demand via ssg recall.
prioritynoHigher = listed first at session start. Default 50.
severitynoInformational label only (error/warning/info). Default warning.

An ambient rule takes no <DECISION>, no target, and no IF/AND/OR conditions — only the metadata above.

Session start and recall

At session start, ssg hook session-start emits a compact index of every ambient rule — one MESSAGE headline per entry, grouped by area — rather than dumping every CONTEXT body, keeping the injected context small.

Pull the full CONTEXT for any entry on demand:

ssg recall <keyword-or-id> # full MESSAGE + CONTEXT for matching entries
ssg recall # reprint the headline index

Ambient rule vs. CONTEXT on a gating rule

Both surface text to the AI, but at different times and for different reasons:

Ambient ruleCONTEXT on a gating rule
WhenSession start, alwaysWhen the rule matches a tool call
TriggerNone — unconditionalThe rule's IF conditions
DecisionNoneALLOW / LOG / DENY / ASK / FORCE / SHADOW
Use forStanding project knowledgeA just-in-time hint tied to a specific action

Where ambient rules live

By convention, ambient knowledge lives in mem_*.rules files under .sigmashake/rules/. The ambient flag is a file-level field — like RUN and WEBHOOK, it is not persisted to the rules database, and loadRulesFromFiles excludes ambient rules from the evaluator's active set so they never participate in tool-call decisions.

In the dashboard, ambient rules appear on the Rules page with a teal AMBIENT badge in place of the decision selector — a visual reminder that they carry no verdict.

Triggers (optional)

Any rule can include RUN and/or WEBHOOK lines that fire asynchronously when the rule matches. Triggers are fire-and-forget — failures log to stderr but never block eval output.

RUN — execute a script

rule block-and-notify {
enabled true
DENY execution
IF command CONTAINS "rm -rf"
MESSAGE "Destructive command blocked."
RUN "./scripts/on-block.sh"
}

The script receives a JSON payload on stdin:

{
"tool": "Bash",
"input": {"command": "rm -rf /"},
"rule_id": "block-and-notify",
"decision": "block",
"matched_pattern": "command CONTAINS \"rm -rf\""
}

WEBHOOK — send an HTTP POST

rule audit-network {
enabled true
LOG network
IF tool EQUALS "WebFetch"
MESSAGE "Network request logged."
WEBHOOK "https://hooks.slack.com/services/T00/B00/xxx"
}

The webhook receives the same JSON payload as the POST body (Content-Type: application/json). Timeout is 5 seconds.

Combining triggers with any decision

Triggers work with all decision types — DENY, ALLOW, LOG, SHADOW, ASK, FORCE. You can use both RUN and WEBHOOK on the same rule:

rule full-trigger {
enabled true
DENY execution
IF command CONTAINS "drop table"
MESSAGE "SQL drop blocked."
RUN "./scripts/alert.sh"
WEBHOOK "https://example.com/audit"
}

Targets

Targets determine which tool capability a rule applies to:

TargetMatches tools with capability
executionShell commands (Bash, terminal)
readFile reading (Read, cat)
writeFile writing/editing (Write, Edit)
editEdit only (subset of write)
searchFile/content search (Glob, Grep)
agentSub-agent spawning (Agent)
networkHTTP requests (WebFetch, WebSearch)
anyAll tools

Conditions

Syntax

IF <field> [NOT] <operator> "<value>"

Fields

FieldResolves to
commandinput.command (Bash commands)
pathinput.file_path or input.path
contentinput.content or input.new_string
toolTool name (e.g., "Bash", "Read")
input.<key>Any key in the tool input object

Operators

OperatorDescription
CONTAINSSubstring match
EQUALSExact string match
STARTS_WITHPrefix match
ENDS_WITHSuffix match
GLOBGlob pattern (*, **, ?)
REGEXRegular expression
WORDWord-boundary match (no regex, O(n))
LINE_CONTAINSPer-line substring, strips // comments
LINE_REGEXPer-line regex, strips // comments

All operators support NOT for negation:

IF command NOT CONTAINS "echo"

AND / OR logic

Within a block, conditions are AND-ed. Use OR to start a new group:

rule block-secret-writes {
enabled true
DENY write
IF path ENDS_WITH ".env"
AND content CONTAINS "API_KEY"
OR path ENDS_WITH ".env.local"
AND content CONTAINS "SECRET"
MESSAGE "Cannot write secrets to env files."
}

This means: (path ends with .env AND content contains API_KEY) OR (path ends with .env.local AND content contains SECRET).

Priority

Higher priority rules are evaluated first. First match wins (short-circuit).

rule allow-echo {
enabled true
priority 100
ALLOW execution
IF command STARTS_WITH "echo"
MESSAGE "Echo commands are always allowed."
}

rule block-all-bash {
enabled true
priority 50
DENY execution
IF tool EQUALS "Bash"
MESSAGE "Bash commands require approval."
}

With priority 100 > 50, echo hello matches allow-echo first and is allowed.

Generic input fields

Access any key in the tool input object using input.<key>:

rule log-explore-agents {
enabled true
LOG agent
IF input.subagent_type EQUALS "Explore"
MESSAGE "Explore agent activity logged."
}

rule block-background-agents {
enabled true
DENY agent
IF input.run_in_background EQUALS "true"
AND input.subagent_type NOT EQUALS "general-purpose"
MESSAGE "Only general-purpose agents may run in background."
}

Non-string values are JSON-stringified before matching. Missing fields resolve to empty string (no match for positive conditions).

Enabling/disabling

rule temporarily-disabled {
enabled false
DENY execution
IF command CONTAINS "test"
MESSAGE "This rule is off."
}

File naming convention

Follow the Sigma-style convention: {technology}_{capability}_{description}.rules

Examples:

  • ts_write_safety.rules
  • git_exec_trunk_workflow.rules
  • bash_exec_destructive_ops.rules
  • agent_exec_oracle_redirect.rules