"""Immutable process-static security posture contract (R232).""" from __future__ import annotations import os import sys import threading from collections.abc import Mapping from dataclasses import dataclass from typing import Any SCHEMA_VERSION = 1 _TRUTHY = frozenset({"1", "true", "yes", "on"}) _FALSY = frozenset({"0", "false", "no", "off"}) _CONTROL_PLANE_TRUTHY = frozenset({"1", "true", "yes"}) _VALID_DEPLOYMENT_PROFILES = frozenset({"local", "lan", "public"}) _VALID_WEBHOOK_MODES = frozenset({"bearer", "hmac", "bearer_or_hmac"}) _installed_posture: EffectiveSecurityPosture | None = None _posture_lock = threading.RLock() @dataclass(frozen=True, slots=True, kw_only=True) class PostureFinding: severity: str code: str message: str remediation: str = "" @dataclass(frozen=True, slots=True, kw_only=True) class EffectiveSecurityPosture: schema_version: int runtime_profile: str deployment_profile: str mae_profile: str network_exposed: bool admin_token_configured: bool observability_token_configured: bool dangerous_profile_override: bool dangerous_bind_override: bool localhost_no_origin_override: bool allow_any_public_llm_host: bool allow_insecure_base_url: bool webhook_auth_mode: str webhook_bearer_configured: bool webhook_hmac_configured: bool webhook_replay_protection_required: bool remote_admin_enabled: bool trust_x_forwarded_for: bool trusted_proxies_configured: bool callback_allow_hosts_configured: bool external_tools_enabled: bool registry_sync_enabled: bool transforms_enabled: bool bridge_enabled: bool bridge_device_token_configured: bool bridge_mtls_enabled: bool bridge_device_cert_map_configured: bool bridge_allowed_device_ids_configured: bool public_shared_surface_acknowledged: bool control_plane_mode: str control_plane_url_configured: bool control_plane_token_configured: bool control_plane_prerequisites_satisfied: bool control_plane_compat_override: bool connector_active_platforms: tuple[str, ...] connector_unguarded_platforms: tuple[str, ...] connector_recommended_allowlist_vars: tuple[str, ...] deployment_checks: tuple[PostureFinding, ...] deployment_pass_codes: tuple[str, ...] deployment_warn_codes: tuple[str, ...] deployment_fail_codes: tuple[str, ...] startup_profile_passed: bool startup_profile_overridden: bool startup_profile_violation_codes: tuple[str, ...] blocked_surface_ids: tuple[str, ...] decision_codes: tuple[str, ...] reason_codes: tuple[str, ...] def _read( environ: Mapping[str, str], primary: str, legacy: str | None = None, default: str = "", ) -> str: try: if primary in environ: value = environ.get(primary, default) elif legacy and legacy in environ: value = environ.get(legacy, default) else: value = default except Exception: # CRITICAL: malformed environment providers must fail closed without echoing # exception content or the attempted value into diagnostics. raise ValueError("security posture input unavailable") from None if value is None: return "" try: return str(value) except Exception: raise ValueError("security posture input is not scalar") from None def _normalized( environ: Mapping[str, str], primary: str, legacy: str | None = None, default: str = "", ) -> str: return _read(environ, primary, legacy, default).strip().lower() def _enabled( environ: Mapping[str, str], primary: str, legacy: str | None = None, ) -> bool: return _normalized(environ, primary, legacy) in _TRUTHY def _configured( environ: Mapping[str, str], primary: str, legacy: str | None = None, ) -> bool: return bool(_read(environ, primary, legacy).strip()) def _network_exposed_from_argv() -> bool: # Preserve the accepted S41 heuristic exactly: only the explicit --listen flag # changes this process-static decision. return "--listen" in sys.argv def _deployment_report(profile: str, environ: Mapping[str, str]): try: from .deployment_profile import evaluate_deployment_profile except ImportError: # pragma: no cover - top-level compatibility mode from services.deployment_profile import evaluate_deployment_profile return evaluate_deployment_profile(profile, environ) def _connector_posture(environ: Mapping[str, str]) -> Mapping[str, Any]: try: from .connector_allowlist_posture import evaluate_connector_allowlist_posture except ImportError: # pragma: no cover - top-level compatibility mode from services.connector_allowlist_posture import ( evaluate_connector_allowlist_posture, ) return evaluate_connector_allowlist_posture(environ) def _blocked_surface_ids(profile: str, mode: str) -> tuple[str, ...]: if profile != "public" or mode != "split": return () # IMPORTANT: these are the stable scalar IDs from the S62 registry. Importing # control_plane here would create a dependency cycle before R233 packages the domain. return ( "callback_egress", "registry_sync", "secrets_write", "tool_execution", "transforms_exec", "webhook_execute", ) def _safe_finding(check: Any) -> PostureFinding: message = str(check.message) if str(check.code) == "DP-WEBHOOK-005": # IMPORTANT: the legacy evaluator includes the raw invalid environment value. # The immutable boundary retains the stable code but never the untrusted value. message = "Unsupported webhook auth mode." return PostureFinding( severity=str(check.severity), code=str(check.code), message=message, remediation=str(check.remediation), ) def resolve_effective_security_posture( environ: Mapping[str, str] | None = None, *, network_exposed: bool | None = None, ) -> EffectiveSecurityPosture: # IMPORTANT: an explicitly supplied empty mapping means empty input. Do not use # `environ or os.environ`; doing so makes tests and lifecycle injection ambient. env = os.environ if environ is None else environ resolved_network_exposed = ( _network_exposed_from_argv() if network_exposed is None else bool(network_exposed) ) deployment_profile = _normalized( env, "OPENCLAW_DEPLOYMENT_PROFILE", default="local" ) if deployment_profile not in _VALID_DEPLOYMENT_PROFILES: raise ValueError("unsupported deployment profile") raw_runtime_profile = _normalized( env, "OPENCLAW_RUNTIME_PROFILE", default="minimal" ) runtime_profile = "hardened" if raw_runtime_profile == "hardened" else "minimal" mae_profile = ( "hardened" if runtime_profile == "hardened" and deployment_profile != "public" else deployment_profile ) report = _deployment_report(deployment_profile, env) findings = tuple(_safe_finding(check) for check in report.checks) pass_codes = tuple(item.code for item in findings if item.severity == "pass") warn_codes = tuple(item.code for item in findings if item.severity == "warn") fail_codes = tuple(item.code for item in findings if item.severity == "fail") dangerous_profile_override = _enabled( env, "OPENCLAW_SECURITY_DANGEROUS_PROFILE_OVERRIDE" ) startup_violations = () if deployment_profile == "local" else fail_codes startup_overridden = bool(startup_violations and dangerous_profile_override) startup_passed = ( deployment_profile == "local" or not startup_violations or startup_overridden ) explicit_control_mode = _normalized(env, "OPENCLAW_CONTROL_PLANE_MODE") if explicit_control_mode in {"embedded", "split"}: control_plane_mode = explicit_control_mode elif deployment_profile == "public": control_plane_mode = "split" else: control_plane_mode = "embedded" control_plane_url_configured = _configured(env, "OPENCLAW_CONTROL_PLANE_URL") control_plane_token_configured = _configured(env, "OPENCLAW_CONTROL_PLANE_TOKEN") control_plane_prerequisites_satisfied = ( control_plane_url_configured and control_plane_token_configured ) control_plane_compat_override = ( _normalized(env, "OPENCLAW_SPLIT_COMPAT_OVERRIDE") in _CONTROL_PLANE_TRUTHY ) connector = _connector_posture(env) active_platforms = tuple( sorted({str(item) for item in connector["active_platforms"]}) ) unguarded_platforms = tuple( sorted({str(item) for item in connector["unguarded_platforms"]}) ) recommended_allowlist_vars = tuple( sorted({str(item) for item in connector["recommended_allowlist_vars"]}) ) reason_codes = list(startup_violations) if deployment_profile == "public" and control_plane_mode == "split": if not control_plane_url_configured: reason_codes.append("CP-URL-MISSING") if not control_plane_token_configured: reason_codes.append("CP-TOKEN-MISSING") elif deployment_profile == "public" and control_plane_mode == "embedded": if not control_plane_compat_override: reason_codes.append("CP-PUBLIC-EMBEDDED") reason_codes.extend( f"CONNECTOR-ALLOWLIST-{platform.upper()}" for platform in unguarded_platforms ) if raw_runtime_profile not in {"", "minimal", "hardened"}: reason_codes.append("RUNTIME-PROFILE-DEFAULTED") decision_codes = [ ( "STARTUP-OVERRIDDEN" if startup_overridden else "STARTUP-PASS" if startup_passed else "STARTUP-DENY" ), ( "CONTROL-PLANE-PASS" if ( deployment_profile != "public" or ( control_plane_mode == "split" and control_plane_prerequisites_satisfied ) or (control_plane_mode == "embedded" and control_plane_compat_override) ) else "CONTROL-PLANE-DENY" ), ( "CONNECTORS-NONE" if not active_platforms else "CONNECTORS-UNGUARDED" if unguarded_platforms else "CONNECTORS-GUARDED" ), "NETWORK-EXPOSED" if resolved_network_exposed else "NETWORK-LOOPBACK", ] raw_webhook_mode = _normalized( env, "OPENCLAW_WEBHOOK_AUTH_MODE", "MOLTBOT_WEBHOOK_AUTH_MODE", ) webhook_auth_mode = ( raw_webhook_mode if raw_webhook_mode in _VALID_WEBHOOK_MODES else "unset" if not raw_webhook_mode else "invalid" ) replay_value = _normalized( env, "OPENCLAW_WEBHOOK_REQUIRE_REPLAY_PROTECTION", "MOLTBOT_WEBHOOK_REQUIRE_REPLAY_PROTECTION", ) return EffectiveSecurityPosture( schema_version=SCHEMA_VERSION, runtime_profile=runtime_profile, deployment_profile=deployment_profile, mae_profile=mae_profile, network_exposed=resolved_network_exposed, admin_token_configured=_configured( env, "OPENCLAW_ADMIN_TOKEN", "MOLTBOT_ADMIN_TOKEN" ), observability_token_configured=_configured( env, "OPENCLAW_OBSERVABILITY_TOKEN", "MOLTBOT_OBSERVABILITY_TOKEN" ), dangerous_profile_override=dangerous_profile_override, dangerous_bind_override=_enabled( env, "OPENCLAW_SECURITY_DANGEROUS_BIND_OVERRIDE", "MOLTBOT_SECURITY_DANGEROUS_BIND_OVERRIDE", ), localhost_no_origin_override=( _normalized(env, "OPENCLAW_LOCALHOST_ALLOW_NO_ORIGIN") == "true" ), allow_any_public_llm_host=_enabled( env, "OPENCLAW_ALLOW_ANY_PUBLIC_LLM_HOST", "MOLTBOT_ALLOW_ANY_PUBLIC_LLM_HOST", ), allow_insecure_base_url=_enabled( env, "OPENCLAW_ALLOW_INSECURE_BASE_URL", "MOLTBOT_ALLOW_INSECURE_BASE_URL", ), webhook_auth_mode=webhook_auth_mode, webhook_bearer_configured=_configured( env, "OPENCLAW_WEBHOOK_BEARER_TOKEN", "MOLTBOT_WEBHOOK_BEARER_TOKEN", ), webhook_hmac_configured=_configured( env, "OPENCLAW_WEBHOOK_HMAC_SECRET", "MOLTBOT_WEBHOOK_HMAC_SECRET", ), webhook_replay_protection_required=replay_value not in _FALSY, remote_admin_enabled=_enabled( env, "OPENCLAW_ALLOW_REMOTE_ADMIN", "MOLTBOT_ALLOW_REMOTE_ADMIN" ), trust_x_forwarded_for=_enabled( env, "OPENCLAW_TRUST_X_FORWARDED_FOR", "MOLTBOT_TRUST_X_FORWARDED_FOR", ), trusted_proxies_configured=_configured( env, "OPENCLAW_TRUSTED_PROXIES", "MOLTBOT_TRUSTED_PROXIES" ), callback_allow_hosts_configured=_configured( env, "OPENCLAW_CALLBACK_ALLOW_HOSTS", "MOLTBOT_CALLBACK_ALLOW_HOSTS", ), external_tools_enabled=_enabled(env, "OPENCLAW_ENABLE_EXTERNAL_TOOLS"), registry_sync_enabled=_enabled(env, "OPENCLAW_ENABLE_REGISTRY_SYNC"), transforms_enabled=_enabled(env, "OPENCLAW_ENABLE_TRANSFORMS"), bridge_enabled=_enabled( env, "OPENCLAW_BRIDGE_ENABLED", "MOLTBOT_BRIDGE_ENABLED" ), bridge_device_token_configured=_configured( env, "OPENCLAW_BRIDGE_DEVICE_TOKEN", "MOLTBOT_BRIDGE_DEVICE_TOKEN", ), bridge_mtls_enabled=_enabled(env, "OPENCLAW_BRIDGE_MTLS_ENABLED"), bridge_device_cert_map_configured=_configured( env, "OPENCLAW_BRIDGE_DEVICE_CERT_MAP" ), bridge_allowed_device_ids_configured=_configured( env, "OPENCLAW_BRIDGE_ALLOWED_DEVICE_IDS", "MOLTBOT_BRIDGE_ALLOWED_DEVICE_IDS", ), public_shared_surface_acknowledged=_enabled( env, "OPENCLAW_PUBLIC_SHARED_SURFACE_BOUNDARY_ACK", "MOLTBOT_PUBLIC_SHARED_SURFACE_BOUNDARY_ACK", ), control_plane_mode=control_plane_mode, control_plane_url_configured=control_plane_url_configured, control_plane_token_configured=control_plane_token_configured, control_plane_prerequisites_satisfied=control_plane_prerequisites_satisfied, control_plane_compat_override=control_plane_compat_override, connector_active_platforms=active_platforms, connector_unguarded_platforms=unguarded_platforms, connector_recommended_allowlist_vars=recommended_allowlist_vars, deployment_checks=findings, deployment_pass_codes=pass_codes, deployment_warn_codes=warn_codes, deployment_fail_codes=fail_codes, startup_profile_passed=startup_passed, startup_profile_overridden=startup_overridden, startup_profile_violation_codes=startup_violations, blocked_surface_ids=_blocked_surface_ids( deployment_profile, control_plane_mode ), decision_codes=tuple(decision_codes), reason_codes=tuple(dict.fromkeys(reason_codes)), ) def install_effective_security_posture( posture: EffectiveSecurityPosture, ) -> EffectiveSecurityPosture: if not isinstance(posture, EffectiveSecurityPosture): raise TypeError("posture must be EffectiveSecurityPosture") global _installed_posture with _posture_lock: if _installed_posture is None: _installed_posture = posture elif _installed_posture is not posture: # CRITICAL: silently replacing process posture creates contradictory # authorization decisions. Reset is an explicit lifecycle/test operation. raise RuntimeError("effective security posture is already installed") return _installed_posture def get_effective_security_posture( *, required: bool = True ) -> EffectiveSecurityPosture | None: with _posture_lock: posture = _installed_posture if posture is None and required: raise RuntimeError("effective security posture is not installed") return posture def get_or_create_effective_security_posture( environ: Mapping[str, str] | None = None, *, network_exposed: bool | None = None, ) -> EffectiveSecurityPosture: with _posture_lock: if _installed_posture is not None: return _installed_posture posture = resolve_effective_security_posture( environ, network_exposed=network_exposed, ) # The RLock makes this identity-stable even under concurrent startup. return install_effective_security_posture(posture) def reset_effective_security_posture_for_tests() -> None: global _installed_posture with _posture_lock: _installed_posture = None def effective_security_posture_diagnostics( posture: EffectiveSecurityPosture | None = None, ) -> dict[str, Any]: resolved = posture or get_effective_security_posture() assert resolved is not None return { "schema_version": resolved.schema_version, "runtime_profile": resolved.runtime_profile, "deployment_profile": resolved.deployment_profile, "mae_profile": resolved.mae_profile, "network_exposed": resolved.network_exposed, "authentication": { "admin_configured": resolved.admin_token_configured, "observability_configured": resolved.observability_token_configured, }, "startup_gate": { "passed": resolved.startup_profile_passed, "overridden": resolved.startup_profile_overridden, "violation_codes": list(resolved.startup_profile_violation_codes), }, "control_plane": { "mode": resolved.control_plane_mode, "prerequisites_satisfied": (resolved.control_plane_prerequisites_satisfied), "compat_override": resolved.control_plane_compat_override, "blocked_surface_count": len(resolved.blocked_surface_ids), }, "connectors": { "active_count": len(resolved.connector_active_platforms), "unguarded_count": len(resolved.connector_unguarded_platforms), }, "decision_codes": list(resolved.decision_codes), "reason_codes": list(resolved.reason_codes), }