From 22a390227e8265e271036ae254a046f06d047389 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 04:07:28 +0000 Subject: [PATCH 1/8] Fix code review issues: nmap sys.exit, sqlmap tight loops, fuzz app ref, semicolons, get_json silent, k8s annotation, deps Agent-Logs-Url: https://github.com/0ai-Cyberviser/Hancock/sessions/b8a89df3-1eb3-4848-a39e-8c5cad791df8 Co-authored-by: 0ai-Cyberviser <266508493+0ai-Cyberviser@users.noreply.github.com> --- collectors/nmap_recon.py | 11 ++------- collectors/sqlmap_exploit.py | 4 ++++ deploy/k8s/service.yaml | 1 + fuzz/fuzz_api_inputs.py | 16 ++++++++++++- hancock_agent.py | 45 ++++++++++++++++++++++++------------ requirements.txt | 1 + 6 files changed, 53 insertions(+), 25 deletions(-) diff --git a/collectors/nmap_recon.py b/collectors/nmap_recon.py index 9776421..7cde3c6 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,7 +23,7 @@ def run_scan(self): logging.info('Scan completed successfully') except Exception as e: logging.error(f'Scan failed for {self.target}: {str(e)}') - sys.exit(1) + raise RuntimeError(f'Scan failed for {self.target}: {str(e)}') from e def parse_xml_to_json(self): try: @@ -63,7 +56,7 @@ def parse_xml_to_json(self): logging.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) + 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..e384af3 100644 --- a/collectors/sqlmap_exploit.py +++ b/collectors/sqlmap_exploit.py @@ -1,3 +1,5 @@ +import time + import requests class SQLMapAPI: @@ -39,6 +41,7 @@ def main(): status = api.get_task_status(task_id) while status['status'] not in ['terminated', 'failed']: print(f'Task status: {status["status"]}') + time.sleep(1) status = api.get_task_status(task_id) # Fetch results @@ -63,6 +66,7 @@ def run_sqlmap(target_url: str, api_url: str = "http://localhost:8775/") -> dict status = api.get_task_status(task_id) while status.get("status") not in ("terminated", "failed"): + time.sleep(1) status = api.get_task_status(task_id) results = api.get_results(task_id) diff --git a/deploy/k8s/service.yaml b/deploy/k8s/service.yaml index 510ca40..e00302a 100644 --- a/deploy/k8s/service.yaml +++ b/deploy/k8s/service.yaml @@ -9,6 +9,7 @@ metadata: annotations: prometheus.io/scrape: "true" prometheus.io/port: "5000" + prometheus.io/path: "/metrics" spec: type: ClusterIP selector: 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..d032040 100644 --- a/hancock_agent.py +++ b/hancock_agent.py @@ -985,13 +985,17 @@ 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) or {} 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 +1008,25 @@ 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) or {} 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 +1052,25 @@ 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) or {} 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 +1079,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 From cbf1cf4bc36963ddc808cf57ae24665a328beca3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 04:29:42 +0000 Subject: [PATCH 2/8] Use module logger in NmapRecon class; add timeout guards to sqlmap polling loops Agent-Logs-Url: https://github.com/0ai-Cyberviser/Hancock/sessions/4d708d66-94e6-42a4-8281-94e3aebd976d Co-authored-by: 0ai-Cyberviser <266508493+0ai-Cyberviser@users.noreply.github.com> --- collectors/nmap_recon.py | 10 +++++----- collectors/sqlmap_exploit.py | 21 ++++++++++++++++++--- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/collectors/nmap_recon.py b/collectors/nmap_recon.py index 7cde3c6..9b9ed0b 100644 --- a/collectors/nmap_recon.py +++ b/collectors/nmap_recon.py @@ -18,11 +18,11 @@ 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)}') + 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): @@ -53,9 +53,9 @@ 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)}') + logger.error('Failed to parse XML: %s', e) raise RuntimeError(f'Failed to parse XML: {str(e)}') from e if __name__ == '__main__': diff --git a/collectors/sqlmap_exploit.py b/collectors/sqlmap_exploit.py index e384af3..e591829 100644 --- a/collectors/sqlmap_exploit.py +++ b/collectors/sqlmap_exploit.py @@ -37,12 +37,18 @@ 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']: + for _ in range(_MAX_POLLS): + if status['status'] in ['terminated', 'failed']: + break print(f'Task status: {status["status"]}') time.sleep(1) status = api.get_task_status(task_id) + else: + print('Timed out waiting for task to complete') + return # Fetch results results = api.get_results(task_id) @@ -64,10 +70,19 @@ 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) return {"returncode": 0, "result": results, "target": target_url} From b33e7bbeb4b4f6b1711ad15e3e7802c390a7325f Mon Sep 17 00:00:00 2001 From: 0ai <0ai@cyberviserai.com> Date: Sun, 5 Apr 2026 04:03:05 -0500 Subject: [PATCH 3/8] Update collectors/sqlmap_exploit.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: 0ai <0ai@cyberviserai.com> --- collectors/sqlmap_exploit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/collectors/sqlmap_exploit.py b/collectors/sqlmap_exploit.py index e591829..4c61dfd 100644 --- a/collectors/sqlmap_exploit.py +++ b/collectors/sqlmap_exploit.py @@ -48,7 +48,7 @@ def main(): status = api.get_task_status(task_id) else: print('Timed out waiting for task to complete') - return + raise SystemExit(1) # Fetch results results = api.get_results(task_id) From b9778bd254dd5dc274247082695fb06cbbcf1200 Mon Sep 17 00:00:00 2001 From: 0ai <0ai@cyberviserai.com> Date: Sun, 5 Apr 2026 04:46:21 -0500 Subject: [PATCH 4/8] Update collectors/sqlmap_exploit.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: 0ai <0ai@cyberviserai.com> --- collectors/sqlmap_exploit.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/collectors/sqlmap_exploit.py b/collectors/sqlmap_exploit.py index 4c61dfd..34251c4 100644 --- a/collectors/sqlmap_exploit.py +++ b/collectors/sqlmap_exploit.py @@ -85,6 +85,14 @@ def run_sqlmap(target_url: str, api_url: str = "http://localhost:8775/") -> dict } 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} From 04fee743f06bd3f7ffacb0c375c882be05501065 Mon Sep 17 00:00:00 2001 From: 0ai <0ai@cyberviserai.com> Date: Sun, 5 Apr 2026 04:59:00 -0500 Subject: [PATCH 5/8] Update collectors/sqlmap_exploit.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: 0ai <0ai@cyberviserai.com> --- collectors/sqlmap_exploit.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/collectors/sqlmap_exploit.py b/collectors/sqlmap_exploit.py index 34251c4..27d8082 100644 --- a/collectors/sqlmap_exploit.py +++ b/collectors/sqlmap_exploit.py @@ -41,9 +41,13 @@ def main(): _MAX_POLLS = 600 status = api.get_task_status(task_id) for _ in range(_MAX_POLLS): - if status['status'] in ['terminated', 'failed']: + 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: {status["status"]}') + print(f'Task status: {task_status}') time.sleep(1) status = api.get_task_status(task_id) else: From d41f12f7167c5f3dd7add3f428074b0561541222 Mon Sep 17 00:00:00 2001 From: 0ai <0ai@cyberviserai.com> Date: Sun, 5 Apr 2026 04:59:09 -0500 Subject: [PATCH 6/8] Update hancock_agent.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: 0ai <0ai@cyberviserai.com> --- hancock_agent.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hancock_agent.py b/hancock_agent.py index d032040..4ed0240 100644 --- a/hancock_agent.py +++ b/hancock_agent.py @@ -991,7 +991,10 @@ def geolocate_endpoint(): _inc("requests_by_endpoint", "/v1/geolocate") _inc("requests_by_mode", "osint") - data = request.get_json(force=True, silent=True) or {} + 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") From 360b38622f43b0ee9c60040cb78721f8f0c7d544 Mon Sep 17 00:00:00 2001 From: 0ai <0ai@cyberviserai.com> Date: Sun, 5 Apr 2026 04:59:22 -0500 Subject: [PATCH 7/8] Update hancock_agent.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: 0ai <0ai@cyberviserai.com> --- hancock_agent.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hancock_agent.py b/hancock_agent.py index 4ed0240..4abee5b 100644 --- a/hancock_agent.py +++ b/hancock_agent.py @@ -1069,7 +1069,10 @@ def map_infrastructure_endpoint(): _inc("requests_by_endpoint", "/v1/map-infrastructure") _inc("requests_by_mode", "osint") - data = request.get_json(force=True, silent=True) or {} + 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") From c49d8d524d5486afc47c04927352cb54018c0927 Mon Sep 17 00:00:00 2001 From: 0ai <0ai@cyberviserai.com> Date: Sun, 5 Apr 2026 05:03:36 -0500 Subject: [PATCH 8/8] Update hancock_agent.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: 0ai <0ai@cyberviserai.com> --- hancock_agent.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hancock_agent.py b/hancock_agent.py index 4abee5b..f58b2c7 100644 --- a/hancock_agent.py +++ b/hancock_agent.py @@ -1025,7 +1025,10 @@ def predict_locations_endpoint(): _inc("requests_by_endpoint", "/v1/predict-locations") _inc("requests_by_mode", "osint") - data = request.get_json(force=True, silent=True) or {} + 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")