Skip to content

Commit cd875b0

Browse files
committed
fix: address Codacy issues from PR #105
- Bandit B104: bind Prometheus exporter to 127.0.0.1 by default, document the explicit 0.0.0.0 opt-in for container/remote setups. - Bandit B310: validate InfluxDB HTTP URL scheme is http(s) before urlopen, both at start_influxdb_sink configuration time and at every send. Annotate the urlopen with nosec B310 + rationale. - Bandit B110: replace bare 'except Exception: pass' cleanup swallowers across the metrics exporters (Prometheus, InfluxDB, OpenTelemetry) and the WebSocket / MQTT / gRPC user templates with debug-level log lines so cleanup failures stay observable. - Semgrep non-literal-import: validate the dotted path against a strict identifier regex before importlib.import_module in the gRPC user, and tag the call with nosemgrep + rationale (the framework genuinely needs to load operator-authored stubs). - pyflakes F401: drop the unused Callable import from parameter_resolver.py. Bandit run on the touched modules now reports zero findings; pytest test/ still passes (84 cases).
1 parent 9a26900 commit cd875b0

7 files changed

Lines changed: 49 additions & 21 deletions

File tree

je_load_density/utils/metrics/influxdb_sink.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,18 @@ def _send_udp(line: str, host: str, port: int) -> None:
5050
sock.close()
5151

5252

53+
_ALLOWED_HTTP_SCHEMES = ("http://", "https://")
54+
55+
5356
def _send_http(line: str, url: str, token: Optional[str], timeout: float) -> None:
57+
if not url.lower().startswith(_ALLOWED_HTTP_SCHEMES):
58+
raise ValueError("InfluxDB HTTP URL must use http:// or https://")
5459
headers = {"Content-Type": "text/plain; charset=utf-8"}
5560
if token:
5661
headers["Authorization"] = f"Token {token}"
5762
req = urllib_request.Request(url, data=line.encode("utf-8"), headers=headers, method="POST")
5863
try:
59-
with urllib_request.urlopen(req, timeout=timeout) as response: # noqa: S310 - configurable URL
64+
with urllib_request.urlopen(req, timeout=timeout) as response: # nosec B310 - scheme validated above
6065
response.read()
6166
except urllib_error.URLError as error:
6267
load_density_logger.warning(f"InfluxDB HTTP write failed: {error}")
@@ -82,8 +87,11 @@ def start_influxdb_sink(
8287
transport = transport.lower()
8388
if transport not in {"udp", "http"}:
8489
raise ValueError(f"unsupported transport: {transport}")
85-
if transport == "http" and not url:
86-
raise ValueError("url required when transport=http")
90+
if transport == "http":
91+
if not url:
92+
raise ValueError("url required when transport=http")
93+
if not url.lower().startswith(_ALLOWED_HTTP_SCHEMES):
94+
raise ValueError("InfluxDB HTTP URL must use http:// or https://")
8795

8896
with _lock:
8997
if _state["started"]:
@@ -131,8 +139,8 @@ def stop_influxdb_sink() -> None:
131139
return
132140
try:
133141
events.request.remove_listener(_state["listener"])
134-
except Exception:
135-
pass
142+
except Exception as error:
143+
load_density_logger.debug(f"influxdb listener detach failed: {error!r}")
136144
_state["started"] = False
137145
_state["listener"] = None
138146
_state["config"] = None

je_load_density/utils/metrics/opentelemetry_exporter.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,14 @@ def stop_opentelemetry_exporter() -> None:
103103
return
104104
try:
105105
events.request.remove_listener(_state["listener"])
106-
except Exception:
107-
pass
106+
except Exception as error:
107+
load_density_logger.debug(f"otel listener detach failed: {error!r}")
108108
provider = _state.get("provider")
109109
if provider is not None:
110110
try:
111111
provider.shutdown()
112-
except Exception:
113-
pass
112+
except Exception as error:
113+
load_density_logger.debug(f"otel provider shutdown failed: {error!r}")
114114
_state["started"] = False
115115
_state["listener"] = None
116116
_state["instruments"] = None

je_load_density/utils/metrics/prometheus_exporter.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,15 @@ def _build_metrics():
3737
}
3838

3939

40-
def start_prometheus_exporter(port: int = 9646, addr: str = "0.0.0.0") -> Optional[int]:
40+
def start_prometheus_exporter(port: int = 9646, addr: str = "127.0.0.1") -> Optional[int]:
4141
"""
4242
啟動 Prometheus 指標伺服器並掛上 Locust request 事件監聽。
4343
Start the Prometheus exporter HTTP server and attach a request listener.
4444
45+
Defaults to binding on loopback. Pass ``addr="0.0.0.0"`` explicitly
46+
to expose the exporter to the network when running in a container
47+
or remote node.
48+
4549
Returns the port the exporter is listening on, or None if the optional
4650
dependency is not installed.
4751
"""
@@ -81,7 +85,7 @@ def stop_prometheus_exporter() -> None:
8185
return
8286
try:
8387
events.request.remove_listener(_state["listener"])
84-
except Exception:
85-
pass
88+
except Exception as error:
89+
load_density_logger.debug(f"prometheus listener detach failed: {error!r}")
8690
_state["started"] = False
8791
_state["listener"] = None

je_load_density/utils/parameterization/parameter_resolver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import os
44
import re
55
import threading
6-
from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional
6+
from typing import Any, Dict, Iterable, Iterator, List, Optional
77

88
_PLACEHOLDER_PATTERN = re.compile(r"\$\{([^}]+)\}")
99
_FUNCTION_PATTERN = re.compile(r"^([a-zA-Z_][a-zA-Z0-9_]*)\((.*)\)$")

je_load_density/wrapper/user_template/grpc_user_template.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import importlib
2+
import re
23
import time
34
from typing import Any, Dict, Tuple
45

@@ -27,10 +28,25 @@ def set_wrapper_grpc_user(user_detail_dict: Dict[str, Any], **kwargs) -> type:
2728
return GrpcUserWrapper
2829

2930

31+
_SAFE_DOTTED_PATH = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$")
32+
33+
3034
def _import_dotted(path: str) -> Any:
35+
"""
36+
Resolve a dotted ``module.attr`` path supplied in a load-test scenario.
37+
38+
The gRPC user genuinely needs to load operator-authored stub
39+
modules at runtime, so a static import literal is not viable. We
40+
accept this by validating that ``path`` is a syntactically safe
41+
Python identifier chain (no separators, no relative dots, no
42+
dunders bridged via traversal) before delegating to importlib.
43+
"""
44+
if not isinstance(path, str) or not _SAFE_DOTTED_PATH.match(path):
45+
raise ImportError(f"invalid dotted import path: {path!r}")
3146
module_name, _, attr = path.rpartition(".")
3247
if not module_name:
3348
raise ImportError(f"invalid dotted import path: {path!r}")
49+
# nosemgrep: python.lang.security.audit.non-literal-import.non-literal-import
3450
module = importlib.import_module(module_name)
3551
return getattr(module, attr)
3652

@@ -74,8 +90,8 @@ def _ensure_channel(self, target: str):
7490
if self._channel is not None:
7591
try:
7692
self._channel.close()
77-
except Exception:
78-
pass
93+
except Exception as error:
94+
load_density_logger.debug(f"grpc channel close before reconnect failed: {error!r}")
7995
self._channel = grpc.insecure_channel(target)
8096
self._target = target
8197
return self._channel

je_load_density/wrapper/user_template/mqtt_user_template.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ def _ensure_client(self, broker: str, step: Dict[str, Any]):
7171
if self._client is not None:
7272
try:
7373
self._client.disconnect()
74-
except Exception:
75-
pass
74+
except Exception as error:
75+
load_density_logger.debug(f"mqtt disconnect before reconnect failed: {error!r}")
7676

7777
client_id = step.get("client_id") or f"loaddensity-{secrets.token_hex(4)}"
7878
client = paho_client.Client(client_id=client_id, clean_session=True)
@@ -165,6 +165,6 @@ def on_stop(self) -> None:
165165
try:
166166
self._client.loop_stop()
167167
self._client.disconnect()
168-
except Exception:
169-
pass
168+
except Exception as error:
169+
load_density_logger.debug(f"mqtt on_stop cleanup failed: {error!r}")
170170
self._client = None

je_load_density/wrapper/user_template/websocket_user_template.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ def _connect(self, url: str, timeout: float) -> None:
6767
if self._ws is not None:
6868
try:
6969
self._ws.close()
70-
except Exception:
71-
pass
70+
except Exception as error:
71+
load_density_logger.debug(f"websocket close before reconnect failed: {error!r}")
7272
self._ws = create_connection(url, timeout=timeout)
7373
self._url = url
7474

0 commit comments

Comments
 (0)