From 3723b33cade8c798fa10d39a4ad999b70d90adde Mon Sep 17 00:00:00 2001 From: poduck Date: Fri, 28 Nov 2025 21:11:26 -0500 Subject: [PATCH] feat: Add plugin code analysis and HTTP whitelist validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented static code analysis to detect and validate HTTP calls in plugins before they are uploaded or approved for the marketplace. **New Functions:** 1. **analyze_plugin_http_calls(script):** - Parses plugin code using Python AST - Detects all api.http_* method calls (GET, POST, PUT, PATCH, DELETE) - Extracts URL from first argument if it's a string literal - Handles dynamic URLs (f-strings, variables) with appropriate warnings - Returns list of HTTP calls with method, URL, and line number 2. **validate_plugin_whitelist(script, scheduled_task):** - Analyzes plugin code for HTTP calls - Validates each detected URL against WhitelistedURL model - Checks both platform-wide and plugin-specific whitelists - Returns validation results with errors, warnings, and detected calls - Provides clear error messages with line numbers **Validation Logic:** - **Static URLs** (string literals): Validated against whitelist, error if not found - **Dynamic URLs** (f-strings, variables): Warning issued, runtime validation required - **Syntax Errors**: Caught and reported as validation errors - **Line Numbers**: All errors/warnings include line number for debugging **Use Cases:** 1. Pre-upload validation: Check plugin before saving to database 2. Approval workflow: Platform staff can see which URLs need whitelisting 3. Marketplace submission: Reject plugins with non-whitelisted URLs 4. Security audit: Analyze existing plugins for HTTP call patterns **Error Messages:** - Clear, actionable messages with line numbers - Direct users to pluginaccess@smoothschedule.com for whitelisting - Warns about dynamic URLs that can't be statically validated This enables proactive security enforcement before plugins are executed, preventing runtime failures and improving user experience. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- smoothschedule/schedule/safe_scripting.py | 129 ++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/smoothschedule/schedule/safe_scripting.py b/smoothschedule/schedule/safe_scripting.py index e9a2306..58faa50 100644 --- a/smoothschedule/schedule/safe_scripting.py +++ b/smoothschedule/schedule/safe_scripting.py @@ -713,6 +713,135 @@ class SafeScriptEngine: return ast.unparse(transformed) +def analyze_plugin_http_calls(script: str) -> List[Dict[str, Any]]: + """ + Analyze plugin code to detect HTTP method calls. + + Args: + script: Plugin code to analyze + + Returns: + List of dictionaries with detected HTTP calls: + [ + { + 'method': 'GET', # HTTP method + 'url': 'https://api.example.com/data', # URL if detectable + 'line': 5, # Line number + }, + ... + ] + + Raises: + SyntaxError: If code doesn't parse + """ + http_calls = [] + + try: + tree = ast.parse(script) + except SyntaxError as e: + raise SyntaxError(f"Invalid Python syntax: {e}") + + # HTTP methods to detect + http_methods = { + 'http_get': 'GET', + 'http_post': 'POST', + 'http_put': 'PUT', + 'http_patch': 'PATCH', + 'http_delete': 'DELETE', + } + + for node in ast.walk(tree): + if isinstance(node, ast.Call): + # Check if it's an API call like api.http_get(...) + if isinstance(node.func, ast.Attribute): + method_name = node.func.attr + if method_name in http_methods: + http_method = http_methods[method_name] + + # Try to extract the URL argument (first positional argument) + url = None + if node.args and len(node.args) > 0: + first_arg = node.args[0] + # If it's a string literal, extract it + if isinstance(first_arg, ast.Constant) and isinstance(first_arg.value, str): + url = first_arg.value + # If it's an f-string or JoinedStr + elif isinstance(first_arg, ast.JoinedStr): + # Can't statically determine f-string URLs + url = '' + # If it's a Name (variable) + elif isinstance(first_arg, ast.Name): + url = f'' + else: + url = '' + + http_calls.append({ + 'method': http_method, + 'url': url, + 'line': node.lineno, + }) + + return http_calls + + +def validate_plugin_whitelist(script: str, scheduled_task=None) -> Dict[str, Any]: + """ + Validate that all HTTP calls in plugin code are whitelisted. + + Args: + script: Plugin code to validate + scheduled_task: ScheduledTask instance (optional, for plugin-specific whitelist) + + Returns: + Dictionary with validation results: + { + 'valid': bool, + 'errors': List[str], # Validation error messages + 'warnings': List[str], # Warnings (e.g., dynamic URLs) + 'http_calls': List[Dict], # Detected HTTP calls + } + """ + from .models import WhitelistedURL + + result = { + 'valid': True, + 'errors': [], + 'warnings': [], + 'http_calls': [], + } + + try: + http_calls = analyze_plugin_http_calls(script) + result['http_calls'] = http_calls + + for call in http_calls: + url = call['url'] + method = call['method'] + line = call['line'] + + # Skip if URL is dynamic (can't validate statically) + if not url or url.startswith('<'): + result['warnings'].append( + f"Line {line}: {method} request uses dynamic URL - cannot validate at upload time. " + f"Ensure URL is whitelisted before running." + ) + continue + + # Check if URL is whitelisted + if not WhitelistedURL.is_url_whitelisted(url, method, scheduled_task): + result['valid'] = False + result['errors'].append( + f"Line {line}: {method} request to '{url}' is not whitelisted. " + f"Contact support at pluginaccess@smoothschedule.com to request whitelisting." + ) + + except SyntaxError as e: + result['valid'] = False + result['errors'].append(f"Syntax error: {str(e)}") + + return result + + def test_script_execution(): """Test the safe script engine"""