diff --git a/collectors/nmap_recon.py b/collectors/nmap_recon.py index 47d1518..43d81d4 100644 --- a/collectors/nmap_recon.py +++ b/collectors/nmap_recon.py @@ -1,7 +1,6 @@ import defusedxml.ElementTree as ET # nosec B405 — using defusedxml drop-in replacement import json import logging -import sys try: import nmap @@ -12,12 +11,6 @@ logger = logging.getLogger(__name__) -# Setup logging -logging.basicConfig( - filename='nmap_recon.log', level=logging.INFO, - format='%(asctime)s %(levelname)s:%(message)s', -) - class NmapRecon: def __init__(self, target): self.target = target @@ -30,12 +23,12 @@ def __init__(self, target): def run_scan(self): try: - logging.info(f'Starting scan for {self.target}') + logger.info('Starting scan for %s', self.target) self.nm.scan(self.target, arguments='-sV -oX nmap_scan.xml') - logging.info('Scan completed successfully') + logger.info('Scan completed successfully') except Exception as e: - logging.error(f'Scan failed for {self.target}: {str(e)}') - sys.exit(1) + logger.error('Scan failed for %s: %s', self.target, e) + raise RuntimeError(f'Scan failed for {self.target}: {str(e)}') from e def parse_xml_to_json(self): try: @@ -68,10 +61,10 @@ def parse_xml_to_json(self): with open('nmap_recon.json', 'w') as json_file: json.dump(data, json_file, indent=4) - logging.info('Parsed XML to JSON and saved to nmap_recon.json') + logger.info('Parsed XML to JSON and saved to nmap_recon.json') except Exception as e: - logging.error(f'Failed to parse XML: {str(e)}') - sys.exit(1) + logger.error('Failed to parse XML: %s', e) + raise RuntimeError(f'Failed to parse XML: {str(e)}') from e if __name__ == '__main__': target = 'target_ip_or_hostname' diff --git a/collectors/sqlmap_exploit.py b/collectors/sqlmap_exploit.py index d61550b..27d8082 100644 --- a/collectors/sqlmap_exploit.py +++ b/collectors/sqlmap_exploit.py @@ -1,3 +1,5 @@ +import time + import requests class SQLMapAPI: @@ -35,11 +37,22 @@ def main(): print(f'Task started with ID: {task_id}') - # Check task status + # Check task status (timeout after 10 minutes) + _MAX_POLLS = 600 status = api.get_task_status(task_id) - while status['status'] not in ['terminated', 'failed']: - print(f'Task status: {status["status"]}') + for _ in range(_MAX_POLLS): + task_status = status.get('status') + if not task_status: + print(f'Failed to get task status from SQLMap API: {status}') + raise SystemExit(1) + if task_status in ['terminated', 'failed']: + break + print(f'Task status: {task_status}') + time.sleep(1) status = api.get_task_status(task_id) + else: + print('Timed out waiting for task to complete') + raise SystemExit(1) # Fetch results results = api.get_results(task_id) @@ -61,11 +74,29 @@ def run_sqlmap(target_url: str, api_url: str = "http://localhost:8775/") -> dict if not task_id: return {"error": "sqlmap did not return a task ID", "target": target_url} + max_polls = 600 # ~10 min with 1 s sleep status = api.get_task_status(task_id) - while status.get("status") not in ("terminated", "failed"): + for _ in range(max_polls): + if status.get("status") in ("terminated", "failed"): + break + time.sleep(1) status = api.get_task_status(task_id) + else: + return { + "returncode": 1, + "error": "timed out waiting for sqlmap task", + "target": target_url, + } results = api.get_results(task_id) + if status.get("status") == "failed": + return { + "returncode": 1, + "error": "sqlmap task failed", + "status": status, + "result": results, + "target": target_url, + } return {"returncode": 0, "result": results, "target": target_url} except Exception as exc: return {"returncode": 1, "error": str(exc), "target": target_url} diff --git a/fuzz/fuzz_api_inputs.py b/fuzz/fuzz_api_inputs.py index 600e292..ef88c33 100644 --- a/fuzz/fuzz_api_inputs.py +++ b/fuzz/fuzz_api_inputs.py @@ -30,7 +30,21 @@ def _get_client(): import hancock_agent # noqa: E402 - _app = hancock_agent.app + # build_app requires a client and model name; supply a no-op mock so the + # fuzzer can exercise request parsing without a real LLM backend. + class _MockClient: + class chat: + class completions: + @staticmethod + def create(**kwargs): + class _Choice: + class message: + content = "mock" + class _Resp: + choices = [_Choice()] + return _Resp() + + _app = hancock_agent.build_app(_MockClient(), "mock-model") _app.config["TESTING"] = True _client = _app.test_client() return _client diff --git a/hancock_agent.py b/hancock_agent.py index 4143faa..f58b2c7 100644 --- a/hancock_agent.py +++ b/hancock_agent.py @@ -985,13 +985,20 @@ def geolocate_endpoint(): """OSINT geolocation — geolocate a list of IP/domain indicators.""" ok, err, _ = _check_auth_and_rate() if not ok: - _inc("errors_total"); return jsonify({"error": err}), 401 if "Unauthorized" in err else 429 - _inc("requests_total"); _inc("requests_by_endpoint", "/v1/geolocate"); _inc("requests_by_mode", "osint") + _inc("errors_total") + return jsonify({"error": err}), 401 if "Unauthorized" in err else 429 + _inc("requests_total") + _inc("requests_by_endpoint", "/v1/geolocate") + _inc("requests_by_mode", "osint") - data = request.get_json(force=True) + data = request.get_json(force=True, silent=True) + if not isinstance(data, dict): + _inc("errors_total") + return jsonify({"error": "JSON object required"}), 400 indicators = data.get("indicators", []) if not indicators: - _inc("errors_total"); return jsonify({"error": "indicators required"}), 400 + _inc("errors_total") + return jsonify({"error": "indicators required"}), 400 try: from collectors.osint_geolocation import GeoIPLookup @@ -1004,20 +1011,28 @@ def geolocate_endpoint(): }) except Exception as exc: logger.exception("Error in /v1/geolocate endpoint: %s", exc) - _inc("errors_total"); return jsonify({"error": "Internal server error"}), 500 + _inc("errors_total") + return jsonify({"error": "Internal server error"}), 500 @app.route("/v1/predict-locations", methods=["POST"]) def predict_locations_endpoint(): """OSINT predictive analytics — predict future threat infrastructure locations.""" ok, err, _ = _check_auth_and_rate() if not ok: - _inc("errors_total"); return jsonify({"error": err}), 401 if "Unauthorized" in err else 429 - _inc("requests_total"); _inc("requests_by_endpoint", "/v1/predict-locations"); _inc("requests_by_mode", "osint") + _inc("errors_total") + return jsonify({"error": err}), 401 if "Unauthorized" in err else 429 + _inc("requests_total") + _inc("requests_by_endpoint", "/v1/predict-locations") + _inc("requests_by_mode", "osint") - data = request.get_json(force=True) + data = request.get_json(force=True, silent=True) + if not isinstance(data, dict): + _inc("errors_total") + return jsonify({"error": "JSON object required"}), 400 historical_data = data.get("historical_data", []) if not historical_data: - _inc("errors_total"); return jsonify({"error": "historical_data required"}), 400 + _inc("errors_total") + return jsonify({"error": "historical_data required"}), 400 try: from collectors.osint_geolocation import ( @@ -1043,20 +1058,28 @@ def predict_locations_endpoint(): return jsonify({"predictions": predictions, "count": len(predictions)}) except Exception as exc: logger.exception("Error in /v1/predict-locations endpoint: %s", exc) - _inc("errors_total"); return jsonify({"error": "Internal server error"}), 500 + _inc("errors_total") + return jsonify({"error": "Internal server error"}), 500 @app.route("/v1/map-infrastructure", methods=["POST"]) def map_infrastructure_endpoint(): """OSINT infrastructure mapping — map and cluster threat infrastructure.""" ok, err, _ = _check_auth_and_rate() if not ok: - _inc("errors_total"); return jsonify({"error": err}), 401 if "Unauthorized" in err else 429 - _inc("requests_total"); _inc("requests_by_endpoint", "/v1/map-infrastructure"); _inc("requests_by_mode", "osint") + _inc("errors_total") + return jsonify({"error": err}), 401 if "Unauthorized" in err else 429 + _inc("requests_total") + _inc("requests_by_endpoint", "/v1/map-infrastructure") + _inc("requests_by_mode", "osint") - data = request.get_json(force=True) + data = request.get_json(force=True, silent=True) + if not isinstance(data, dict): + _inc("errors_total") + return jsonify({"error": "JSON object body required"}), 400 indicators = data.get("indicators", []) if not indicators: - _inc("errors_total"); return jsonify({"error": "indicators required"}), 400 + _inc("errors_total") + return jsonify({"error": "indicators required"}), 400 try: from collectors.osint_geolocation import InfrastructureMapper @@ -1065,7 +1088,8 @@ def map_infrastructure_endpoint(): return jsonify(mapping) except Exception as exc: logger.exception("Error in /v1/map-infrastructure endpoint: %s", exc) - _inc("errors_total"); return jsonify({"error": "Internal server error"}), 500 + _inc("errors_total") + return jsonify({"error": "Internal server error"}), 500 return app # ← returned for testing diff --git a/requirements.txt b/requirements.txt index a862310..65bc4ab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ python-json-logger defusedxml pytest maxminddb +python-nmap