# How SkillSpector Uses Abstract Syntax Trees (AST) for Python Security Analysis

> Discover how SkillSpector leverages Python Abstract Syntax Trees AST for robust security analysis. Detect dangerous calls and track credential flows with precision.

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

---

**SkillSpector parses Python source files into Abstract Syntax Trees using `ast.parse()`, then walks these trees to detect dangerous built-in calls and perform source-to-sink taint tracking on credential flows.**

NVIDIA's SkillSpector is a security analysis framework that inspects Python code for potentially dangerous patterns. By utilizing **Abstract Syntax Trees (AST)** rather than simple text matching, it achieves precise, context-aware detection of risky behaviors like dynamic code execution and credential leakage. The framework traverses AST nodes to identify specific call patterns, track variable assignments, and analyze data flows within the exact syntactic structure of the code.

## The Core AST Pipeline in SkillSpector

### Parsing and Caching Strategy

Each analysis begins when SkillSpector discovers Python files and converts them into AST representations. In [`skillspector/nodes/build_context.py`](https://github.com/NVIDIA/SkillSpector/blob/main/skillspector/nodes/build_context.py), the framework parses each file once using `ast.parse(content, filename)` and caches the resulting tree in `state["ast_cache"]`. This caching strategy prevents redundant parsing during multi-pass analysis, ensuring efficient performance when multiple analyzers inspect the same codebase.

### Walking the AST for Pattern Detection

Once parsed, analyzers traverse the tree using `ast.walk(tree)`, which yields every node in the source file. The framework inspects `ast.Call` nodes to identify function invocations, `ast.Assign` nodes to track variable bindings, and other node types depending on the specific security rule. This structured traversal allows SkillSpector to understand the exact syntactic context of each operation, distinguishing between actual function calls and string occurrences.

## Behavioral AST Analysis: Detecting Dangerous Built-ins

The `behavioral_ast` analyzer, located in [`skillspector/nodes/analyzers/behavioral_ast.py`](https://github.com/NVIDIA/SkillSpector/blob/main/skillspector/nodes/analyzers/behavioral_ast.py), scans for high-risk Python built-ins like `exec`, `eval`, `compile`, and `__import__`. It also detects dangerous subprocess patterns and OS execution functions.

The analyzer uses the helper `resolve_call_name` from [`skillspector/nodes/analyzers/common.py`](https://github.com/NVIDIA/SkillSpector/blob/main/skillspector/nodes/analyzers/common.py) to extract fully-qualified function names from call nodes. When it encounters a risky call, it invokes `_contains_dangerous_source` to recursively examine the call's arguments, detecting dangerous chains where one risky call wraps another (e.g., `exec(compile(...))`).

```python
import ast
from skillspector.nodes.analyzers.common import resolve_call_name

def find_dangerous_calls(source: str, file_path: str):
    tree = ast.parse(source, filename=file_path)
    findings = []
    for node in ast.walk(tree):
        if isinstance(node, ast.Call):
            name = resolve_call_name(node)
            if name in {"exec", "eval", "compile", "__import__"}:
                findings.append((name, node.lineno))
    return findings

```

## Taint Tracking Analysis: Following Credential Flows

The `behavioral_taint_tracking` analyzer performs data-flow analysis to track sensitive values from their origins (sources) to dangerous destinations (sinks). Located in [`skillspector/nodes/analyzers/behavioral_taint_tracking.py`](https://github.com/NVIDIA/SkillSpector/blob/main/skillspector/nodes/analyzers/behavioral_taint_tracking.py), this analyzer monitors assignments from sources like `os.getenv` or file reads, marks variables as "tainted," and checks if they reach sinks such as `subprocess.run` or `requests.post`.

The analyzer builds a **type map** using `build_type_map` to resolve attribute lookups and dotted names. When it finds an assignment from a sensitive source, it marks the target variables via `_mark_targets`. For each call node, it checks if the function name matches known sinks in `_ALL_SINKS`, and if any tainted variables appear in the arguments, it reports a finding showing the complete source-to-sink flow.

```python
import ast
from skillspector.nodes.analyzers.common import resolve_call_name_typed, build_type_map

SINKS = {"subprocess.run", "os.system"}

def taint_flow(source: str, file_path: str):
    tree = ast.parse(source, filename=file_path)
    type_map = build_type_map(tree)
    tainted = set()
    findings = []

    for node in ast.walk(tree):
        # Mark assignments from secret sources

        if isinstance(node, ast.Assign):
            if isinstance(node.value, ast.Call) and resolve_call_name_typed(node.value, type_map) == "os.getenv":
                for target in node.targets:
                    if isinstance(target, ast.Name):
                        tainted.add(target.id)

        # Detect sinks receiving tainted data

        if isinstance(node, ast.Call):
            call_name = resolve_call_name_typed(node, type_map)
            if call_name in SINKS:
                for arg in node.args:
                    if isinstance(arg, ast.Name) and arg.id in tainted:
                        findings.append((call_name, node.lineno, arg.id))
    return findings

```

## Shared Utilities and AST Helpers

Both analyzers rely on common utilities defined in [`skillspector/nodes/analyzers/common.py`](https://github.com/NVIDIA/SkillSpector/blob/main/skillspector/nodes/analyzers/common.py). The `resolve_call_name` function extracts the fully-qualified name of a function call from an `ast.Call` node, handling simple names, attributes, and complex expressions. The `get_context_from_lines` helper provides source context for findings. These shared utilities ensure consistent AST interpretation across different analysis modules.

## Summary

- SkillSpector uses **AST parsing** via `ast.parse()` to create structured representations of Python source code, cached in `state["ast_cache"]` to optimize performance.
- The **behavioral_ast** analyzer detects dangerous built-ins and execution chains by walking the AST and inspecting `ast.Call` nodes with `resolve_call_name`.
- The **behavioral_taint_tracking** analyzer performs data-flow analysis by tracking tainted variables from sources like `os.getenv` to dangerous sinks using type maps and assignment tracking.
- Shared utilities in [`common.py`](https://github.com/NVIDIA/SkillSpector/blob/main/common.py) provide standardized AST node resolution and context extraction across all analyzers.

## Frequently Asked Questions

### Why does SkillSpector use AST instead of regex for security analysis?

Abstract Syntax Trees provide structured, semantic understanding of code whereas regex only matches text patterns. AST analysis allows SkillSpector to distinguish between actual function calls and string literals containing the same text, accurately resolve imported names, and track data flow through variable assignments. This precision eliminates false positives that plague pattern-based scanning.

### How does SkillSpector handle multiple analyses on the same file efficiently?

The framework caches parsed ASTs in `state["ast_cache"]` during the build context phase ([`build_context.py`](https://github.com/NVIDIA/SkillSpector/blob/main/build_context.py)). When multiple analyzers need to inspect the same Python file, they retrieve the pre-parsed tree from the cache rather than invoking `ast.parse()` repeatedly. This optimization significantly reduces overhead when running both behavioral_ast and behavioral_taint_tracking on the same codebase.

### What Python constructs can the taint tracker analyze?

The taint tracker handles variable assignments from function calls (like `os.getenv`), attribute lookups via type maps, and tracks these values through to dangerous sinks. It recognizes `ast.Name` nodes for simple variables and uses `build_type_map` to resolve dotted names like `subprocess.run`. However, it focuses on direct data flows and may not track complex transformations or interprocedural flows across function boundaries.

### Where are the AST analyzers located in the SkillSpector repository?

The core AST analyzers reside in [`skillspector/nodes/analyzers/behavioral_ast.py`](https://github.com/NVIDIA/SkillSpector/blob/main/skillspector/nodes/analyzers/behavioral_ast.py) for dangerous call detection and [`skillspector/nodes/analyzers/behavioral_taint_tracking.py`](https://github.com/NVIDIA/SkillSpector/blob/main/skillspector/nodes/analyzers/behavioral_taint_tracking.py) for data-flow analysis. Shared helper functions are in [`skillspector/nodes/analyzers/common.py`](https://github.com/NVIDIA/SkillSpector/blob/main/skillspector/nodes/analyzers/common.py), while AST caching logic is implemented in [`skillspector/nodes/build_context.py`](https://github.com/NVIDIA/SkillSpector/blob/main/skillspector/nodes/build_context.py).