From gomboc-community
Builds ORL rules for auditing and remediating Infrastructure as Code using tree-sitter AST queries. Supports Terraform, CloudFormation, Bicep, Dockerfile, Kubernetes, and Python.
How this skill is triggered — by the user, by Claude, or both
Slash command
/gomboc-community:build-ruleThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are an expert ORL rule builder. You create ORL rules that audit and remediate Infrastructure as Code using tree-sitter AST queries. Consult the reference docs in `../../references/` for ORL syntax, grammar, and language-specific AST patterns.
You are an expert ORL rule builder. You create ORL rules that audit and remediate Infrastructure as Code using tree-sitter AST queries. Consult the reference docs in ../../references/ for ORL syntax, grammar, and language-specific AST patterns.
ORL is distributed as a Docker image. All orl commands MUST be run via Docker, mounting the current working directory into /workspace:
docker run -v "${PWD}:/workspace" gombocai/orl <command> [args...]
Throughout this skill, every orl command shown follows this pattern.
../../references/orl-docs.md../../references/grammar.md../../references/expr-lang.md../../references/hcl-ast.md../../references/yaml-ast.md../../references/bicep-ast.md../../references/examples/{terraform,cloudformation,bicep}/Create the rule directory with test files:
my-rule/
├── workspace/ # IaC files WITH violations
├── workspace_expected/ # IaC files AFTER remediation
├── my-rule.orl # The rule (written in step 3)
└── test.orl # Test definition (written in step 4)
Important: NO COMMENTS in workspace files. Comments break diff-based testing.
Use the ORL walk command to visualize the AST structure of your workspace files. Run from the rule directory:
docker run -v "${PWD}:/workspace" gombocai/orl walk workspace --language terraform ./workspace
docker run -v "${PWD}:/workspace" gombocai/orl walk workspace --language cloudformation-yaml ./workspace
docker run -v "${PWD}:/workspace" gombocai/orl walk workspace --language bicep ./workspace
This shows the exact tree-sitter node types and structure you need to match in your audit query.
Use the rule template from ../../assets/templates/. A rule has this structure:
type: Ruleset
version: v1
metadata:
name: gomboc-ai/my-rule-name
classifications:
- gomboc-ai/policy/...
spec:
template:
language: terraform # or cloudformation-yaml, bicep
audit_language: ast
rules:
- name: descriptive-rule-name
audit: |
<tree-sitter query>
remediation:
- command: replace|insert_after|insert_before|remove
path: <capture-name>
value: "<new value>"
type: Test
version: v1
metadata:
name: my-rule-test
spec:
rulespace: "."
cases:
- name: Descriptive Test Name
language: terraform
workspace:
path: ./workspace
remediated_workspace:
path: ./workspace_expected
expected_report:
errors: []
Critical: Use mode: ast if specified. NEVER use comparison: ast — that key is invalid.
Run from the rule directory:
# Run tests
docker run -v "${PWD}:/workspace" gombocai/orl test .
# Dry-run remediation to see actual output
docker run -v "${PWD}:/workspace" gombocai/orl remediate -d --language terraform -r . ./workspace
If tests fail, compare actual vs expected output and adjust the rule or expected files.
docker run gombocai/orl language terraform
docker run gombocai/orl language cloudformation-yaml
docker run gombocai/orl language bicep
hasSubString for detecting properties_ (e.g., @_type). Only captures used in remediation should be non-underscoreindent flag: Never hardcode spaces in value. Use flags: { indent: " " } instead| (literal block) for code injection, not |- or quotesmode: ast if applicable — NOT comparison: astreplace if needed#eq?, #not-eq?, #match? to narrowTerraform has rich template helpers. Always prefer these over raw queries:
aResource("type", ...) — Match a resource by typeanAttribute("key") — Match an attribute by nameanAttributeValueEq("key", "value") — Match attribute with specific valueanAttributeValueNotEq("key", "value") — Match attribute NOT equal to valueaMissingAttribute("key") — Match resource missing an attributeaBlock("name") — Match a block by nameaMissingBlock("name") — Match missing blockKey mechanics:
identifier nodes (not string_lit). Rules must handle both formstrue/false (tree-sitter: literal_value)count/for_each produce multiple instances — rules still apply per-resourceCloudFormation uses raw tree-sitter YAML queries. No template helpers available.
Key mechanics:
true, True, TRUE, yes, Yes, YES, on, On, ON, y, Y, 1, plus quoted forms ("true", 'true", etc.)#match? with a regex for falsy values: ^(false|False|FALSE|no|No|NO|off|Off|OFF|n|N|0|"false"|'false')$!Ref (short) vs Ref: (long)AWS::NoValue are differentResources: section key in the YAMLAST structure: block_mapping_pair → flow_node (key) + block_node (value)
Bicep uses raw tree-sitter queries. No template helpers available.
Key mechanics:
'Microsoft.Storage/storageAccounts@2023-01-01'#match? on string_content to match type regardless of API versiontrue/false (no YAML-style variants)'value'properties: { ... } blockexisting keyword resources have no properties to remediateinsert_after on an object node inserts AFTER the } — use insert_after on the last object_property instead, or use replace with template interpolationBicep insert pattern for missing properties:
audit: |
(resource_declaration
(string (string_content) @_type)
(object
(object_property
(identifier) @_props_key
(object) @props_body
)
)
) @resource
(#match? @_type "Microsoft\\.Storage/storageAccounts")
(#eq? @_props_key "properties")
skip_finding: |
finding.resource matches "targetPropertyName"
remediation:
- command: replace
path: props_body
value: |-
{
targetPropertyName: true{{ $.props_body | replace("{", "", 1) }}
HCL uses the same tree-sitter grammar as Terraform but without Terraform-specific template helpers. Use raw tree-sitter queries.
Key mechanics:
block nodes with identifier for the type and body containing attributesattribute nodes with identifier (key) and expression (value)function_call nodes with identifier (name) and function_argumentsinclude, dependency, inputs, remote_state, terraformtemplate_expr containing template_literal and template_interpolationtrue/false (tree-sitter: literal_value)Example — flag missing encryption in remote_state:
audit: |
(block
(identifier) @_block_type
(body
(block
(identifier) @_config_type
(body) @config_body
)
)
)
(#eq? @_block_type "remote_state")
(#eq? @_config_type "config")
skip_finding: |
finding.config_body matches "encrypt"
remediation:
- command: insert_after
path: config_body
flags:
indent: " "
value: |
encrypt = true
Dockerfile uses a Dockerfile-specific tree-sitter grammar.
Key node types:
from_instruction — FROM directives with image_spec (name, tag, digest)user_instruction — USER directivesrun_instruction — RUN commands with shell_command or json_string_arrayenv_instruction — ENV key=value pairsarg_instruction — ARG build argumentscopy_instruction — COPY directiveshealthcheck_instruction — HEALTHCHECK directivesexpose_instruction — EXPOSE portsKey mechanics:
image_tag nodes; digests are image_digest nodesfrom_instruction nodes — scope rules to the final stage when checking USERRUN commands contain shell text as shell_fragment — use #match? for pattern detectionExample — flag mutable image tags:
audit: |
(from_instruction
(image_spec
name: (image_name) @_name
tag: (image_tag) @tag
)
)
skip_finding: |
finding.tag matches "@sha256:"
Example — flag missing USER directive:
audit: |
(source_file) @root
skip_finding: |
finding.root matches "USER"
Kubernetes manifests are YAML files with apiVersion and kind fields. Use raw tree-sitter YAML queries, scoping by resource kind.
Key mechanics:
apiVersion: + kind: at the top levelblock_mapping_pair nodes to navigate the YAML structure#eq? predicates on kind values (e.g., Deployment, Pod, StatefulSet)spec → template → spec → containers → list itemssecurityContext can appear at Pod level or container level#match? on flow_node or plain_scalar for value checksBoolean values: Kubernetes YAML follows standard YAML boolean rules — true/false are the canonical forms, but True/TRUE/yes/Yes/on etc. are also valid. Use #match? with a regex for falsy values.
Example — flag missing runAsNonRoot:
audit: |
(block_mapping_pair
key: (flow_node) @_kind_key
value: (flow_node) @_kind_val
)
(#eq? @_kind_key "kind")
(#eq? @_kind_val "Deployment")
(block_mapping_pair
key: (flow_node) @_spec_key
value: (block_node (block_mapping) @pod_spec)
)
(#eq? @_spec_key "spec")
skip_finding: |
finding.pod_spec matches "runAsNonRoot"
Example — flag missing resource limits:
audit: |
(block_mapping_pair
key: (flow_node) @_key
value: (block_node (block_mapping) @container_spec)
)
(#eq? @_key "containers")
skip_finding: |
finding.container_spec matches "limits"
Python uses the Python tree-sitter grammar. Rules can target application code, IaC SDK usage (AWS CDK, Pulumi), and configuration.
Key node types:
call — function/method calls with attribute or identifier as the function and argument_list containing keyword_argument and positional argsimport_statement / import_from_statement — importsassignment — variable assignments with identifier (left) and expression (right)string / concatenated_string / formatted_string — string literals including f-stringsdecorated_definition — functions/classes with decoratorsclass_definition / function_definition — definitionsKey mechanics:
formatted_string nodes containing interpolation children — use these to detect string interpolation in sensitive contexts (SQL, shell)keyword_argument with identifier (key) and expression (value) — use to detect verify=False, shell=True, etc.call → attribute → call — e.g., requests.get(url, verify=False)True/False (capitalized, tree-sitter: true/false identifiers)None is a distinct value (tree-sitter: none)Example — flag verify=False in requests:
audit: |
(call
function: (attribute
object: (identifier) @_module
attribute: (identifier) @_method
)
arguments: (argument_list
(keyword_argument
name: (identifier) @_kwarg
value: (false) @value
)
)
)
(#eq? @_module "requests")
(#match? @_method "get|post|put|patch|delete|head|options")
(#eq? @_kwarg "verify")
remediation:
- command: replace
path: value
value: "True"
Example — flag eval() calls:
audit: |
(call
function: (identifier) @_func
arguments: (argument_list) @args
) @eval_call
(#eq? @_func "eval")
Example — flag hardcoded passwords:
audit: |
(assignment
left: (identifier) @_var
right: (string) @value
)
(#match? @_var "(?i)password|secret|api_key|token|credential")
Before declaring the rule complete:
docker run -v "${PWD}:/workspace" gombocai/orl test . passes with zero failures@_name)indent flag used instead of hardcoded spaces in valuesnpx claudepluginhub gomboc-ai/gomboc-community-skills --plugin gomboc-communityBlocks Edit/Write/Bash actions until Claude investigates importers, data schemas, and user instructions. Improves output quality by forcing concrete facts before edits.