Rule Format
sol-azy allows developers and auditors to write custom static analysis rules using the Starlark language — a Python-like configuration language used by projects like Bazel and Buck/Buck2 (Buck2 docs).
These rules are evaluated against the Rust AST (Abstract Syntax Tree) of a Solana program, enabling precise pattern matching to detect vulnerabilities or code smells.
Rule File Structure
A valid rule file is a .star script containing two main parts:
RULE_METADATA— a dictionary with basic infosyn_ast_rule(root)— the entrypoint function, called on each parsed file
RULE_METADATA = {
"version": "0.1.0",
"author": "your-name",
"name": "Rule Name",
"severity": "Low" | "Medium" | "High" | "Critical",
"certainty": "Low" | "Medium" | "High",
"description": "What the rule checks for"
}
Example Rule: Arbitrary CPI
RULE_METADATA = {
"version": "0.1.0",
"author": "forefy",
"name": "Arbitrary Cross-Program Invocation",
"severity": "Medium",
"certainty": "Medium",
"description": "Detects CPIs made to arbitrary or unchecked program IDs."
}
def syn_ast_rule(root: dict) -> list[dict]:
matches = []
raw_nodes = syn_ast.find_raw_nodes(root)
for sink in raw_nodes:
if template_manager.is_matching_template_by_key(sink, "CALL_FN_SOLANAPROGRAM_PROGRAM_INVOKE") and not template_manager.is_matching_template_by_key(sink, "CHECK_SPLTOKEN_ID_CTX_ACCOUNT_AUTHORITY_KEY"):
matches.append(syn_ast.to_result(sink))
return matches
Execution Flow
When sol-azy runs a rule:
- It parses the source code into an AST
- Converts it to JSON
- Passes it as the
rootparameter tosyn_ast_rule - The rule inspects the tree using helper functions (typically, here the
syn_ast.find_raw_nodes()here is used to gather each function independently) - Any result added to
matchesis reported
Helper Libraries
sol-azy ships with built-in sol-azy helpers (written in Starlark):
src/static/starlark_libs/
├── syn_ast.star # AST navigation utilities
└── template_manager.star # Match against common templates
These can be imported and used in any rule. Examples:
raw_nodes = syn_ast.find_raw_nodes(root)
template_manager.is_matching_template_by_key(node, "CHECK_INSTRUCTION_DISCRIMINATOR")
📌 Note: The
template_managerlogic enables reusable pattern detection (documented in Templates).
Writing New Rules
To create a new rule:
- Create a
.starfile in your rules directory - Define
RULE_METADATAandsyn_ast_rule(...) - Use
cargo run -- sast ...to apply the rule
Documentation
- Starlark language reference (GitHub)
- Starlark spec & built-ins
- Starlark in Bazel (docs)
- Starkark in Buck2 (docs)