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

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.

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—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.


# 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.


# 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.


# 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).


# 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.


# 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().


# 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.


# 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.


# 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").


# Triggers AST9

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

Implementation Architecture

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

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 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →