Skip to content
21 changes: 7 additions & 14 deletions collectors/nmap_recon.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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'
Expand Down
39 changes: 35 additions & 4 deletions collectors/sqlmap_exploit.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import time

import requests

class SQLMapAPI:
Expand Down Expand Up @@ -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)
Expand All @@ -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}
16 changes: 15 additions & 1 deletion fuzz/fuzz_api_inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 39 additions & 15 deletions hancock_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 (
Expand All @@ -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
Expand All @@ -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

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ python-json-logger
defusedxml
pytest
maxminddb
python-nmap
Loading