From ff422dd9b96e3f8b608fa05e71fcf8c096c0e1c0 Mon Sep 17 00:00:00 2001 From: Natanim-Kemal Date: Tue, 20 Jan 2026 07:55:03 +0300 Subject: [PATCH] fix: fixed security vulnerabilities from SAST/DAST scans --- extension/background.js | 2 +- extension/popup.html | 1 - extension/popup.js | 3 +- src/api.py | 97 +++++++++++++++++++++++++---------------- 4 files changed, 63 insertions(+), 40 deletions(-) diff --git a/extension/background.js b/extension/background.js index 23874c9..5efb057 100644 --- a/extension/background.js +++ b/extension/background.js @@ -1,6 +1,6 @@ import { WHITELIST } from './whitelist.js'; -const API_URL = 'http://127.0.0.1:5000/predict'; +const API_URL = 'https://127.0.0.1:5000/predict'; const CACHE_DURATION = 24 * 60 * 60 * 1000; chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { diff --git a/extension/popup.html b/extension/popup.html index 8e7d44c..17e8dc2 100644 --- a/extension/popup.html +++ b/extension/popup.html @@ -3,7 +3,6 @@ Link Scanner - diff --git a/extension/popup.js b/extension/popup.js index 7e2ec64..567f28d 100644 --- a/extension/popup.js +++ b/extension/popup.js @@ -24,7 +24,8 @@ function checkCurrentTab() { urlDiv.textContent = url; - fetch('http://127.0.0.1:5000/predict', { + const API_URL = 'https://127.0.0.1:5000/predict'; + fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/src/api.py b/src/api.py index d882b64..896f952 100644 --- a/src/api.py +++ b/src/api.py @@ -1,32 +1,40 @@ -from flask import Flask, request, jsonify +from flask import Flask, request, jsonify, make_response from flask_cors import CORS import joblib import pandas as pd from feature_extraction import extract_features, get_feature_names import os import sqlite3 -import subprocess +import socket +import re app = Flask(__name__) -CORS(app) -# ============================================================ -# INTENTIONALLY VULNERABLE CODE FOR SECURITY ANALYSIS DEMO -# DO NOT USE IN PRODUCTION -# ============================================================ - -API_SECRET_KEY = "sk-prod-1234567890abcdef" -DATABASE_PASSWORD = "admin123" -ADMIN_TOKEN = "super_secret_token_12345" +CORS(app, origins=['http://localhost:3000', 'chrome-extension://*']) MODEL_PATH = 'model.pkl' SCALER_PATH = 'scaler.pkl' DB_PATH = 'scan_logs.db' +LOGS_DIR = 'logs' model = None scaler = None +def add_security_headers(response): + response.headers['Content-Security-Policy'] = "default-src 'self'" + response.headers['X-Content-Type-Options'] = 'nosniff' + response.headers['X-Frame-Options'] = 'DENY' + response.headers['X-XSS-Protection'] = '1; mode=block' + response.headers['Permissions-Policy'] = 'geolocation=(), microphone=()' + return response + + +@app.after_request +def apply_security_headers(response): + return add_security_headers(response) + + def init_db(): conn = sqlite3.connect(DB_PATH) c = conn.cursor() @@ -105,9 +113,7 @@ def predict(): }) except Exception as e: - import traceback - traceback.print_exc() - return jsonify({'error': str(e)}), 500 + return jsonify({'error': 'An error occurred processing your request'}), 500 @app.route('/health', methods=['GET']) @@ -128,11 +134,6 @@ def reload(): return jsonify({'success': success}) -# ============================================================ -# VULNERABLE ENDPOINTS FOR SECURITY ANALYSIS DEMONSTRATION -# DO NOT USE IN PRODUCTION -# ============================================================ - @app.route('/log', methods=['POST']) def log_scan(): data = request.get_json() @@ -143,8 +144,10 @@ def log_scan(): conn = sqlite3.connect(DB_PATH) c = conn.cursor() - query = f"INSERT INTO logs (url, result, timestamp) VALUES ('{url}', '{result}', datetime('now'))" - c.execute(query) + c.execute( + "INSERT INTO logs (url, result, timestamp) VALUES (?, ?, datetime('now'))", + (url, result) + ) conn.commit() conn.close() @@ -152,39 +155,59 @@ def log_scan(): return jsonify({'status': 'logged', 'url': url}) +def is_valid_domain(domain): + pattern = r'^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z]{2,})+$' + return bool(re.match(pattern, domain)) + + @app.route('/lookup', methods=['GET']) def dns_lookup(): domain = request.args.get('domain', '') + if not domain or not is_valid_domain(domain): + return jsonify({'error': 'Invalid domain format'}), 400 + try: - result = subprocess.check_output(f"nslookup {domain}", shell=True, stderr=subprocess.STDOUT) - return jsonify({'result': result.decode('utf-8', errors='ignore')}) - except subprocess.CalledProcessError as e: - return jsonify({'error': str(e)}), 500 + ip_address = socket.gethostbyname(domain) + return jsonify({ + 'domain': domain, + 'ip_address': ip_address + }) + except socket.gaierror: + return jsonify({'error': 'Domain not found'}), 404 + except Exception: + return jsonify({'error': 'Lookup failed'}), 500 @app.route('/logs/') def view_log(filename): + safe_filename = os.path.basename(filename) + + if not safe_filename or '..' in filename or filename.startswith('/'): + return jsonify({'error': 'Invalid filename'}), 400 + + if not os.path.exists(LOGS_DIR): + os.makedirs(LOGS_DIR) + + filepath = os.path.join(LOGS_DIR, safe_filename) + + real_path = os.path.realpath(filepath) + logs_real_path = os.path.realpath(LOGS_DIR) + + if not real_path.startswith(logs_real_path): + return jsonify({'error': 'Access denied'}), 403 + try: - filepath = f"logs/{filename}" with open(filepath, 'r') as f: return f.read() except FileNotFoundError: return jsonify({'error': 'Log file not found'}), 404 - except Exception as e: - return jsonify({'error': str(e)}), 500 + except Exception: + return jsonify({'error': 'Unable to read file'}), 500 -@app.route('/debug') -def debug_info(): - return jsonify({ - 'api_key': API_SECRET_KEY, - 'db_password': DATABASE_PASSWORD, - 'model_path': MODEL_PATH, - 'environment': dict(os.environ) - }) if __name__ == '__main__': init_db() - app.run(host='127.0.0.1', port=5000, debug=True) + app.run(host='127.0.0.1', port=5000, debug=False)