Coverage for src / openenv / core / security.py: 96.23%
33 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-25 13:36 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-25 13:36 +0000
1"""Security posture helpers for secure-by-default with explicit override support."""
3from __future__ import annotations
5from collections.abc import Mapping
7from openenv.core.models import Manifest
10_WILDCARD_TOOL_NAMES = {"*", "all"}
11_LOOPBACK_BINDS = {"127.0.0.1", "localhost"}
14def assess_manifest_security(manifest: Manifest) -> list[str]:
15 """Return human-readable advisories for explicit manifest-level risk choices.
17 The intent is to keep user overrides possible while making risky choices
18 visible during validation, export, and build flows.
19 """
20 advisories: list[str] = []
22 if "@sha256:" not in manifest.runtime.base_image:
23 advisories.append(
24 "runtime.base_image is not pinned with a digest; supply-chain trust and "
25 "build reproducibility are reduced."
26 )
27 if manifest.runtime.user == "root":
28 advisories.append(
29 "runtime.user is set to root; containers should prefer an unprivileged "
30 "runtime user when possible."
31 )
32 if not manifest.openclaw.sandbox.read_only_root:
33 advisories.append(
34 "openclaw.sandbox.read_only_root is disabled; writable roots increase the "
35 "impact of agent or container compromise."
36 )
37 if manifest.openclaw.sandbox.network == "host":
38 advisories.append(
39 "openclaw.sandbox.network='host'; this weakens host isolation by sharing "
40 "the host network namespace with the sandbox."
41 )
42 elif manifest.openclaw.sandbox.network not in {"bridge", "none"}: 42 ↛ 43line 42 didn't jump to line 43 because the condition on line 42 was never true
43 advisories.append(
44 f"openclaw.sandbox.network={manifest.openclaw.sandbox.network!r}; verify "
45 "that the selected network mode preserves the intended host isolation."
46 )
47 if any(
48 tool_name.strip().lower() in _WILDCARD_TOOL_NAMES
49 for tool_name in [*manifest.openclaw.tools_allow, *manifest.openclaw.tools_deny]
50 ):
51 advisories.append(
52 "openclaw.tools contains wildcard entries; explicit tool names are safer "
53 "and easier to audit."
54 )
55 if "shell_command" in manifest.openclaw.tools_allow:
56 advisories.append(
57 "openclaw.tools.allow includes shell_command; keep tool scopes narrow and "
58 "use human approval for destructive actions."
59 )
60 return advisories
63def assess_runtime_env_security(values: Mapping[str, str]) -> list[str]:
64 """Return advisories for runtime env overrides that weaken deployment defaults."""
65 advisories: list[str] = []
67 gateway_bind = values.get("OPENCLAW_GATEWAY_HOST_BIND", "")
68 if gateway_bind and gateway_bind not in _LOOPBACK_BINDS:
69 advisories.append(
70 "OPENCLAW_GATEWAY_HOST_BIND exposes the gateway beyond localhost; verify "
71 "that external access is intentional and protected by host firewall rules."
72 )
74 bridge_bind = values.get("OPENCLAW_BRIDGE_HOST_BIND", "")
75 if bridge_bind and bridge_bind not in _LOOPBACK_BINDS:
76 advisories.append(
77 "OPENCLAW_BRIDGE_HOST_BIND exposes the bridge beyond localhost; verify "
78 "that external access is intentional and protected by host firewall rules."
79 )
81 if values.get("OPENCLAW_ALLOW_INSECURE_PRIVATE_WS", "").strip():
82 advisories.append(
83 "OPENCLAW_ALLOW_INSECURE_PRIVATE_WS is enabled; this weakens transport "
84 "security for private websocket connections."
85 )
87 return advisories