53 lines
2.5 KiB
Python
53 lines
2.5 KiB
Python
"""Non-secret exposure diagnostics preferences, independent of NPM ACL policy."""
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
from sqlmodel import Session, select
|
|
|
|
from ..core.time import utc_now
|
|
from ..db import session as database
|
|
from ..db.models import AppSetting
|
|
from ..schemas.exposure_preferences import ExposurePreferences, ExposurePreferencesUpdate
|
|
from .audit_logger import log_event
|
|
|
|
EXPOSURE_PREFERENCES_KEY = "exposure:preferences"
|
|
|
|
|
|
def get_exposure_preferences(session: Session) -> ExposurePreferences:
|
|
row = session.get(AppSetting, EXPOSURE_PREFERENCES_KEY)
|
|
value = row.value_json if row and isinstance(row.value_json, dict) else {}
|
|
# Only an explicit stored boolean false disables diagnostics. Corrupt or
|
|
# missing configuration keeps the secure default without crashing reads.
|
|
return ExposurePreferences(nginxAccessListChecksEnabled=value.get("nginxAccessListChecksEnabled") is not False)
|
|
|
|
|
|
def runtime_nginx_access_list_checks_enabled() -> bool:
|
|
"""Read the same persisted flag for connectors running outside API routes."""
|
|
try:
|
|
with Session(database.engine) as session:
|
|
return get_exposure_preferences(session).nginxAccessListChecksEnabled
|
|
except SQLAlchemyError:
|
|
return True
|
|
|
|
|
|
def update_exposure_preferences(session: Session, payload: ExposurePreferencesUpdate, *, identity: str) -> ExposurePreferences:
|
|
before = get_exposure_preferences(session)
|
|
after = ExposurePreferences(**payload.model_dump())
|
|
if before == after:
|
|
return after
|
|
row = session.get(AppSetting, EXPOSURE_PREFERENCES_KEY) or AppSetting(key=EXPOSURE_PREFERENCES_KEY)
|
|
row.value_json = after.model_dump()
|
|
row.is_secret = False
|
|
row.updated_at = utc_now()
|
|
session.add(row)
|
|
# Derived persistent snapshots must not show warnings from the previous
|
|
# preference. Their signatures also include the effective boolean.
|
|
for cached in session.exec(select(AppSetting)).all():
|
|
if cached.key in {"network_exposure:cache", "safety_center:dashboard_cache"} or cached.key.startswith("ai_security:review_cache:"):
|
|
session.delete(cached)
|
|
log_event(
|
|
session, source="manual", identity=identity, action_id="settings.exposure.update",
|
|
target=EXPOSURE_PREFERENCES_KEY, target_type="setting", risk="low",
|
|
policy_decision="allowed", reason="Administrator updated NPM access-list diagnostics preference",
|
|
result="success", before_state=before.model_dump(), after_state=after.model_dump(),
|
|
)
|
|
return after
|