Skip to content

How to Write Templates

forefy edited this page Feb 21, 2026 · 9 revisions

radar Templates

Table of Contents

Template example

version: 0.1.0
author: forefy
accent: anchor
name: Issue name here
description: Issue technical description here 
severity: Medium (filter templates for example via --ignore low,medium)
certainty: Medium (filter templates for example via --ignore uncertain)
vulnerable_example: https://github.com/Auditware/radar/blob/main/api/tests/mocks/arbitrary_cpi/bad/src/lib.rs#L11-L14 (Each template is shipped with a god/ and bad/ minimal compilable contracts, and is tested to work as expected)

rule: |
  for source, nodes in ast:
      try:
          # Your python logic here, for example:
          funcs = nodes.find_all_functions().exit_on_none()
          invoke_refs = funcs.find_by_names("invoke")
          if len(invoke_refs) == 0:
              continue
          spl_token_checks = funcs.find_by_names("spl_token", "ID")
          spl_token_2022_checks = funcs.find_by_names("spl_token_2022", "ID")
          if len(spl_token_checks) >= 2 or len(spl_token_2022_checks) >= 2:
              continue
          for invoke_ref in invoke_refs:
              print(invoke_ref.parent.to_result())
      except:
          continue

A template consists of descriptive fields, and a logical rule.

The descriptive fields represent the information associated with the vulnerability and the template itself.

The rule is python based statements through which we iterate on the AST (Abstract Syntax Tree) of the contract and extract insightful information.


Understanding Rules

Accessing AST Data

At a first glance, we can see that we have the ast variable magically available to us, and that we can iterate on it in source and nodes pairs.

source is the file path of the current iteration

An example source looks like

/contract/programs/program_name/insecure/src/lib.rs

nodes are the nodes of that specific file path

Example nodes looks like

{"mod": {"attrs": [...], "vis": "pub", "ident": "node_name", "content": [...], "src": {...}}}

Pythonic abstractions

We have setup ease-of-use rule functions, and there are operations that can be done on each.

These functions live in a single file in the repo dsl_ast_iterator.py, and to deep dive and understand the different methods available that's the place to be.

Rule Functions Documentation

We can learn from the template at the top of this page how example calls are made on the AST. Take this line for example:

cpi_groups = nodes.find_chained_calls("solana_program", "program", "invoke").exit_on_none()

find_chained_calls is a method implemented by the ASTNode iterator, and to understand how to use it we can look up the function name in the dsl_ast_iterator.py file.

def find_chained_calls(self, *idents: tuple[str, ...]) -> ASTNodeListGroup:
    ...

By the signature alone, we can understand that the function receives a dynamic number of string arguments, and returns a group of AST node lists.

AST Iterator classes

In a high level, there are three important classes: ASTNode, ASTNodeList and ASTNodeListGroup, all give us an abstraction to iterate over the rust JSON AST radar generates.

ASTNode represent an AST node, and the layers above it (List, ListGroup) can be thought of similarly - the functions implemented on ASTNode can be called in the Node, List, or ListGroup level accordingly.

In the code snippet above we iterate on an ASTNodeList, retreiving occurrences of solana_program::program::invoke(..).

That returns us List Groups (i.e. list of ast node lists) of the nodes involved in those occurrences, including further relevant data like line positioning, metadata, child nodes, parent nodes etc.

We can then use this info and pass it to more methods, filtering results further, or print nodes based on conditions we choose.

Indicating results / vulnerable code segments

When we want to indicate a result, we just print the vulnerable node found (or the node whose line information we want to include in the raised vulnerability/insight):

for cpi_group in cpi_groups:
    print(cpi_group.first().parent.to_result())

In that example we printed the first CPI's parent from each node list group, using the .to_result() to ensure it's picked up as a vulnerability item.


Write your first rule

For an easy learning curve, we've setup demo.ipynb in which you can start playing with a simulated rule and see results, no docker setup needed!

Clone this wiki locally