fix(settings): suppress false drift when Infisical unreachable

- Add infisical_available flag to _build_env_var_row
- Stabilize error code (no exception text in API response)
- Document raw-comparison safety inline
This commit is contained in:
2026-05-04 06:24:26 +00:00
parent 562eae010a
commit c30c987ec2

View File

@@ -2587,7 +2587,7 @@ def _read_infisical_values() -> tuple[dict[str, str], list[str]]:
) )
except Exception as e: except Exception as e:
logger.warning("infisical_read_failed: %s", e) logger.warning("infisical_read_failed: %s", e)
return {}, [f"infisical_read_failed: {e}"] return {}, ["infisical_read_failed"]
values: dict[str, str] = {} values: dict[str, str] = {}
for s in secrets: for s in secrets:
if s.secret_key in ENV_CATALOG: if s.secret_key in ENV_CATALOG:
@@ -2599,18 +2599,36 @@ def _build_env_var_row(
spec: EnvSpec, spec: EnvSpec,
infisical_value: str | None, infisical_value: str | None,
container_value: str | None, container_value: str | None,
infisical_available: bool = True,
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Build a single response row for an env var.""" """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: 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 i_norm = mask_secret(infisical_value) if infisical_value else None
c_norm = mask_secret(container_value) if container_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 = ( drift = (
(infisical_value or "") != (container_value or "") (infisical_value or "") != (container_value or "")
and bool(infisical_value or container_value) and bool(infisical_value or container_value)
) )
infisical_display: str | None = i_norm infisical_display = i_norm
container_display: str | None = c_norm container_display = c_norm
else: else:
infisical_display = infisical_value infisical_display = infisical_value
container_display = container_value container_display = container_value
@@ -2618,7 +2636,6 @@ def _build_env_var_row(
normalize_for_compare(spec, infisical_value) normalize_for_compare(spec, infisical_value)
!= normalize_for_compare(spec, container_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: if infisical_value is None and container_value is None:
drift = False drift = False
row = spec.to_public_dict() row = spec.to_public_dict()
@@ -2635,11 +2652,14 @@ async def api_mcp_env():
"""List all catalog env vars with Infisical + container values.""" """List all catalog env vars with Infisical + container values."""
infisical_values, errors = _read_infisical_values() infisical_values, errors = _read_infisical_values()
project_id, env, path = _infisical_ctx() project_id, env, path = _infisical_ctx()
infisical_available = not errors # empty errors list → Infisical reachable
rows = [] rows = []
for key, spec in ENV_CATALOG.items(): for key, spec in ENV_CATALOG.items():
i_val = infisical_values.get(key) i_val = infisical_values.get(key)
c_val = os.environ.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 { return {
"vars": rows, "vars": rows,
"infisical_environment": env, "infisical_environment": env,