# NVIDIA SkillSpector Behavioral AST Patterns (AST1-AST9): Security Detection Guide

> Explore NVIDIA SkillSpector's behavioral AST patterns (AST1-AST9) for robust security detection. Learn how it identifies risky Python code by analyzing abstract syntax trees.

- Repository: [NVIDIA Corporation/SkillSpector](https://github.com/NVIDIA/SkillSpector)
- Tags: deep-dive
- Published: 2026-06-25

---

**SkillSpector's behavioral AST analyzer identifies nine dangerous Python code patterns (AST1-AST9) by scanning abstract syntax trees for risky execution flows, dynamic imports, and reflective calls, with severity and confidence scoring defined in [`behavioral_ast.py`](https://github.com/NVIDIA/SkillSpector/blob/main/behavioral_ast.py).**

NVIDIA's SkillSpector is an open-source security analysis tool that detects high-risk code execution patterns in Python source files. The **behavioral AST analyzer**—implemented in [`src/skillspector/nodes/analyzers/behavioral_ast.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/behavioral_ast.py)—maps nine distinct detection rules to specific dangerous programming practices. Each pattern (AST1 through AST9) targets a specific vector for code injection, arbitrary execution, or reflective attacks.

## What Are Behavioral AST Patterns?

**Behavioral AST patterns** are static analysis rules that detect dangerous function calls and execution chains by inspecting Python's Abstract Syntax Tree (AST). Unlike simple string matching, these patterns analyze the semantic structure of code to identify `exec()` calls, dynamic imports, and reflective attribute access that could enable malicious behavior.

The analyzer assigns **severity levels** (CRITICAL, HIGH, MEDIUM, LOW) and **confidence scores** to each finding based on the `_RULE_SEVERITIES` and `_RULE_CONFIDENCES` dictionaries defined in the source. When SkillSpector scans a file, it generates a finding containing the rule ID, location, and specific message associated with the triggered pattern.

## The Nine Behavioral AST Patterns Explained

### AST1: Direct exec() Call Detection

**AST1** triggers when the analyzer detects a direct call to Python's built-in `exec()` function. This pattern identifies code that dynamically executes Python code strings.

```python

# Triggers AST1

exec("print('hello')")

```

### AST2: Direct eval() Call Detection

**AST2** identifies calls to the built-in `eval()` function, which evaluates arbitrary string expressions and poses significant code injection risks.

```python

# Triggers AST2

eval("2 + 2")

```

### AST3: Dynamic Import via __import__()

**AST3** detects runtime module imports using `__import__()` rather than standard import statements. This pattern catches attempts to load modules dynamically based on string variables.

```python

# Triggers AST3

mod = __import__("os")

```

### AST4: Subprocess Module Execution

**AST4** monitors calls to functions within the `subprocess` module that match the whitelist `_SUBPROCESS_CALLS` (including `subprocess.run`, `subprocess.call`, and similar execution functions).

```python

# Triggers AST4

import subprocess
subprocess.run(["ls", "-l"])

```

### AST5: OS System and Exec Family Calls

**AST5** detects calls to operating system execution functions defined in the `_OS_EXEC_CALLS` whitelist, including `os.system()`, `os.spawnv()`, and other process-spawning utilities in the `os` module.

```python

# Triggers AST5

import os
os.system("rm -rf /tmp/*")

```

### AST6: Direct compile() Call Detection

**AST6** identifies usage of the built-in `compile()` function, which can prepare code objects for later execution via `exec()` or `eval()`.

```python

# Triggers AST6

code = "x = 1"
compile(code, "<string>", "exec")

```

### AST7: Dynamic Attribute Access via getattr()

**AST7** triggers when `getattr()` is called with a **non-constant** attribute name (e.g., a variable). This pattern detects reflective attribute access that could bypass static analysis.

```python

# Triggers AST7

attr = "dynamic_name"
getattr(some_obj, attr)

```

### AST8: Dangerous Execution Chain

**AST8** detects nested dangerous calls where an `exec()` or `eval()` argument itself contains a dangerous source (such as a `subprocess` call). This pattern identifies multi-layered execution attacks.

```python

# Triggers AST8

exec("import subprocess; subprocess.call(['ls'])")

```

### AST9: Reflective Dangerous Call with Literal Sink

**AST9** identifies `getattr()` calls where the attribute name is a **constant literal** belonging to the dangerous set `{exec, eval, system, popen, __import__}`. Unlike AST7, this detects reflective access to specific sinks like `getattr(builtins, "exec")`.

```python

# Triggers AST9

getattr(builtins, "exec")("print('hi')")

```

## Implementation Architecture

The behavioral AST analyzer operates through a coordinated set of modules within the SkillSpector codebase:

- **[`src/skillspector/nodes/analyzers/behavioral_ast.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/behavioral_ast.py)**: Contains the core detection logic, rule definitions, and the `BehavioralASTAnalyzer` class that implements the visitor pattern for AST traversal.

- **[`src/skillspector/nodes/analyzers/common.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/common.py)**: Provides helper utilities for import alias resolution and source extraction used by the behavioral analyzer.

- **[`src/skillspector/models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py)**: Defines the `Finding` data models that store rule IDs, messages, severities, and locations for each detected pattern.

- **[`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py)**: Maintains the global analysis state and file cache consumed by the AST node during scanning.

## Summary

- **AST1-AST3** detect direct execution risks: `exec()`, `eval()`, and `__import__()` calls.
- **AST4-AST5** identify operating system command execution through `subprocess` and `os` module functions.
- **AST6** catches code object compilation via `compile()`.
- **AST7** and **AST9** distinguish between variable-based and literal-based reflective attribute access via `getattr()`.
- **AST8** specifically targets nested execution chains where dangerous functions wrap other dangerous calls.
- All nine patterns are defined in [`src/skillspector/nodes/analyzers/behavioral_ast.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/behavioral_ast.py) with configurable severities and confidence scores.

## Frequently Asked Questions

### What is the difference between AST7 and AST9?

**AST7** triggers when `getattr()` uses a variable or non-constant attribute name, while **AST9** triggers when `getattr()` uses a string literal that matches dangerous function names like `"exec"`, `"eval"`, `"system"`, `"popen"`, or `"__import__"`. AST7 detects dynamic reflection, whereas AST9 detects reflective access to specific execution sinks.

### How does SkillSpector determine the severity of an AST pattern finding?

The analyzer references the `_RULE_SEVERITIES` dictionary in [`behavioral_ast.py`](https://github.com/NVIDIA/SkillSpector/blob/main/behavioral_ast.py) to assign levels (CRITICAL, HIGH, MEDIUM, LOW) based on the specific AST pattern detected. Each rule ID maps to a severity level that reflects the potential security impact of the dangerous code construct.

### Can AST4 detect any subprocess function call?

No, **AST4** only detects calls to functions explicitly listed in the `_SUBPROCESS_CALLS` whitelist, which includes common execution functions like `subprocess.run()` and `subprocess.call()` but does not necessarily include every function in the subprocess module. The whitelist ensures the analyzer focuses on high-risk execution paths.

### What makes AST8 different from a standard AST1 detection?

While **AST1** simply detects any `exec()` call, **AST8** specifically identifies `exec()` or `eval()` calls where the argument string contains another dangerous source—such as a `subprocess` invocation—creating a compound execution chain. This pattern catches sophisticated attacks where code execution wraps additional system commands.