Skip to content

[Feature]: Fail-fast, centralized configuration validation for the Flask ML API #987

Description

@pavsoss

[Feature]: Fail-fast, centralized configuration validation for the Flask ML API

Summary

The Flask ML API reads configuration ad-hoc via scattered os.getenv(...) calls across backend/api.py, backend/rate_limiting.py and backend/domain_checker.py. A backend/config.py module exists but api.py doesn't use it (it only serves the FastAPI/DB side). The result:

  • Misconfiguration fails late and partiallyINTERNAL_SECRET is validated at import, but MODEL_PATH/VECTORIZER_PATH, MAX_MESSAGE_LENGTH, SERVICE_IP_ALLOWLIST, FLASK_HOST/FLASK_DEBUG, rate-limit windows and optional threat-intel keys fail lazily (or silently fall back), so an operator finds out on the first request, not at boot.
  • Defaults and parsing (_env_flag, int(os.getenv(...))) are duplicated in more than one file.
  • Errors are reported one at a time — fix one env var, restart, hit the next.

This proposes a single validated Settings object, loaded once at startup, that reports all configuration problems together.

Motivation / current state

Confirmed scattered reads include (non-exhaustive):

  • api.py: INTERNAL_SECRET, MODEL_PATH, VECTORIZER_PATH, LABEL_ENCODER_PATH, URL_MODEL_PATH, URL_VECTORIZER_PATH, MAX_MESSAGE_LENGTH, NODE_ENV, SERVICE_IP_ALLOWLIST, FLASK_PORT, FLASK_HOST, FLASK_DEBUG.
  • rate_limiting.py: REDIS_URL / RATE_LIMIT_STORAGE_URI, per-policy *_RATE_LIMIT overrides.
  • domain_checker.py: SAFE_BROWSING_API_KEY, VIRUSTOTAL_API_KEY.

backend/config.py today is 14 lines and only covers the DB/FastAPI vars — it is not the ML API's config source. Not a duplicate of any open issue.

Proposed solution

Add a Settings dataclass that owns parsing, defaults and validation, and swap the scattered reads for settings.*. Validation collects every error and raises one ConfigError listing all of them (matches the "report all bad inputs, not just the first" principle).

File Change
backend/settings.py (new) @dataclass(frozen=True) Settings (typed fields for paths, host/port, flags, limits, IP allowlist, optional API keys) + load_settings() that aggregates all validation errors (INTERNAL_SECRET presence/length, port range, non-negative MAX_MESSAGE_LENGTH, FLASK_DEBUG+non-loopback combo, model files exist) into one ConfigError; typed _env_int/_env_flag helpers
backend/api.py replace scattered os.getenv + _load_internal_secret + _env_flag with settings = load_settings(); use settings.* at the model-load, IP-allowlist, message-length and __main__ sites
backend/config.py re-export the ML fields from settings (single source of truth), keep DB fields
backend/tests/test_settings.py (new) missing INTERNAL_SECRET; too-short secret; invalid port; the aggregated error message names all problems at once; valid config loads
backend/.env.example / README.md document the validated variables in one table

Difficulty: Hard

Consolidating configuration touches several modules, requires careful aggregation-style validation, a typed immutable settings object, and must preserve existing security behaviour and tests — depth across the service's startup path.

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions