diff --git a/web/app.py b/web/app.py index 3b6779b..d1ba5b2 100644 --- a/web/app.py +++ b/web/app.py @@ -2587,7 +2587,7 @@ def _read_infisical_values() -> tuple[dict[str, str], list[str]]: ) except Exception as e: logger.warning("infisical_read_failed: %s", e) - return {}, [f"infisical_read_failed: {e}"] + return {}, ["infisical_read_failed"] values: dict[str, str] = {} for s in secrets: if s.secret_key in ENV_CATALOG: @@ -2599,18 +2599,36 @@ def _build_env_var_row( spec: EnvSpec, infisical_value: str | None, container_value: str | None, + infisical_available: bool = True, ) -> dict[str, Any]: - """Build a single response row for an env var.""" - if spec.is_secret: + """Build a single response row for an env var. + + When infisical_available=False and infisical_value=None, drift is forced + to False (we cannot detect drift without ground truth — UI shows the + 'errors' field instead). + """ + # When Infisical is unreachable, we have no ground truth — don't fabricate drift. + if not infisical_available and infisical_value is None: + drift = False + if spec.is_secret: + infisical_display: str | None = None + container_display: str | None = ( + mask_secret(container_value) if container_value else None + ) + else: + infisical_display = None + container_display = container_value + elif spec.is_secret: i_norm = mask_secret(infisical_value) if infisical_value else None c_norm = mask_secret(container_value) if container_value else None - # drift: compare raw before masking + # Raw comparison (not hash): values stay in server memory only — never + # logged or returned in the response. mask_secret is applied to display only. drift = ( (infisical_value or "") != (container_value or "") and bool(infisical_value or container_value) ) - infisical_display: str | None = i_norm - container_display: str | None = c_norm + infisical_display = i_norm + container_display = c_norm else: infisical_display = infisical_value container_display = container_value @@ -2618,7 +2636,6 @@ def _build_env_var_row( normalize_for_compare(spec, infisical_value) != normalize_for_compare(spec, container_value) ) - # only count as drift if at least one side is non-null if infisical_value is None and container_value is None: drift = False row = spec.to_public_dict() @@ -2635,11 +2652,14 @@ async def api_mcp_env(): """List all catalog env vars with Infisical + container values.""" infisical_values, errors = _read_infisical_values() project_id, env, path = _infisical_ctx() + infisical_available = not errors # empty errors list → Infisical reachable rows = [] for key, spec in ENV_CATALOG.items(): i_val = infisical_values.get(key) c_val = os.environ.get(key) - rows.append(_build_env_var_row(spec, i_val, c_val)) + rows.append( + _build_env_var_row(spec, i_val, c_val, infisical_available=infisical_available) + ) return { "vars": rows, "infisical_environment": env,