Conversation
…ty parsing, a comprehensive risk scoring system, and a new dedicated report generation module.
| context = ssl.create_default_context() | ||
| try: | ||
| with socket.create_connection((domain, 443), timeout=2) as sock: | ||
| with context.wrap_socket(sock, server_hostname=domain) as ssock: |
Check failure
Code scanning / CodeQL
Use of insecure SSL/TLS version High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 3 days ago
In general, the problem is that ssl.create_default_context() is used without constraining the minimum TLS version, so TLS 1.0 and 1.1 may be negotiated. To fix this, configure the SSL context to disallow TLS 1.0 and 1.1, either via context.minimum_version = ssl.TLSVersion.TLSv1_2 (Python 3.7+) or, for older versions, by setting the appropriate OP_NO_TLSv1 and OP_NO_TLSv1_1 flags on context.options. This preserves existing functionality (connecting to servers, reading certificates, checking TLS version) while ensuring that only strong protocol versions are used for the main validated connection.
The single best fix here is to keep using ssl.create_default_context() but explicitly set context.minimum_version = ssl.TLSVersion.TLSv1_2 right after the context is created. For maximal compatibility with slightly older Python 3 versions that might not support minimum_version, we can defensively fall back to disabling TLSv1 and TLSv1_1 via context.options. These changes occur in Framework/Built_In_Automation/Security/nmap_scan.py in the get_cryptography_data function around line 927, immediately after context = ssl.create_default_context().
We do not need new imports because ssl is already imported at the top of the file. The fix is implemented by replacing the single line that creates the context with a small block that both creates and configures it, leaving all subsequent logic (socket creation, certificate retrieval, TLS version checking) unchanged.
| @@ -925,6 +925,15 @@ | ||
| print(f"[{domain}] Fetching SSL/TLS Certificate information...", flush=True) | ||
| try: | ||
| context = ssl.create_default_context() | ||
| # Restrict to strong TLS versions (TLS 1.2 and above) | ||
| if hasattr(context, "minimum_version") and hasattr(ssl, "TLSVersion"): | ||
| context.minimum_version = ssl.TLSVersion.TLSv1_2 | ||
| else: | ||
| # Fallback for older Python versions: disable TLSv1 and TLSv1_1 explicitly | ||
| if hasattr(ssl, "OP_NO_TLSv1"): | ||
| context.options |= ssl.OP_NO_TLSv1 | ||
| if hasattr(ssl, "OP_NO_TLSv1_1"): | ||
| context.options |= ssl.OP_NO_TLSv1_1 | ||
| try: | ||
| with socket.create_connection((domain, 443), timeout=2) as sock: | ||
| with context.wrap_socket(sock, server_hostname=domain) as ssock: |
| p = Path(file_path) | ||
|
|
||
| # Absolute path provided | ||
| if p.is_absolute() and p.is_file(): |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 3 days ago
In general, to fix this issue we must ensure that any user‑supplied path is validated and constrained to a safe root directory before being used in filesystem access. This usually means combining the untrusted segment with a trusted base directory, normalizing the resulting path, and verifying that the resolved path is within the base.
For this function, the safest, least disruptive approach is:
- Treat any
file_path(absolute or relative) as a path segment that must reside underAUTO_LOG_DIR. - Combine
AUTO_LOG_DIRwithfile_pathand resolve the result with.resolve()to eliminate..and symlinks. - Check that the resolved path is a file and that it is contained within
AUTO_LOG_DIRusingis_relative_to(Python 3.9+) or an equivalent prefix/ancestor check. - If the check fails, return a 400/404 JSON error instead of serving the file.
- Keep the existing “run_dirs search” behavior for relative paths, but ensure the resulting
candidateis also underAUTO_LOG_DIRbefore returning it (a defense‑in‑depth step).
Concretely, in server/reports.py:
- Replace the “absolute path” branch (
if p.is_absolute() and p.is_file(): return FileResponse(str(p))) so absolute user paths are no longer used directly. - Instead, construct
candidate = (AUTO_LOG_DIR / file_path).resolve(), verify thatcandidate.is_file()and thatAUTO_LOG_DIRis a parent ofcandidate. If so, return it; otherwise, continue to the existing search or return an error. - Optionally, add a small helper function (within the snippet) to perform the “is path under base dir” check using
Path.is_relative_towhen available, orbase in path.parentsfor older Pythons.
This keeps existing functionality (serving security reports from within AutomationLog) while eliminating the risk of reading arbitrary paths.
| @@ -26,10 +26,17 @@ | ||
| if file_path: | ||
| p = Path(file_path) | ||
|
|
||
| # Absolute path provided | ||
| if p.is_absolute() and p.is_file(): | ||
| return FileResponse(str(p)) | ||
| # Resolve the requested path under AUTO_LOG_DIR to avoid path traversal | ||
| explicit_candidate = (AUTO_LOG_DIR / p).resolve() | ||
| try: | ||
| is_under_auto_log = explicit_candidate.is_relative_to(AUTO_LOG_DIR) # type: ignore[attr-defined] | ||
| except AttributeError: | ||
| # Python < 3.9 fallback: check by ancestor relationship | ||
| is_under_auto_log = AUTO_LOG_DIR == explicit_candidate or AUTO_LOG_DIR in explicit_candidate.parents | ||
|
|
||
| if explicit_candidate.is_file() and is_under_auto_log: | ||
| return FileResponse(str(explicit_candidate)) | ||
|
|
||
| # Relative path – search across every debug run folder (newest first) | ||
| run_dirs = sorted( | ||
| AUTO_LOG_DIR.glob("debug_*/session_*/*"), | ||
| @@ -37,8 +43,13 @@ | ||
| reverse=True, | ||
| ) | ||
| for run_dir in run_dirs: | ||
| candidate = run_dir / file_path | ||
| if candidate.is_file(): | ||
| candidate = (run_dir / p).resolve() | ||
| try: | ||
| candidate_under_auto_log = candidate.is_relative_to(AUTO_LOG_DIR) # type: ignore[attr-defined] | ||
| except AttributeError: | ||
| candidate_under_auto_log = AUTO_LOG_DIR == candidate or AUTO_LOG_DIR in candidate.parents | ||
|
|
||
| if candidate.is_file() and candidate_under_auto_log: | ||
| return FileResponse(str(candidate)) | ||
|
|
||
| # ── Case 2: auto-find the newest HTML in any security_report folder ── |
|
|
||
| # Absolute path provided | ||
| if p.is_absolute() and p.is_file(): | ||
| return FileResponse(str(p)) |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 3 days ago
In general, the fix is to ensure that any path derived from user input is constrained to a safe root directory and is normalized before use. That means we should never directly serve an arbitrary absolute path provided by the client. Instead, we should only allow access to files within a specific directory tree (here, likely under AUTO_LOG_DIR), resolving and normalizing paths, and then verifying that the result is still within that root. For additional safety, we can also forbid absolute paths altogether for this endpoint.
For this specific function, the single best fix with minimal change in behavior is:
- Treat
file_pathas a path relative to the automation log root (AUTO_LOG_DIR), not as an arbitrary absolute path. - Build an absolute candidate path as
AUTO_LOG_DIR / file_path, normalize it with.resolve(), and verify that the resolved path is withinAUTO_LOG_DIRusing.relative_to(AUTO_LOG_DIR)(catchingValueErrorto reject attempts to escape via..or absolute paths). - If the normalized path is a file, serve it; otherwise continue with the existing fallback logic (searching run dirs and finally auto‑finding the latest report).
- Remove the branch that returns files for arbitrary user‑supplied absolute paths.
Concretely in server/reports.py:
- In
get_security_report, replace the block that createsp = Path(file_path)and conditionally returns any absolute file (if p.is_absolute() and p.is_file():) plus the current relative search with a safe resolution step:- Construct
raw_path = AUTO_LOG_DIR / file_path. - Call
resolved = raw_path.resolve(). - Use
resolved.relative_to(AUTO_LOG_DIR)inside atryblock to ensure the resolved path is underAUTO_LOG_DIR. If it isn’t, ignore it and move to the fallback logic. - If it is and
resolved.is_file()is true, returnFileResponse(str(resolved)).
- Construct
- Optionally keep the existing run‑directory search as an additional, but now redundant, resolution method; however, it’s simpler and safer just to rely on the normalized
AUTO_LOG_DIRcheck, since all run dirs are already underAUTO_LOG_DIR.
No new imports are needed: pathlib.Path is already imported and provides .resolve() and .relative_to().
| @@ -18,29 +18,27 @@ | ||
| """ | ||
| Security report downloader for the ZeuZ Node. | ||
|
|
||
| - file_path (optional): exact path of the report file (absolute OR relative to a run dir). | ||
| - file_path (optional): exact path of the report file (relative to the AutomationLog dir). | ||
| If not given, the newest HTML file in the security_report folder is returned. | ||
| """ | ||
| try: | ||
| # ── Case 1: caller supplied an explicit file path ────────────────────── | ||
| if file_path: | ||
| p = Path(file_path) | ||
| # Treat user input as a path relative to AUTO_LOG_DIR, and ensure it | ||
| # cannot escape this root via ".." or absolute paths. | ||
| raw_path = AUTO_LOG_DIR / file_path | ||
| resolved_path = raw_path.resolve() | ||
|
|
||
| # Absolute path provided | ||
| if p.is_absolute() and p.is_file(): | ||
| return FileResponse(str(p)) | ||
| try: | ||
| # This will raise ValueError if resolved_path is not under AUTO_LOG_DIR | ||
| resolved_path.relative_to(AUTO_LOG_DIR) | ||
| except ValueError: | ||
| # Disallow paths outside the AutomationLog directory | ||
| pass | ||
| else: | ||
| if resolved_path.is_file(): | ||
| return FileResponse(str(resolved_path)) | ||
|
|
||
| # Relative path – search across every debug run folder (newest first) | ||
| run_dirs = sorted( | ||
| AUTO_LOG_DIR.glob("debug_*/session_*/*"), | ||
| key=lambda d: d.stat().st_mtime, | ||
| reverse=True, | ||
| ) | ||
| for run_dir in run_dirs: | ||
| candidate = run_dir / file_path | ||
| if candidate.is_file(): | ||
| return FileResponse(str(candidate)) | ||
|
|
||
| # ── Case 2: auto-find the newest HTML in any security_report folder ── | ||
| candidates = list(AUTO_LOG_DIR.glob("debug_*/session_*/*/security_report/*.html")) | ||
|
|
| return FileResponse(str(latest_report)) | ||
|
|
||
| except Exception as e: | ||
| return JSONResponse({"error": str(e)}, status_code=500) |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 3 days ago
In general, to fix this kind of issue, the API should not return raw exception messages (or stack traces) to the client. Instead, it should return a generic, user-safe error message and log the detailed exception (including stack trace) on the server for diagnostics. This ensures developers still have the information needed for debugging while preventing information disclosure to external users.
For this specific route in server/reports.py, the best approach without changing existing functionality is:
- Keep the broad
except Exceptionto preserve current error-handling behavior (the route still returns HTTP 500 when something unexpected happens). - Inside the
exceptblock:- Log the exception details (including stack trace) on the server side.
- Return a generic JSON error message that does not include
str(e)or any sensitive details.
- To log the exception with a stack trace, use Python’s standard
loggingmodule andlogger.exception(...), which automatically includes the traceback. - This requires:
- Importing
loggingat the top ofserver/reports.py. - Initializing a module-level logger via
logging.getLogger(__name__). - Replacing
JSONResponse({"error": str(e)}, status_code=500)with a logging call and a genericJSONResponse.
- Importing
Concretely, in server/reports.py:
- Add
import loggingnear the other imports and definelogger = logging.getLogger(__name__)after the imports. - Modify the
exceptblock ofget_security_report(lines 56–57) to:- Call
logger.exception("Unhandled error in get_security_report"). - Return
JSONResponse({"error": "An internal error occurred while fetching the security report."}, status_code=500)or similarly generic text.
- Call
| @@ -4,7 +4,10 @@ | ||
| from fastapi import APIRouter | ||
| from fastapi.responses import FileResponse, JSONResponse | ||
| from typing import Optional | ||
| import logging | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| router = APIRouter(prefix="/debug/reports/security", tags=["security-reports"]) | ||
|
|
||
| # Resolve the Node's root directory relative to THIS file's location | ||
| @@ -54,4 +56,8 @@ | ||
| return FileResponse(str(latest_report)) | ||
|
|
||
| except Exception as e: | ||
| return JSONResponse({"error": str(e)}, status_code=500) | ||
| logger.exception("Unhandled error in get_security_report") | ||
| return JSONResponse( | ||
| {"error": "An internal error occurred while fetching the security report."}, | ||
| status_code=500, | ||
| ) |
Overview
This PR significantly refactors the
nmap_scan.pymodule to improve execution times, accurately parse a wider range of vulnerabilities, and overhauls the HTML security report for a more professional, modern look. Additionally, it lays the groundwork for in-depth cryptography testing by capturing and displayingcrypto_datawithin the same UI.Key Changes
1. Nmap Scanning Enhancements 🚀
-T4,--open, and--min-rate 1000to quickly identify relevant services.2. Improved Vulnerability Parsing 🛡️
script_outputfor indicators like"EXPLOIT","VULNERABLE", or"vulnerable".5.0to non-CVE vulnerabilities so that they accurately impact the risk profile.3. Redesigned HTML Reporting Engine 📊
(0-100)based on vulnerability count and their total severity.No Security Risks Found(Green/Clean) or an alert indicating the number of risks detected based on the scan's results.4. Cryptography Integration 🔐
ssl,socket, andurllibto handle cryptographic metrics.generate_htmlfunction has been updated to acceptcrypto_dataas a parameter and neatly appends the cryptography snippets into the final report.Impact