# How AST-Based Behavioral Analysis Detects Dangerous Code in SkillSpector

> Discover how SkillSpector's AST-based behavioral analysis uncovers dangerous code by identifying risky Python function calls and complex execution patterns. Protect your projects today.

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

---

**AST-based behavioral analysis in SkillSpector detects dangerous code by parsing Python files into abstract syntax trees and walking them to identify risky function calls like `exec()`, `eval()`, and `subprocess` operations, including chained execution patterns that wrap multiple dangerous operations.**

SkillSpector, NVIDIA's open-source security analysis tool, uses AST-based behavioral analysis to automatically identify potentially dangerous execution patterns in Python source code. This static analysis approach inspects the abstract syntax tree of each file to catch risky behaviors such as dynamic code execution, shell commands, and malicious import chains before they reach production.

## The Core Analysis Pipeline

The behavioral analysis engine lives in [[`behavioral_ast.py`](https://github.com/NVIDIA/SkillSpector/blob/main/behavioral_ast.py)](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/behavioral_ast.py) and follows a three-stage pipeline to inspect source files.

### Parsing Python Source Files

The analyzer first filters files by size, respecting the `MAX_FILE_BYTES` limit defined in [[`static_runner.py`](https://github.com/NVIDIA/SkillSpector/blob/main/static_runner.py)](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/static_runner.py). For qualifying files, it calls `ast.parse()` to convert raw Python source into an abstract syntax tree. This parsing step captures the complete structure of the code while discarding formatting details like comments and whitespace.

### Walking the Abstract Syntax Tree

Once parsed, the analyzer uses `ast.walk(tree)` to iterate over every node in the AST. The walker specifically targets `ast.Call` nodes representing function invocations. For each call encountered, the analyzer extracts the fully-qualified name using `resolve_call_name()` from [[`common.py`](https://github.com/NVIDIA/SkillSpector/blob/main/common.py)](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/common.py), handling both plain names and attribute chains like `os.system` or `subprocess.Popen`.

## Detecting Dangerous Call Patterns

SkillSpector matches extracted call names against several rule sets to identify eight distinct categories of dangerous behavior. Each detection generates an `AnalyzerFinding` with specific severity and confidence levels defined in `_RULE_SEVERITIES` and `_RULE_CONFIDENCES`.

The detection rules include:

- **AST1**: Direct `exec()` calls found in `_DANGEROUS_BUILTINS`
- **AST2**: Direct `eval()` calls found in `_DANGEROUS_BUILTINS`
- **AST3**: Dynamic imports via `__import__()` found in `_DANGEROUS_BUILTINS`
- **AST4**: Subprocess invocations like `subprocess.run` or `subprocess.Popen` found in `_SUBPROCESS_CALLS`
- **AST5**: OS execution family calls like `os.system` found in `_OS_EXEC_CALLS`
- **AST6**: Use of `compile()` to create executable code objects found in `_DANGEROUS_BUILTINS`
- **AST7**: Dynamic attribute access using `getattr()` where the attribute argument is not a constant
- **AST8**: Chained dangerous calls detected by `_contains_dangerous_source`

## Identifying Dangerous Execution Chains

The **AST8** rule specifically targets complex attack vectors where dangerous calls wrap secondary risky sources. When the analyzer detects `exec()`, `eval()`, or `compile()` calls, it examines the first argument (`ast_node.args[0]`) using the `_contains_dangerous_source()` helper.

If this argument contains calls to subprocess modules, OS execution functions, `__import__`, or obfuscated loaders (such as base64/urllib patterns), SkillSpector flags the pattern as a dangerous execution chain. This catches sophisticated evasion techniques like `exec(eval("os.system('whoami')"))` that might bypass simpler signature-based detection.

## Name Resolution and Context Extraction

The [[`common.py`](https://github.com/NVIDIA/SkillSpector/blob/main/common.py)](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/common.py) module provides utilities for accurate call identification. The `resolve_call_name()` function constructs dotted names from AST nodes, while `_build_import_aliases()` and `build_type_map()` enable future extensions to resolve import aliases (e.g., `import subprocess as sp; sp.run(...)`).

Each finding includes location metadata (file path, start/end lines) and a source snippet extracted via `get_source_segment()` to provide developers with immediate context for remediation.

## Practical Examples

### Direct Dangerous Calls

```python

# vulnerable.py

exec(user_input)          # flagged as AST1 (high severity)

```

Running SkillSpector on this file produces a finding with the message "exec() call detected".

### Subprocess Execution

```python
import subprocess

subprocess.run("rm -rf /", shell=True)   # flagged as AST4 (medium severity)

```

### Dangerous Execution Chains

```python
import os

exec(eval("os.system('whoami')"))   # flagged as AST8 (critical severity)

```

The outer `exec` triggers AST8 because `_contains_dangerous_source` discovers the nested `os.system` call.

### Dynamic Attribute Access

```python
obj = SomeClass()
method_name = get_user_input()
getattr(obj, method_name)()   # flagged as AST7 (low severity)

```

## Summary

AST-based behavioral analysis in SkillSpector provides lightweight but powerful static security scanning for Python codebases. Key implementation details include:

- **File parsing** using `ast.parse()` with size limits enforced by `MAX_FILE_BYTES`
- **Tree walking** via `ast.walk()` targeting `ast.Call` nodes specifically
- **Eight detection rules** (AST1-AST8) covering builtins, subprocess, OS execution, and dynamic attribute access
- **Chain detection** through `_contains_dangerous_source()` for nested dangerous calls
- **Rich findings** converted via `analyzer_finding_to_finding()` in [`static_runner.py`](https://github.com/NVIDIA/SkillSpector/blob/main/static_runner.py) to include severity, confidence, and source context

## Frequently Asked Questions

### What is AST-based behavioral analysis in SkillSpector?

AST-based behavioral analysis is a static security scanning technique that parses Python source code into an abstract syntax tree and inspects function call patterns. As implemented in NVIDIA/SkillSpector, it walks the AST to detect dangerous operations like `exec()`, `eval()`, and `subprocess` calls without executing the code.

### How does SkillSpector detect dangerous execution chains?

SkillSpector detects dangerous execution chains through the AST8 rule, which triggers when `exec()`, `eval()`, or `compile()` wrap other risky operations. The analyzer examines the first argument of these calls using `_contains_dangerous_source()` to identify nested subprocess calls, OS execution functions, or dynamic imports.

### What types of dangerous calls does the AST analyzer identify?

The analyzer identifies calls in `_DANGEROUS_BUILTINS` (like `exec` and `eval`), `_SUBPROCESS_CALLS` (like `subprocess.run`), `_OS_EXEC_CALLS` (like `os.system`), and dynamic `getattr()` usage. Each category maps to specific AST rule IDs ranging from AST1 to AST8 with assigned severity levels.

### How are findings converted to the final output format?

Raw `AnalyzerFinding` objects created in [`behavioral_ast.py`](https://github.com/NVIDIA/SkillSpector/blob/main/behavioral_ast.py) are converted to generic `Finding` objects via `analyzer_finding_to_finding()` defined in [`static_runner.py`](https://github.com/NVIDIA/SkillSpector/blob/main/static_runner.py). This transformation standardizes the output format with location data, severity scores, and source snippets for integration into SkillSpector's analysis pipeline.