Skip to main content

Writing Rules

Best practices and patterns for writing effective governance rules.

Start with the baseline

Run ssg init to get a starter ruleset that covers common safety scenarios:

  • Block destructive commands (rm -rf, git push --force)
  • Prevent secret file access (.env, credentials)
  • Enforce code style (@ts-ignore, console.log)
  • Require exact version pins

Rule design principles

1. Be specific, not broad

Bad:

rule block-all-bash {
DENY execution
IF tool EQUALS "Bash"
MESSAGE "All Bash commands blocked."
}

Good:

rule block-destructive-commands {
DENY execution
IF command CONTAINS "rm -rf"
OR command CONTAINS "git reset --hard"
MESSAGE "Destructive command blocked."
}

2. Use priority to create exceptions

Higher priority rules are checked first:

rule allow-echo {
priority 100
ALLOW execution
IF command STARTS_WITH "echo"
MESSAGE "Echo is always safe."
}

rule block-dangerous {
priority 50
DENY execution
IF command REGEX "(rm|del|drop|truncate)"
MESSAGE "Dangerous command blocked."
}

3. Use the right operator

ScenarioOperator
Known exact substringCONTAINS
Known exact valueEQUALS
File path matchingGLOB
Complex patternREGEX
Whole-word matchWORD
Code analysis (ignore comments)LINE_CONTAINS or LINE_REGEX

4. Test your rules

echo '{"tool":"Bash","input":{"command":"rm -rf /"}}' | ssg test-rule --rule block-destructive-commands --explain

5. Use LOG before DENY

When introducing new rules, start with LOG to observe matches without blocking:

rule observe-network-calls {
LOG network
IF tool EQUALS "WebFetch"
MESSAGE "Network call observed."
}

Once you've confirmed it catches the right calls, change to DENY.

Common patterns

Block and suggest alternative

rule use-read-not-cat {
FORCE execution
IF command STARTS_WITH "cat "
MESSAGE "Use the Read tool instead of cat."
SUBSTITUTE "Read"
}

Require approval for sensitive operations

rule approve-deploys {
ASK execution
IF command CONTAINS "npm publish"
OR command CONTAINS "wrangler deploy"
MESSAGE "Deployment requires approval."
PROMPT "Allow deployment to proceed?"
}

Monitor agent activity

rule log-agent-spawns {
LOG agent
IF tool EQUALS "Agent"
MESSAGE "Agent spawn logged."
}

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

Protect specific directories

rule protect-migrations {
DENY write
IF path GLOB "migrations/**"
MESSAGE "Migration files are read-only. Create new migrations instead."
}

Enforce naming conventions

rule test-file-naming {
DENY write
IF path GLOB "test/**"
AND path NOT ENDS_WITH ".test.ts"
MESSAGE "Test files must end with .test.ts"
}

File organization

Group rules by technology and capability:

.sigmashake/rules/
├── bash_exec_destructive_ops.rules
├── bash_exec_safe_commands.rules
├── git_exec_trunk_workflow.rules
├── ts_write_safety.rules
├── ts_write_gts_style.rules
├── agent_exec_oracle_redirect.rules
└── fs_read_secret_files.rules

Debugging rules

Lint for syntax errors

ssg lint

Test with explain

echo '{"tool":"Bash","input":{"command":"test"}}' | ssg eval --explain

Check which rule matched

The eval output includes rule_id — trace it back to the rule definition.

Check for overlaps

ssg dedupe