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

1"""Security posture helpers for secure-by-default with explicit override support.""" 

2 

3from __future__ import annotations 

4 

5from collections.abc import Mapping 

6 

7from openenv.core.models import Manifest 

8 

9 

10_WILDCARD_TOOL_NAMES = {"*", "all"} 

11_LOOPBACK_BINDS = {"127.0.0.1", "localhost"} 

12 

13 

14def assess_manifest_security(manifest: Manifest) -> list[str]: 

15 """Return human-readable advisories for explicit manifest-level risk choices. 

16 

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] = [] 

21 

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 

61 

62 

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] = [] 

66 

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 ) 

73 

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 ) 

80 

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 ) 

86 

87 return advisories