Initial commit

This commit is contained in:
Alexander Braml
2026-04-08 14:48:24 +02:00
commit 16838618a3
24 changed files with 1481 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
"""Security Demo Package."""
__version__ = "0.1.0"

View File

@@ -0,0 +1,109 @@
"""Cryptography utilities - streamlined version.
FINDING CLASSIFICATIONS:
- TRUE POSITIVE (TP): Actual security vulnerability
- FALSE POSITIVE (FP): Flagged but not a real issue in context
- UNCERTAIN: Could be either depending on deployment context
"""
import os
import random
import secrets
import ssl
import string
from typing import Tuple
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
# =============================================================================
# HARDCODED SECRET PATTERNS
# =============================================================================
# TP: Hardcoded production key
PRODUCTION_KEY = b"aK9$mX2#pL7@nQ4&wE8*rT5%yU1!oI3^"
# FP: Example/placeholder key clearly marked
EXAMPLE_KEY = "REPLACE_THIS_KEY_IN_PRODUCTION"
# FP: Test key with test prefix
TEST_API_KEY = "test_sk_4eC39HqLyjWDarjtT1zdp7dc"
# UNCERTAIN: Looks real but might be intentionally fake
BACKUP_KEY = "bkp_2024_xK9mP2sL7nQ4wE8rT5yU1oI3aB6cD"
# =============================================================================
# RANDOM NUMBER PATTERNS
# =============================================================================
def generate_session_token_insecure() -> str:
"""TP: Using random for session token."""
chars = string.ascii_letters + string.digits
return "".join(random.choice(chars) for _ in range(32))
def generate_otp_insecure() -> str:
"""TP: Using random for OTP."""
return str(random.randint(100000, 999999))
def generate_session_token_secure() -> str:
"""FP: Using secrets for session token."""
return secrets.token_urlsafe(32)
def shuffle_playlist(songs: list) -> list:
"""FP: random is fine for non-security shuffling."""
result = songs.copy()
random.shuffle(result)
return result
def roll_dice() -> int:
"""FP: random for game mechanics."""
return random.randint(1, 6)
# =============================================================================
# CIPHER MODE PATTERNS
# =============================================================================
def encrypt_ecb(key: bytes, data: bytes) -> bytes:
"""TP: ECB mode reveals patterns."""
cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend())
encryptor = cipher.encryptor()
return encryptor.update(data) + encryptor.finalize()
def encrypt_cbc_random_iv(key: bytes, data: bytes) -> Tuple[bytes, bytes]:
"""FP: CBC with random IV is secure."""
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
encryptor = cipher.encryptor()
return iv, encryptor.update(data) + encryptor.finalize()
# =============================================================================
# SSL/TLS PATTERNS
# =============================================================================
def create_insecure_context() -> ssl.SSLContext:
"""TP: Certificate verification disabled."""
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
return context
def create_secure_context() -> ssl.SSLContext:
"""FP: Properly configured secure context."""
context = ssl.create_default_context()
context.check_hostname = True
context.verify_mode = ssl.CERT_REQUIRED
return context

View File

@@ -0,0 +1,92 @@
"""Database module - streamlined version.
FINDING CLASSIFICATIONS:
- TRUE POSITIVE (TP): Actual security vulnerability
- FALSE POSITIVE (FP): Flagged but not a real issue in context
- UNCERTAIN: Could be either depending on deployment context
"""
import hashlib
import hmac
import secrets
from typing import Any, List, Optional
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
class DatabaseManager:
"""Database operations with SQL patterns."""
def __init__(self, db_url: str = "sqlite:///app.db"):
self.engine = create_engine(db_url)
self.Session = sessionmaker(bind=self.engine)
# =========================================================================
# SQL INJECTION PATTERNS
# =========================================================================
def find_by_username_unsafe(self, username: str) -> Optional[dict]:
"""TP: SQL injection via string formatting."""
session = self.Session()
query = f"SELECT * FROM users WHERE username = '{username}'"
result = session.execute(text(query))
return result.fetchone()
def search_users_unsafe(self, search_term: str) -> List[dict]:
"""TP: SQL injection in LIKE clause."""
session = self.Session()
query = f"SELECT * FROM users WHERE username LIKE '%{search_term}%'"
result = session.execute(text(query))
return result.fetchall()
def find_by_id_safe(self, user_id: int) -> Optional[dict]:
"""FP: Parameterized query is safe."""
session = self.Session()
result = session.execute(
text("SELECT * FROM users WHERE id = :id"), {"id": user_id}
)
return result.fetchone()
def dynamic_column_sort(self, column: str, order: str = "ASC") -> List[dict]:
"""UNCERTAIN: Column name from allowlist but still uses f-string."""
allowed_columns = ["username", "email", "created_at"]
if column not in allowed_columns:
raise ValueError("Invalid column")
session = self.Session()
query = f"SELECT * FROM users ORDER BY {column} {order}"
result = session.execute(text(query))
return result.fetchall()
class PasswordManager:
"""Password hashing patterns."""
def hash_password_md5(self, password: str) -> str:
"""TP: MD5 is cryptographically broken for passwords."""
return hashlib.md5(password.encode()).hexdigest()
def hash_password_sha1(self, password: str) -> str:
"""TP: SHA1 is weak for password hashing."""
return hashlib.sha1(password.encode()).hexdigest()
def compute_file_checksum_md5(self, filepath: str) -> str:
"""FP: MD5 acceptable for file integrity (non-security)."""
hasher = hashlib.md5(usedforsecurity=False)
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hasher.update(chunk)
return hasher.hexdigest()
def verify_signature_sha256(
self, message: bytes, signature: str, key: bytes
) -> bool:
"""FP: HMAC-SHA256 for signatures is secure."""
expected = hmac.new(key, message, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
def hash_password_pbkdf2(self, password: str) -> tuple:
"""FP: PBKDF2 is a proper password hash."""
salt = secrets.token_bytes(32)
key = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, 600000)
return key.hex(), salt.hex()

View File

@@ -0,0 +1,77 @@
"""Network client module - streamlined version.
FINDING CLASSIFICATIONS:
- TRUE POSITIVE (TP): Actual security vulnerability
- FALSE POSITIVE (FP): Flagged but not a real issue in context
- UNCERTAIN: Could be either depending on deployment context
"""
import ssl
import urllib.request
from typing import Dict
from urllib.parse import urljoin, urlparse
import requests
class APIClient:
"""HTTP API client."""
def __init__(self, base_url: str):
self.base_url = base_url
def get_insecure(self, endpoint: str) -> Dict:
"""TP: SSL verification disabled."""
url = urljoin(self.base_url, endpoint)
response = requests.get(url, verify=False, timeout=30)
return response.json()
def get_secure(self, endpoint: str) -> Dict:
"""FP: Default SSL verification."""
url = urljoin(self.base_url, endpoint)
response = requests.get(url, verify=True, timeout=30)
return response.json()
def get_no_timeout(self, endpoint: str) -> Dict:
"""TP: No timeout specified."""
url = urljoin(self.base_url, endpoint)
response = requests.get(url) # No timeout!
return response.json()
def get_with_timeout(self, endpoint: str) -> Dict:
"""FP: Proper timeout specified."""
url = urljoin(self.base_url, endpoint)
response = requests.get(url, timeout=30)
return response.json()
class URLFetcher:
"""Fetch URLs."""
def fetch_any_url(self, url: str) -> bytes:
"""TP: Arbitrary URL fetch (SSRF potential)."""
with urllib.request.urlopen(url) as response:
return response.read()
def fetch_https_only(self, url: str) -> bytes:
"""FP: Only HTTPS URLs allowed."""
parsed = urlparse(url)
if parsed.scheme != "https":
raise ValueError("Only HTTPS URLs allowed")
with urllib.request.urlopen(url, timeout=30) as response:
return response.read()
def fetch_allowlisted(self, url: str) -> bytes:
"""FP: Domain allowlist."""
allowed = ["api.example.com", "cdn.example.com"]
parsed = urlparse(url)
if parsed.netloc not in allowed:
raise ValueError("Domain not allowed")
with urllib.request.urlopen(url, timeout=30) as response:
return response.read()
def fetch_unverified_ssl(self, url: str) -> bytes:
"""TP: Unverified SSL context."""
context = ssl._create_unverified_context()
with urllib.request.urlopen(url, context=context) as response:
return response.read()

View File

@@ -0,0 +1,20 @@
"""Production secrets - THIS FILE SHOULD NOT BE IN VERSION CONTROL!
TP: All secrets in this file are real production credentials.
"""
# TP: Real AWS credentials
AWS_PROD_ACCESS_KEY = "AKIAI44QH8DHBPRODKEY"
AWS_PROD_SECRET_KEY = "je7MtGbClwBF/2Zp9Utk/h3yCo8nvbPRODSECRET"
# TP: Real Stripe production key
STRIPE_PROD_SECRET = "sk_live_51HqJK2eZvKYlo2CProdSecretKey123"
# TP: Real GitHub PAT
GITHUB_PROD_PAT = "ghp_ProdTokenaBcDeFgHiJkLmNoPqRsTuVwXyZ12"
# TP: Real SSH private key
SSH_PRIVATE_KEY = """-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEA0Z3VS5JJcds3xfn/ygWyF8PbnGy0AHB1x4JLHlLxMIWPqlrRkj17
-----END OPENSSH PRIVATE KEY-----"""

View File

@@ -0,0 +1,192 @@
"""Semgrep-specific patterns module - streamlined version.
FINDING CLASSIFICATIONS:
- TRUE POSITIVE (TP): Actual security vulnerability
- FALSE POSITIVE (FP): Flagged but not a real issue in context
- UNCERTAIN: Could be either depending on deployment context
"""
import os
import re
import json
import logging
from typing import Any, Dict, List
from urllib.parse import urlparse
from flask import Flask, request, redirect, make_response, jsonify, send_file
import jwt
import requests
logger = logging.getLogger(__name__)
app = Flask(__name__)
# =============================================================================
# OPEN REDIRECT PATTERNS
# =============================================================================
@app.route("/redirect/unsafe")
def redirect_unsafe():
"""TP: Open redirect - user controls destination URL."""
next_url = request.args.get("next", "/")
return redirect(next_url)
@app.route("/redirect/validated")
def redirect_validated():
"""FP: Redirect with domain validation."""
next_url = request.args.get("next", "/")
parsed = urlparse(next_url)
if parsed.netloc and parsed.netloc != "example.com":
return redirect("/")
return redirect(next_url)
@app.route("/redirect/relative_only")
def redirect_relative():
"""UNCERTAIN: Checks :// but not protocol-relative URLs."""
next_url = request.args.get("next", "/")
if "://" in next_url:
return redirect("/")
return redirect(next_url)
# =============================================================================
# PATH TRAVERSAL PATTERNS
# =============================================================================
@app.route("/files/download")
def download_file():
"""TP: Path traversal via user-controlled filename."""
filename = request.args.get("file", "readme.txt")
filepath = os.path.join("/var/www/files", filename)
return send_file(filepath)
@app.route("/files/safe_download")
def safe_download():
"""FP: Path traversal prevented with realpath check."""
filename = request.args.get("file", "readme.txt")
base_dir = "/var/www/files"
filepath = os.path.join(base_dir, filename)
real_path = os.path.realpath(filepath)
if not real_path.startswith(os.path.realpath(base_dir)):
return "Access denied", 403
return send_file(real_path)
# =============================================================================
# JWT PATTERNS
# =============================================================================
JWT_SECRET = "super_secret_jwt_key_12345" # TP: Hardcoded JWT secret
def verify_jwt_none_allowed(token: str) -> Dict:
"""TP: JWT verification disabled."""
return jwt.decode(token, options={"verify_signature": False})
def verify_jwt_secure(token: str, secret: str) -> Dict:
"""FP: JWT with externally provided secret."""
return jwt.decode(token, secret, algorithms=["HS256"])
# =============================================================================
# SSRF PATTERNS
# =============================================================================
@app.route("/fetch/url")
def fetch_url():
"""TP: SSRF - fetches arbitrary user-provided URL."""
url = request.args.get("url")
response = requests.get(url)
return response.text
@app.route("/fetch/allowlisted")
def fetch_allowlisted():
"""FP: SSRF prevented with domain allowlist."""
url = request.args.get("url")
parsed = urlparse(url)
allowed_hosts = ["api.github.com", "cdn.example.com"]
if parsed.netloc not in allowed_hosts:
return "Domain not allowed", 403
response = requests.get(url)
return response.text
# =============================================================================
# HARDCODED CREDENTIALS PATTERNS
# =============================================================================
# TP: Hardcoded credentials
DATABASE_URL = "postgresql://admin:secretpassword123@db.example.com:5432/prod"
AWS_ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE"
# FP: Placeholder credentials
EXAMPLE_API_KEY = "your_api_key_here"
TEST_DATABASE_URL = "postgresql://test:test@localhost:5432/test_db"
# UNCERTAIN: Test key format but could be real
STRIPE_KEY = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
# =============================================================================
# COMMAND INJECTION PATTERNS
# =============================================================================
def run_system_command(user_input: str):
"""TP: Command injection via os.system."""
os.system(f"echo {user_input}")
def run_safe_command():
"""FP: Hardcoded command, no user input."""
os.system("date")
# =============================================================================
# INSECURE RANDOM PATTERNS
# =============================================================================
import random
def generate_token_insecure() -> str:
"""TP: Using random module for security token."""
return "".join(random.choices("abcdefghijklmnopqrstuvwxyz0123456789", k=32))
def shuffle_playlist(items: List[str]) -> List[str]:
"""FP: Random for non-security purpose."""
shuffled = items.copy()
random.shuffle(shuffled)
return shuffled
# =============================================================================
# DEBUG MODE PATTERNS
# =============================================================================
DEBUG_MODE = True # TP: Debug flag
@app.route("/debug/eval")
def debug_eval():
"""TP: Debug endpoint with eval."""
if DEBUG_MODE:
expr = request.args.get("expr", "1+1")
return str(eval(expr))
return "Disabled"
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=5001) # TP: Debug mode

View File

@@ -0,0 +1 @@
"""Services module with additional patterns."""

View File

@@ -0,0 +1,39 @@
"""Authentication service - streamlined version."""
import hashlib
import hmac
import secrets
from typing import Optional
class AuthenticationService:
"""Handle user authentication."""
# TP: Hardcoded JWT secret
JWT_SECRET = "hardcoded_jwt_secret_key_2024"
# FP: Default for development only
DEV_SECRET = "development_only_not_production"
def hash_password_insecure(self, password: str) -> str:
"""TP: MD5 for password hashing."""
return hashlib.md5(password.encode()).hexdigest()
def generate_token_secure(self, user_id: int) -> str:
"""FP: Secrets module for token generation."""
token = secrets.token_urlsafe(32)
return f"{user_id}:{token}"
def verify_webhook_signature(self, payload: bytes, signature: str) -> bool:
"""FP: HMAC verification is secure."""
expected = hmac.new(
self.JWT_SECRET.encode(), payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
def verify_webhook_insecure(self, payload: bytes, signature: str) -> bool:
"""TP: Using == for signature comparison (timing attack)."""
expected = hmac.new(
self.JWT_SECRET.encode(), payload, hashlib.sha256
).hexdigest()
return expected == signature # Timing attack vulnerable!

View File

@@ -0,0 +1,42 @@
"""File handling service - streamlined version."""
import os
import pickle
import tempfile
from typing import Any
import yaml
class FileService:
"""Handle file operations."""
def load_pickle_user_path(self, filepath: str) -> Any:
"""TP: Pickle from user-controlled path."""
with open(filepath, "rb") as f:
return pickle.load(f)
def load_pickle_fixed_path(self) -> Any:
"""FP: Pickle from known internal path."""
with open("/etc/app/cache.pkl", "rb") as f:
return pickle.load(f)
def save_temp_insecure(self, data: bytes) -> str:
"""TP: Predictable temp file."""
filepath = f"/tmp/data_{os.getpid()}.dat"
with open(filepath, "wb") as f:
f.write(data)
return filepath
def save_temp_secure(self, data: bytes) -> str:
"""FP: Secure temp file creation."""
with tempfile.NamedTemporaryFile(delete=False) as f:
f.write(data)
return f.name
def load_yaml_unsafe(self, yaml_string: str) -> Any:
"""TP: Unsafe YAML loader."""
return yaml.load(yaml_string, Loader=yaml.Loader)
def load_yaml_safe(self, yaml_string: str) -> Any:
"""FP: SafeLoader is secure."""
return yaml.safe_load(yaml_string)

175
src/security_demo/utils.py Normal file
View File

@@ -0,0 +1,175 @@
"""Utilities module - streamlined for Pylint patterns.
FINDING CLASSIFICATIONS:
- TRUE POSITIVE (TP): Actual code quality issue
- FALSE POSITIVE (FP): Flagged but acceptable in context
- UNCERTAIN: Depends on coding standards/context
"""
import json
import logging
from typing import Any, Dict, List
# TP: Module-level variable not UPPER_CASE
global_counter = 0
# FP: Constant follows convention
MAX_RETRIES = 3
# =============================================================================
# NAMING CONVENTION PATTERNS
# =============================================================================
def processData(items): # TP: not snake_case
"""Process items."""
return [item * 2 for item in items]
def calculate_total(values): # FP: Proper snake_case
"""Calculate total."""
return sum(values)
class userManager: # TP: not PascalCase
"""Manage users."""
pass
class UserRepository: # FP: Proper PascalCase
"""User repository."""
pass
# =============================================================================
# ARGUMENT PATTERNS
# =============================================================================
def too_many_arguments(a, b, c, d, e, f, g, h, i, j, k):
"""TP: Too many arguments."""
return sum([a, b, c, d, e, f, g, h, i, j, k])
def reasonable_arguments(user_id: int, name: str, email: str) -> dict:
"""FP: Reasonable number of arguments."""
return {"id": user_id, "name": name, "email": email}
# =============================================================================
# DEFAULT ARGUMENT PATTERNS
# =============================================================================
def mutable_default_list(items=[]): # TP: Mutable default
"""TP: Mutable default argument."""
items.append(1)
return items
def safe_default_none(items=None): # FP: Safe None default
"""FP: Safe None default pattern."""
if items is None:
items = []
return items
# =============================================================================
# EXCEPTION HANDLING PATTERNS
# =============================================================================
def bare_except_handler(data):
"""TP: Bare except catches everything."""
try:
return json.loads(data)
except: # TP: bare except
return None
def specific_except_handler(data):
"""FP: Specific exception handling."""
try:
return json.loads(data)
except json.JSONDecodeError:
return None
# =============================================================================
# BUILTIN SHADOWING PATTERNS
# =============================================================================
def shadow_builtins(list, dict): # TP: Shadows builtins
"""TP: Shadows multiple builtins."""
return len(list) + len(dict)
def proper_naming(items: List[int], mapping: Dict) -> int: # FP
"""FP: Descriptive names don't shadow."""
return len(items) + len(mapping)
# =============================================================================
# RETURN STATEMENT PATTERNS
# =============================================================================
def inconsistent_return(value): # TP: Implicit None
"""TP: Some paths return None implicitly."""
if value > 0:
return value
# Implicit None return
def all_paths_return(value): # FP
"""FP: All paths return explicitly."""
if value > 0:
return value
return 0
# =============================================================================
# LOOP PATTERNS
# =============================================================================
def range_len_antipattern(items): # TP: Should use enumerate
"""TP: Should use enumerate."""
result = []
for i in range(len(items)):
result.append((i, items[i]))
return result
def proper_enumerate(items): # FP
"""FP: Proper enumerate usage."""
return [(i, item) for i, item in enumerate(items)]
# =============================================================================
# DOCUMENTATION PATTERNS
# =============================================================================
def function_without_docstring(): # TP: Missing docstring
pass
def function_with_docstring(): # FP
"""This function has a docstring."""
pass
class ClassWithoutDocstring: # TP
pass
class ClassWithDocstring: # FP
"""This class has a docstring."""
pass

View File

@@ -0,0 +1,174 @@
"""Web application module - streamlined version.
FINDING CLASSIFICATIONS:
- TRUE POSITIVE (TP): Actual security vulnerability
- FALSE POSITIVE (FP): Flagged but not a real issue in context
- UNCERTAIN: Could be either depending on deployment context
"""
import os
import subprocess
import pickle
import tempfile
from typing import Any
from flask import Flask, request, render_template_string
import yaml
app = Flask(__name__)
# TP: Hardcoded secret key
app.config["SECRET_KEY"] = "production_secret_key_v2_xK9#mP2$"
# FP: Environment variable with fallback
app.config["DEV_API_KEY"] = os.environ.get("API_KEY", "dev_placeholder_key")
# =============================================================================
# COMMAND INJECTION PATTERNS
# =============================================================================
@app.route("/admin/execute")
def admin_execute():
"""TP: Direct shell injection from user input."""
command = request.args.get("cmd", "whoami")
result = subprocess.call(command, shell=True)
return {"exit_code": result}
@app.route("/build/compile")
def compile_code():
"""FP: Shell=True but command is completely hardcoded."""
result = subprocess.call("make clean && make build", shell=True)
return {"status": "completed", "exit_code": result}
@app.route("/health/disk")
def check_disk():
"""FP: No shell, hardcoded command list."""
result = subprocess.run(["/usr/bin/df", "-h", "/"], capture_output=True, text=True)
return {"disk_usage": result.stdout}
# =============================================================================
# TEMPLATE INJECTION PATTERNS
# =============================================================================
@app.route("/render/custom")
def render_custom():
"""TP: User controls entire template string."""
template = request.args.get("tpl", "{{ 7*7 }}")
return render_template_string(template)
@app.route("/report/generate")
def generate_report():
"""FP: Template hardcoded, only data is dynamic."""
user_name = request.args.get("name", "Anonymous")
REPORT_TEMPLATE = "<h1>Report for {{ name }}</h1>"
return render_template_string(REPORT_TEMPLATE, name=user_name)
# =============================================================================
# DESERIALIZATION PATTERNS
# =============================================================================
@app.route("/session/load")
def load_session():
"""TP: Pickle load from user-controlled path."""
session_file = request.args.get("file")
with open(session_file, "rb") as f:
data = pickle.load(f)
return {"session": str(data)}
@app.route("/config/load")
def load_config():
"""FP: Pickle from known safe internal path."""
with open("/etc/app/internal_config.pkl", "rb") as f:
config = pickle.load(f)
return {"config_keys": list(config.keys())}
# =============================================================================
# YAML PATTERNS
# =============================================================================
@app.route("/yaml/parse")
def parse_yaml():
"""TP: Unsafe YAML loader with user input."""
yaml_content = request.get_data(as_text=True)
data = yaml.load(yaml_content, Loader=yaml.Loader)
return {"parsed": data}
@app.route("/yaml/safe")
def yaml_safe():
"""FP: SafeLoader is secure."""
yaml_content = request.get_data(as_text=True)
data = yaml.safe_load(yaml_content)
return {"data": data}
# =============================================================================
# TEMP FILE PATTERNS
# =============================================================================
@app.route("/upload/process")
def process_upload():
"""TP: Predictable temp file path."""
data = request.get_data()
filepath = f"/tmp/upload_{os.getpid()}"
with open(filepath, "wb") as f:
f.write(data)
return {"saved_to": filepath}
@app.route("/export/csv")
def export_csv():
"""FP: Uses tempfile module correctly."""
with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
f.write("name,value\n")
return {"file": f.name}
# =============================================================================
# EVAL PATTERNS
# =============================================================================
def eval_user_code(code: str) -> Any:
"""TP: Direct eval of user input."""
return eval(code)
def literal_eval_safe(expr: str) -> Any:
"""FP: ast.literal_eval is safe."""
import ast
return ast.literal_eval(expr)
# =============================================================================
# BINDING PATTERNS
# =============================================================================
def get_production_bind() -> str:
"""TP: Binds to all interfaces."""
return "0.0.0.0"
def get_internal_bind() -> str:
"""FP: Localhost only."""
return "127.0.0.1"
if __name__ == "__main__":
app.run(host=get_production_bind(), debug=True, port=5000)