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 valuesProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.
npx claudepluginhub gomboc-ai/gomboc-community-skills --plugin gomboc-community