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
| Scenario | Operator |
|---|---|
| Known exact substring | CONTAINS |
| Known exact value | EQUALS |
| File path matching | GLOB |
| Complex pattern | REGEX |
| Whole-word match | WORD |
| 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