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
root
parameter 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
matches
is 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_manager
logic enables reusable pattern detection (documented in Templates).
Writing New Rules
To create a new rule:
- Create a
.star
file in your rules directory - Define
RULE_METADATA
andsyn_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)