import os import logging from pathlib import Path from flask import Flask # Local imports from .utils.settings import get_settings from .utils.rules_engine import RuleEngine, load_rules_from_yaml, Rule # our code based rules from .rules.function_rules import ( FactAdapter, FunctionRuleAdapter, script_src_uses_data_or_blob, script_src_has_dangerous_extension, script_third_party_host, form_submits_to_different_host, form_http_on_https_page, form_action_missing, ) from . import routes # blueprint # from .utils import io_helpers # if need logging/setup later # from .utils import cache_db # available for future injections def create_app() -> Flask: """ Create and configure the Flask application instance. Returns: Flask: The configured Flask app. """ # Basic app object app = Flask(__name__, template_folder="templates", static_folder="static") # Load settings (safe fallback to defaults if file missing) settings = get_settings() # Secret key loaded from env (warn if missing) app.secret_key = os.getenv("SECRET_KEY") if not app.secret_key: app.logger.warning("[init] SECRET_KEY is not set; sessions may be insecure in production.") # Configure storage directory (bind-mount is still handled by sandbox.sh) sandbox_storage_default = Path("/data") app.config["SANDBOX_STORAGE"] = str(sandbox_storage_default) # --------------------------- # Suspicious Rules Engine # --------------------------- # Determine rules file path relative to this package (allow env override) base_dir = Path(__file__).resolve().parent default_rules_path = base_dir / "config" / "suspicious_rules.yaml" rules_path_str = os.getenv("SNEAKYSCOPE_RULES_FILE", str(default_rules_path)) rules_path = Path(rules_path_str) # Create engine bound to Flask logger so all verbose/debug goes to app.logger engine = RuleEngine(rules=[], logger=app.logger) # Try to load from YAML if present; log clearly if not if rules_path.exists(): try: loaded_rules = load_rules_from_yaml(rules_path, logger=app.logger) # Add rules one-by-one (explicit, clearer logs if any rule fails to compile) index = 0 total = len(loaded_rules) while index < total: engine.add_rule(loaded_rules[index]) index = index + 1 app.logger.info(f"[init] Loaded {len(loaded_rules)} suspicious rules from {rules_path}") except Exception as e: app.logger.warning(f"[init] Failed loading rules from {rules_path}: {e}") else: app.logger.warning(f"[init] Rules file not found at {rules_path}. Engine will start with zero rules.") # Built-in function-based rules adapter = FactAdapter(logger=app.logger) engine.add_rule(Rule( name="form_action_missing", description="Form has no action attribute", category="form", rule_type="function", function=FunctionRuleAdapter(form_action_missing, category="form", adapter=adapter), )) engine.add_rule(Rule( name="form_http_on_https_page", description="Form submits via HTTP from HTTPS page", category="form", rule_type="function", function=FunctionRuleAdapter(form_http_on_https_page, category="form", adapter=adapter), )) engine.add_rule(Rule( name="form_submits_to_different_host", description="Form submits to a different host", category="form", rule_type="function", function=FunctionRuleAdapter(form_submits_to_different_host, category="form", adapter=adapter), )) # Script rules expect dict 'facts' (you’ll wire per-script facts later) engine.add_rule(Rule( name="script_src_uses_data_or_blob", description="Script src uses data:/blob: URL", category="script", rule_type="function", function=FunctionRuleAdapter(script_src_uses_data_or_blob, category="script", adapter=adapter), )) engine.add_rule(Rule( name="script_src_has_dangerous_extension", description="External script with dangerous extension", category="script", rule_type="function", function=FunctionRuleAdapter(script_src_has_dangerous_extension, category="script", adapter=adapter), )) engine.add_rule(Rule( name="script_third_party_host", description="Script is from a third-party host", category="script", rule_type="function", function=FunctionRuleAdapter(script_third_party_host, category="script", adapter=adapter), )) # Store engine both ways: attribute (convenient) and config app.rule_engine = engine app.config["RULE_ENGINE"] = engine # App metadata available to templates app.config["APP_NAME"] = settings.app.name app.config["APP_VERSION"] = f"v{settings.app.version_major}.{settings.app.version_minor}" # Register blueprints app.register_blueprint(routes.bp) # Example log lines so we know we booted cleanly app.logger.info(f"SneakyScope started: {app.config['APP_NAME']} {app.config['APP_VERSION']}") app.logger.info(f"SANDBOX_STORAGE: {app.config['SANDBOX_STORAGE']}") app.logger.info(f"Registered {len(engine.rules)} total rules (YAML + function)") return app