mirror of
https://github.com/FunnyWolf/agentic-soc-platform.git
synced 2026-08-22 13:12:56 +02:00
Fix code scanning exception exposure alerts
Sanitize API-facing exception details and replace OpenCTI pattern regex with linear parsing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -32,8 +32,8 @@ def ldap_authenticates(username, password, config=None):
|
||||
try:
|
||||
from ldap3 import SUBTREE, Connection, Server
|
||||
from ldap3.core.exceptions import LDAPException
|
||||
except ImportError as exc:
|
||||
logger.warning("LDAP login failed for %s: ldap3 is not installed", username, exc_info=exc)
|
||||
except ImportError:
|
||||
logger.warning("LDAP login failed for %s: ldap3 is not installed", username, exc_info=True)
|
||||
return False
|
||||
|
||||
connections = []
|
||||
@@ -146,8 +146,9 @@ def test_ldap_config(config, *, test_username="", test_password=""):
|
||||
try:
|
||||
from ldap3 import Connection, Server
|
||||
from ldap3.core.exceptions import LDAPException
|
||||
except ImportError as exc:
|
||||
return {"success": False, "detail": f"ldap3 is not installed: {exc}", "response_preview": ""}
|
||||
except ImportError:
|
||||
logger.warning("LDAP test failed: ldap3 is not installed", exc_info=True)
|
||||
return {"success": False, "detail": "LDAP support is not installed on the server.", "response_preview": ""}
|
||||
|
||||
try:
|
||||
server = Server(config["server_uri"])
|
||||
@@ -158,5 +159,6 @@ def test_ldap_config(config, *, test_username="", test_password=""):
|
||||
conn = Connection(server, auto_bind=True, **bind_kwargs)
|
||||
conn.unbind()
|
||||
return {"success": True, "detail": "LDAP bind succeeded.", "response_preview": ""}
|
||||
except LDAPException as exc:
|
||||
return {"success": False, "detail": f"LDAP bind failed: {exc}", "response_preview": ""}
|
||||
except LDAPException:
|
||||
logger.warning("LDAP bind test failed", exc_info=True)
|
||||
return {"success": False, "detail": "LDAP bind failed.", "response_preview": ""}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
import logging
|
||||
import mimetypes
|
||||
|
||||
from django.conf import settings
|
||||
@@ -54,6 +55,7 @@ from .utils import bool_param, list_param, parse_tags, parse_timezone_aware_date
|
||||
API_VERSION = "v1"
|
||||
MIN_CLI_VERSION = "0.1.0"
|
||||
SERVER_VERSION = "0.5.0"
|
||||
logger = logging.getLogger(__name__)
|
||||
FOUNDATION_CAPABILITIES = [
|
||||
"agent.version",
|
||||
"case.list",
|
||||
@@ -448,7 +450,8 @@ class PlaybookRunView(APIView):
|
||||
user_input=request.data.get("user_input", ""),
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise ValidationError({"detail": str(exc)}) from exc
|
||||
logger.info("Invalid agent playbook run request", exc_info=True)
|
||||
raise ValidationError({"detail": "Unknown playbook definition."}) from exc
|
||||
return agent_response(request, operation="playbook.run", data=serialize_playbook(playbook), status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
@@ -521,7 +524,8 @@ class ThreatIntelQueryView(APIView):
|
||||
provider=provider,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise ValidationError({"detail": str(exc)}) from exc
|
||||
logger.info("Invalid agent threat intelligence query", exc_info=True)
|
||||
raise ValidationError({"detail": "Invalid threat intelligence query."}) from exc
|
||||
return agent_response(request, operation="ti.query", data=_dump(result))
|
||||
|
||||
|
||||
@@ -541,7 +545,8 @@ class CMDBLookupView(APIView):
|
||||
provider=provider,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise ValidationError({"detail": str(exc)}) from exc
|
||||
logger.info("Invalid agent CMDB lookup request", exc_info=True)
|
||||
raise ValidationError({"detail": "Invalid CMDB lookup request."}) from exc
|
||||
return agent_response(request, operation="cmdb.lookup", data=_dump(result))
|
||||
|
||||
|
||||
|
||||
@@ -66,8 +66,9 @@ def scan_module_definitions(*, scripts_dir=None, scripts_dirs=None):
|
||||
for path in paths:
|
||||
try:
|
||||
definition = _definition_from_script(path)
|
||||
except Exception as exc:
|
||||
errors.append({"path": str(path), "error": f"{type(exc).__name__}: {exc}"})
|
||||
except Exception:
|
||||
logger.exception("Failed to load module definition from %s", path)
|
||||
errors.append({"path": str(path), "error": "Failed to load module definition."})
|
||||
continue
|
||||
if definition is not None:
|
||||
definitions.append(definition)
|
||||
|
||||
@@ -101,14 +101,15 @@ def _module_record_with_stream_health(definition, *, redis_client=None):
|
||||
record = _module_record(definition)
|
||||
try:
|
||||
record["stream_health"] = _stream_health(definition.stream_name, redis_client=redis_client)
|
||||
except redis.RedisError as exc:
|
||||
except redis.RedisError:
|
||||
logger.exception("Failed to read module stream health for %s", definition.stream_name)
|
||||
record["stream_health"] = {
|
||||
"available": False,
|
||||
"length": 0,
|
||||
"first_id": "",
|
||||
"last_id": "",
|
||||
"groups": [],
|
||||
"warning": f"{type(exc).__name__}: {exc}",
|
||||
"warning": "Stream health is unavailable.",
|
||||
}
|
||||
return record
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
@@ -9,6 +10,8 @@ from apps.agentic.runtime.loader import discover_script_class, iter_overlaid_pyt
|
||||
from apps.inbox.notifications import notify_playbook_completion
|
||||
from apps.playbooks.models import Playbook, PlaybookJobStatus
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def default_playbook_scripts_dir():
|
||||
return Path(settings.BASE_DIR) / "playbooks"
|
||||
@@ -57,8 +60,9 @@ def scan_playbook_definitions(*, scripts_dir=None, scripts_dirs=None):
|
||||
class_name="Playbook",
|
||||
base_class=BasePlaybook,
|
||||
)
|
||||
except Exception as exc:
|
||||
errors.append({"path": str(path), "error": f"{type(exc).__name__}: {exc}"})
|
||||
except Exception:
|
||||
logger.exception("Failed to load playbook definition from %s", path)
|
||||
errors.append({"path": str(path), "error": "Failed to load playbook definition."})
|
||||
continue
|
||||
if definition is not None:
|
||||
definitions.append(definition)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import logging
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework import viewsets, permissions, status
|
||||
@@ -14,6 +16,8 @@ from apps.common.advanced_filters import AdvancedFilterBackend
|
||||
from .models import Playbook
|
||||
from .serializers import PlaybookSerializer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PlaybookViewSet(AuditActorMixin, viewsets.ModelViewSet):
|
||||
queryset = Playbook.objects.select_related("user", "case")
|
||||
@@ -63,7 +67,8 @@ class PlaybookViewSet(AuditActorMixin, viewsets.ModelViewSet):
|
||||
user=request.user,
|
||||
user_input=user_input,
|
||||
)
|
||||
except ValueError as exc:
|
||||
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
except ValueError:
|
||||
logger.info("Invalid playbook run request", exc_info=True)
|
||||
return Response({"detail": "Unknown playbook definition."}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
return Response(self.get_serializer(playbook).data, status=status.HTTP_201_CREATED)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import logging
|
||||
|
||||
import httpx
|
||||
from pycti import OpenCTIApiClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _chat_completions_url(base_url):
|
||||
normalized = base_url.rstrip("/")
|
||||
@@ -57,10 +61,11 @@ def test_llm_provider(config):
|
||||
"detail": f"LLM provider test failed with HTTP {response.status_code}.",
|
||||
"response_preview": _redact(response.text, [api_key])[:500],
|
||||
}
|
||||
except Exception as exc:
|
||||
except Exception:
|
||||
logger.exception("LLM provider test failed")
|
||||
return {
|
||||
"success": False,
|
||||
"detail": _redact(exc, [api_key]),
|
||||
"detail": "LLM provider test failed due to a connection error.",
|
||||
"response_preview": "",
|
||||
}
|
||||
|
||||
@@ -98,10 +103,11 @@ def test_alienvault_otx_config(config):
|
||||
"detail": f"AlienVault OTX test failed with HTTP {response.status_code}.",
|
||||
"response_preview": _redact(response.text, [api_key])[:500],
|
||||
}
|
||||
except Exception as exc:
|
||||
except Exception:
|
||||
logger.exception("AlienVault OTX configuration test failed")
|
||||
return {
|
||||
"success": False,
|
||||
"detail": _redact(exc, [api_key]),
|
||||
"detail": "AlienVault OTX test failed due to a connection error.",
|
||||
"response_preview": "",
|
||||
}
|
||||
|
||||
@@ -159,10 +165,11 @@ def test_opencti_config(config):
|
||||
"detail": "OpenCTI responded successfully.",
|
||||
"response_preview": str(preview)[:500],
|
||||
}
|
||||
except Exception as exc:
|
||||
except Exception:
|
||||
logger.exception("OpenCTI configuration test failed")
|
||||
return {
|
||||
"success": False,
|
||||
"detail": _redact(exc, [token]),
|
||||
"detail": "OpenCTI test failed due to a connection error.",
|
||||
"response_preview": "",
|
||||
}
|
||||
|
||||
@@ -186,10 +193,11 @@ def test_splunk_config(config):
|
||||
"detail": "Splunk responded successfully.",
|
||||
"response_preview": str({key: info.get(key) for key in ("serverName", "version", "guid")})[:500],
|
||||
}
|
||||
except Exception as exc:
|
||||
except Exception:
|
||||
logger.exception("Splunk configuration test failed")
|
||||
return {
|
||||
"success": False,
|
||||
"detail": _redact(exc, [password]),
|
||||
"detail": "Splunk test failed due to a connection error.",
|
||||
"response_preview": "",
|
||||
}
|
||||
|
||||
@@ -213,9 +221,10 @@ def test_elk_config(config):
|
||||
"version": (info.get("version") or {}).get("number") if isinstance(info.get("version"), dict) else "",
|
||||
})[:500],
|
||||
}
|
||||
except Exception as exc:
|
||||
except Exception:
|
||||
logger.exception("ELK configuration test failed")
|
||||
return {
|
||||
"success": False,
|
||||
"detail": _redact(exc, [api_key]),
|
||||
"detail": "ELK test failed due to a connection error.",
|
||||
"response_preview": "",
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import transaction
|
||||
@@ -33,6 +35,8 @@ from .serializers import (
|
||||
)
|
||||
from .services import test_alienvault_otx_config, test_elk_config, test_llm_provider, test_opencti_config, test_splunk_config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
LLM_AUDIT_FIELDS = ("name", "base_url", "model", "proxy", "tags", "enabled", "priority", "api_key")
|
||||
OTX_AUDIT_FIELDS = ("enabled", "api_key", "base_url", "proxy")
|
||||
OPENCTI_AUDIT_FIELDS = ("enabled", "url", "token", "ssl_verify", "proxy")
|
||||
@@ -98,8 +102,9 @@ def _run_config_test(operation, func, config):
|
||||
config,
|
||||
timeout_seconds=settings.CONFIG_TEST_TIMEOUT_SECONDS,
|
||||
)
|
||||
except OperationTimeoutError as exc:
|
||||
return {"success": False, "detail": str(exc), "response_preview": ""}
|
||||
except OperationTimeoutError:
|
||||
logger.warning("Configuration test timed out: %s", operation, exc_info=True)
|
||||
return {"success": False, "detail": "Configuration test timed out.", "response_preview": ""}
|
||||
|
||||
|
||||
def _config_from_instance(instance, values):
|
||||
|
||||
@@ -198,7 +198,7 @@ class WebhookAPITests(SimpleTestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 503)
|
||||
self.assertEqual(response.json()["detail"], "Failed to write Redis stream search: RuntimeError")
|
||||
self.assertEqual(response.json()["detail"], "Webhook stream service is unavailable.")
|
||||
|
||||
def test_old_agentic_forwarder_route_is_removed(self):
|
||||
with self.assertRaises(Resolver404):
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import logging
|
||||
|
||||
from pydantic import ValidationError
|
||||
from rest_framework import permissions, status
|
||||
from rest_framework.response import Response
|
||||
@@ -9,6 +11,10 @@ from apps.webhook.service import (
|
||||
handle_splunk_webhook,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
INVALID_WEBHOOK_PAYLOAD_DETAIL = "Invalid webhook payload."
|
||||
WEBHOOK_STREAM_UNAVAILABLE_DETAIL = "Webhook stream service is unavailable."
|
||||
|
||||
|
||||
class SplunkWebhookView(APIView):
|
||||
authentication_classes = []
|
||||
@@ -17,10 +23,12 @@ class SplunkWebhookView(APIView):
|
||||
def post(self, request):
|
||||
try:
|
||||
result = handle_splunk_webhook(request.data)
|
||||
except (ValidationError, ValueError) as exc:
|
||||
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
except WebhookRedisError as exc:
|
||||
return Response({"detail": str(exc)}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
|
||||
except (ValidationError, ValueError):
|
||||
logger.info("Invalid Splunk webhook payload", exc_info=True)
|
||||
return Response({"detail": INVALID_WEBHOOK_PAYLOAD_DETAIL}, status=status.HTTP_400_BAD_REQUEST)
|
||||
except WebhookRedisError:
|
||||
logger.exception("Failed to process Splunk webhook")
|
||||
return Response({"detail": WEBHOOK_STREAM_UNAVAILABLE_DETAIL}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
|
||||
return Response(result.model_dump(), status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@@ -31,8 +39,10 @@ class KibanaWebhookView(APIView):
|
||||
def post(self, request):
|
||||
try:
|
||||
result = handle_kibana_webhook(request.data)
|
||||
except (ValidationError, ValueError) as exc:
|
||||
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
except WebhookRedisError as exc:
|
||||
return Response({"detail": str(exc)}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
|
||||
except (ValidationError, ValueError):
|
||||
logger.info("Invalid Kibana webhook payload", exc_info=True)
|
||||
return Response({"detail": INVALID_WEBHOOK_PAYLOAD_DETAIL}, status=status.HTTP_400_BAD_REQUEST)
|
||||
except WebhookRedisError:
|
||||
logger.exception("Failed to process Kibana webhook")
|
||||
return Response({"detail": WEBHOOK_STREAM_UNAVAILABLE_DETAIL}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
|
||||
return Response(result.model_dump(), status=status.HTTP_200_OK)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import logging
|
||||
|
||||
from integrations.cmdb.models import CMDBQueryOutput
|
||||
from integrations.cmdb.providers import get_providers
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def list_providers():
|
||||
return list(get_providers().keys())
|
||||
@@ -25,8 +29,9 @@ def lookup_artifact_context(artifact_type, artifact_value, provider=None):
|
||||
results.append(result)
|
||||
if result.error:
|
||||
errors.append(f"[{provider_name}] {result.error}")
|
||||
except Exception as exc:
|
||||
errors.append(f"[{provider_name}] {type(exc).__name__}: {exc}")
|
||||
except Exception:
|
||||
logger.exception("CMDB provider lookup failed: %s", provider_name)
|
||||
errors.append(f"[{provider_name}] Provider lookup failed.")
|
||||
|
||||
return CMDBQueryOutput(
|
||||
artifact_type=artifact_type,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
@@ -6,6 +7,8 @@ import yaml
|
||||
from integrations.siem.models import IndexInfo, SchemaFieldInfo
|
||||
from asp import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CUSTOM_REGISTRY_DIR = Path(settings.CUSTOM_DIR) / "data" / "siem"
|
||||
|
||||
|
||||
@@ -54,8 +57,9 @@ def scan_registry_configs():
|
||||
for yaml_file in _iter_overlaid_yaml_files(*default_registry_dirs()):
|
||||
try:
|
||||
index_info = _load_yaml_file(yaml_file)
|
||||
except Exception as exc:
|
||||
errors.append({"path": str(yaml_file), "error": f"{type(exc).__name__}: {exc}"})
|
||||
except Exception:
|
||||
logger.exception("Failed to load SIEM registry config from %s", yaml_file)
|
||||
errors.append({"path": str(yaml_file), "error": "Failed to load SIEM registry config."})
|
||||
continue
|
||||
fields = [field.model_dump() for field in index_info.fields]
|
||||
indices.append({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import ipaddress
|
||||
import re
|
||||
from urllib.parse import quote
|
||||
@@ -45,6 +46,8 @@ OPENCTI_UNSUPPORTED_TYPES = {
|
||||
ArtifactType.SCRIPT_CONTENT,
|
||||
}
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseThreatIntelProvider:
|
||||
name = ""
|
||||
@@ -135,8 +138,9 @@ class OpenCTIProvider(BaseThreatIntelProvider):
|
||||
matches = self._find_matches(client, indicator, plan)
|
||||
context = self._load_context(client, matches)
|
||||
return self._summarize(indicator, artifact_type, plan, matches, context)
|
||||
except Exception as exc:
|
||||
return self._error(indicator, artifact_type, _redact(str(exc), [self.token]), indicator_type=plan.get("indicator_type"))
|
||||
except Exception:
|
||||
logger.exception("OpenCTI query failed")
|
||||
return self._error(indicator, artifact_type, "OpenCTI query failed.", indicator_type=plan.get("indicator_type"))
|
||||
|
||||
def _client(self):
|
||||
if self.client is not None:
|
||||
@@ -251,9 +255,8 @@ class OpenCTIProvider(BaseThreatIntelProvider):
|
||||
|
||||
@staticmethod
|
||||
def _pattern_contains_value(pattern, indicator):
|
||||
values = re.findall(r"'((?:\\.|[^'])*)'", pattern)
|
||||
lowered = indicator.lower()
|
||||
for value in values:
|
||||
for value in _single_quoted_values(pattern):
|
||||
unescaped = value.replace("\\'", "'").replace("\\\\", "\\")
|
||||
if unescaped.lower() == lowered:
|
||||
return True
|
||||
@@ -565,11 +568,14 @@ class AlienVaultOTXProvider(BaseThreatIntelProvider):
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except httpx.HTTPStatusError as exc:
|
||||
return {"error": f"OTX HTTP {exc.response.status_code}: {exc.response.text}"}
|
||||
except httpx.HTTPError as exc:
|
||||
return {"error": f"OTX request failed: {type(exc).__name__}: {exc}"}
|
||||
except ValueError as exc:
|
||||
return {"error": f"OTX response is not valid JSON: {exc}"}
|
||||
logger.info("OTX request returned HTTP %s", exc.response.status_code)
|
||||
return {"error": f"OTX HTTP {exc.response.status_code}."}
|
||||
except httpx.HTTPError:
|
||||
logger.exception("OTX request failed")
|
||||
return {"error": "OTX request failed."}
|
||||
except ValueError:
|
||||
logger.exception("OTX response is not valid JSON")
|
||||
return {"error": "OTX response is not valid JSON."}
|
||||
|
||||
def summarize_result(self, attributes, indicator_type, indicator):
|
||||
if attributes.get("error"):
|
||||
@@ -744,6 +750,32 @@ def _looks_like_url(value):
|
||||
return "." in value and "/" in value
|
||||
|
||||
|
||||
def _single_quoted_values(value):
|
||||
values = []
|
||||
current = []
|
||||
in_quote = False
|
||||
escaped = False
|
||||
for char in value:
|
||||
if not in_quote:
|
||||
if char == "'":
|
||||
in_quote = True
|
||||
current = []
|
||||
continue
|
||||
if escaped:
|
||||
current.append(f"\\{char}")
|
||||
escaped = False
|
||||
continue
|
||||
if char == "\\":
|
||||
escaped = True
|
||||
continue
|
||||
if char == "'":
|
||||
values.append("".join(current))
|
||||
in_quote = False
|
||||
continue
|
||||
current.append(char)
|
||||
return values
|
||||
|
||||
|
||||
def _redact(value, secrets):
|
||||
redacted = str(value)
|
||||
for secret in secrets:
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import logging
|
||||
|
||||
from integrations.threat_intel.models import TIQueryOutput
|
||||
from integrations.threat_intel.providers import get_providers
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
RISK_PRIORITY = {"high": 3, "medium": 2, "low": 1, None: 0}
|
||||
|
||||
|
||||
@@ -35,8 +39,9 @@ def query_indicator(indicator, *, artifact_type, provider=None):
|
||||
results.append(result)
|
||||
if result.error:
|
||||
errors.append(f"[{provider_name}] {result.error}")
|
||||
except Exception as exc:
|
||||
errors.append(f"[{provider_name}] {type(exc).__name__}: {exc}")
|
||||
except Exception:
|
||||
logger.exception("Threat intelligence provider query failed: %s", provider_name)
|
||||
errors.append(f"[{provider_name}] Provider query failed.")
|
||||
|
||||
return TIQueryOutput(
|
||||
indicator=indicator,
|
||||
|
||||
Reference in New Issue
Block a user