For AI agents: A markdown version of this page is available at https://docs.datadoghq.com/security/code_security/static_analysis/custom_rules.md. A documentation index is available at /llms.txt.
This product is not supported for your selected Datadog site. ().

Code Security is not available for the site.

Datadog Static Code Analysis lets you define static analysis rules as custom rules. You can share these custom rules within your organization.

Rule organization

SAST rules are organized within rulesets. A ruleset is a collection of rules. There are no constraints on how rules are organized within a ruleset. For example, some users might want to have rulesets for a specific language and others for a category.

A ruleset must have a unique name with only letters, numbers, and hyphens (-). Examples of valid ruleset names are python-security, cobra-team-checks, or my-company-security-checks.

Manage rules with the template repository

As an alternative to managing custom rules in Datadog, the datadog-custom-rules-template GitHub repository provides a Git-based workflow for managing custom rules as code. On every push to main, a GitHub Action automatically creates, updates, or deletes rulesets and rules to match what’s in the repository.

Get started

  1. Click Use this template on the datadog-custom-rules-template repository to create your own copy.
  2. Add your Datadog credentials as GitHub secrets. See Authentication.
  3. Rename rulesets/my-custom-rules/ or add new ruleset directories under rulesets/.
  4. Push to main. The GitHub Action uploads your rules automatically.

Authentication

  1. In your GitHub repository, go to Settings → Secrets and variables → Actions.
  2. Add three secrets:
    • DD_API_KEY — your Datadog API key
    • DD_APP_KEY — your Datadog Application key
    • DD_SITE — your Datadog site hostname (for example, datadoghq.com, datadoghq.eu, or us3.datadoghq.com)

How sync works

On every push to main, the GitHub Action runs upload.py, which:

  • Creates rulesets and rules that are new in the repository
  • Updates rulesets and rules whose content has changed
  • Deletes rulesets and rules that have been removed the repository

Only changed rules trigger API calls—unchanged rules are skipped.

To run a sync manually, in to your GitHub repository, go to the Actions tab, select Upload Custom Rules, and click Run workflow.

Rule file format

Each ruleset directory contains a ruleset.yaml and a separate .yaml file for each rule.

ruleset.yaml

name: my-org-custom-rules
short_description: One-line summary
description: Longer description of what this ruleset covers.

Rule file

name: your-rule-name
short_description: One-line summary
description: Detailed description.
category: BEST_PRACTICES     # SECURITY | BEST_PRACTICES | CODE_STYLE | ERROR_PRONE | PERFORMANCE
severity: ERROR              # ERROR | WARNING | NOTICE | INFO
language: JAVASCRIPT
tree_sitter_query: <TREE_SITTER_QUERY>    # See Anatomy of a custom rule
code: |-                                  # See Anatomy of a custom rule
  function visit(node, filename, code) { <RULE_LOGIC> }
tests: []
is_published: false          # Set to true when the rule is ready for scans

Anatomy of a custom rule

A custom rule is composed of three main components:

  • A tree-sitter query that captures what AST elements to check.
  • JavaScript code that process the AST elements reports violations.
  • Test code to test the rule.

Tree-sitter query

Custom rules use tree-sitter queries to query the code abstract syntax tree (AST) and retrieve elements to analyze. Elements of the AST are captured by the query using the @ operator.

All captured nodes from the tree-sitter query are injected in the JavaScript code and further processed to produce violations.

JavaScript code

The JavaScript code is defined in a visit function. This function is triggered at each match of the tree-sitter query. If a tree-sitter query captures a function call and the analyzed code contains 10 function calls, the visit function is called 10 times and each invocation has the capture of each occurrence.

The visit function has the signature visit(node, path, code):

  • node is the tree-sitter context being matched.
  • path is the path under analysis (convenient for filtering violation on path or filename).
  • code is the code under analysis.

To get a captured node, use the captures attribute of the first argument of the visit function. For example, the code below retrieves the functionName from a tree-sitter query. Each element contains the following attributes:

  • cstType: the tree-sitter type of the node.
  • start: start position of the node. The position contains line and col attributes.
  • end: end position of the node. The position contains line and col attributes.
  • text: the content of the node.
line and col attributes start at 1. Any result with line or col set to 0 is ignored.
function visit(node, filename, code) {
  const functionNameNode = node.captures["functionName"];
  console.log("cst type");
  console.log(functionNameNode.cstType);
  console.log("start line");
  console.log(functionNameNode.start.line);
}

The analyzer includes a few helper functions to help you write rules:

  • buildError(startLine, startCol, endLine, endCol, message, severity, category) builds an error.
    • severity is one of the following: ERROR, WARNING, NOTICE and INFO.
    • category is one of the following: BEST_PRACTICES, CODE_STYLE, ERROR_PRONE, PERFORMANCE and SECURITY.
  • addError(error) reports an error.
  • ddsa.getParent(node) returns the given node’s parent, or undefined if the node is the root node.
  • ddsa.getChildren(node) returns an array of the given node’s children, or an empty array if the node is a leaf node.

Rule examples

All Datadog default rules are available in Code Security. You can easily analyze and copy them to create your own custom rules.