Security oriented static analyser for python code.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
AST node analysis and import tracking during security test execution. The Context system provides detailed information about function calls, imports, and code patterns essential for accurate vulnerability detection in Python source code.
Provides contextual information during AST node analysis for security tests. Context objects give access to function call details, string literals, import patterns, and other code analysis data.
class Context:
def __init__(self, context_object=None):
"""
Initialize context with optional context object.
Parameters:
- context_object: optional context data for initialization
"""
@property
def call_function_name(self):
"""
Get function name being called (not fully qualified).
Returns:
str: Function name or None if not a function call
"""
@property
def call_function_name_qual(self):
"""
Get fully qualified function name including module path.
Returns:
str: Qualified function name (e.g., 'os.system') or None
"""
@property
def call_args(self):
"""
Get list of function call arguments.
Returns:
list: Function call arguments as AST nodes
"""
@property
def call_args_count(self):
"""
Get number of function call arguments.
Returns:
int: Number of arguments in function call
"""
@property
def call_keywords(self):
"""
Get dictionary of keyword parameters.
Returns:
dict: Keyword arguments mapped to values
"""
@property
def string_literal_value(self):
"""
Get value of string literal node.
Returns:
str: String literal value or None if not a string literal
"""
@property
def node(self):
"""
Get the current AST node being analyzed.
Returns:
ast.Node: Current AST node
"""
@property
def string_val(self):
"""
Get string literal value (alternative to string_literal_value).
Returns:
str: String value or None
"""
@property
def bytes_val(self):
"""
Get bytes literal value.
Returns:
bytes: Bytes value or None
"""
def is_module_being_imported(self, module):
"""
Check if specified module is being imported.
Parameters:
- module: str, module name to check
Returns:
bool: True if module is being imported
"""
def is_module_imported_like(self, module):
"""
Check if module is imported with pattern matching.
Parameters:
- module: str, module pattern to match
Returns:
bool: True if module matches import pattern
"""
def is_module_imported_exact(self, module):
"""
Check if exact module name is imported.
Parameters:
- module: str, exact module name
Returns:
bool: True if exact module is imported
"""
def check_call_arg_value(self, argument_name, argument_values):
"""
Check if function call argument matches specific values.
Parameters:
- argument_name: str, name of argument to check
- argument_values: list, acceptable values for the argument
Returns:
bool: True if argument matches any of the values
"""
def get_lineno_for_call_arg(self, argument_name):
"""
Get line number for specific function call argument.
Parameters:
- argument_name: str, name of argument
Returns:
int: Line number or None if not found
"""from bandit.core import test_properties as test
import bandit
@test.checks('Call')
@test.test_id('B999')
def check_dangerous_calls(context):
"""Example security test using context analysis."""
# Check function name
if context.call_function_name == 'eval':
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.HIGH,
text="Use of eval() detected - potential code injection",
cwe=94
)
# Check qualified function name
if context.call_function_name_qual == 'os.system':
# Analyze arguments
if context.call_args_count > 0:
first_arg = context.call_args[0]
if hasattr(first_arg, 's'): # String literal
command = first_arg.s
if any(char in command for char in ['&', '|', ';']):
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.MEDIUM,
text="Shell command with potential injection vectors",
cwe=78
)@test.checks('Import', 'ImportFrom')
@test.test_id('B900')
def check_dangerous_imports(context):
"""Check for dangerous module imports."""
# Check for specific dangerous imports
dangerous_modules = ['pickle', 'cPickle', 'dill', 'shelve']
for module in dangerous_modules:
if context.is_module_being_imported(module):
return bandit.Issue(
severity=bandit.MEDIUM,
confidence=bandit.HIGH,
text=f"Import of {module} module detected - potential deserialization risk",
cwe=502
)
# Check for wildcard imports from security-sensitive modules
if context.is_module_imported_like('subprocess.*'):
return bandit.Issue(
severity=bandit.LOW,
confidence=bandit.MEDIUM,
text="Wildcard import from subprocess module",
cwe=20
)@test.checks('Str')
@test.test_id('B901')
def check_hardcoded_secrets(context):
"""Check for hardcoded secrets in string literals."""
if context.string_literal_value:
value = context.string_literal_value.lower()
# Check for common secret patterns
secret_patterns = [
'password=',
'secret_key=',
'api_key=',
'access_token=',
'private_key='
]
for pattern in secret_patterns:
if pattern in value and len(value) > 20:
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.MEDIUM,
text="Potential hardcoded secret detected",
cwe=259
)@test.checks('Call')
@test.test_id('B902')
def check_ssl_context(context):
"""Check SSL context configuration."""
if context.call_function_name_qual in ['ssl.create_default_context', 'ssl.SSLContext']:
keywords = context.call_keywords
# Check for disabled certificate verification
if 'check_hostname' in keywords:
if hasattr(keywords['check_hostname'], 'value') and not keywords['check_hostname'].value:
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.HIGH,
text="SSL hostname verification disabled",
cwe=295
)
# Check for weak SSL versions
if 'protocol' in keywords:
protocol_node = keywords['protocol']
if hasattr(protocol_node, 'attr') and 'SSL' in protocol_node.attr:
if any(weak in protocol_node.attr for weak in ['SSLv2', 'SSLv3', 'TLSv1']):
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.HIGH,
text="Weak SSL/TLS protocol version",
cwe=326
)@test.checks('Call')
@test.test_id('B903')
def check_subprocess_shell(context):
"""Advanced subprocess call analysis."""
if context.call_function_name_qual in ['subprocess.call', 'subprocess.run', 'subprocess.Popen']:
keywords = context.call_keywords
args = context.call_args
# Check if shell=True is used
shell_enabled = False
if 'shell' in keywords:
shell_node = keywords['shell']
if hasattr(shell_node, 'value') and shell_node.value:
shell_enabled = True
if shell_enabled and args:
# Analyze first argument for user input patterns
first_arg = args[0]
if hasattr(first_arg, 's'): # String literal
command = first_arg.s
# Check for common injection patterns
if any(pattern in command for pattern in ['%s', '{}', '+', 'format(']):
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.MEDIUM,
text="subprocess call with shell=True and potential injection",
cwe=78
)Install with Tessl CLI
npx tessl i tessl/pypi-bandit