Endpoint Monitor Service
A typed FastAPI service for process-local endpoint monitors and secure, on-demand HTTP checks. The complete contract and security semantics are in SPEC.md.
Quick start
Requires Python 3.12.
python -m venv .venv
. .venv/bin/activate
pip install -e '.[dev]'
make check
uvicorn app.main:app --reload
Open http://127.0.0.1:8000/docs. Example:
curl -sS -X POST http://127.0.0.1:8000/monitors \
-H 'content-type: application/json' \
-d '{"name":"Example","url":"https://example.com/health?token=not-logged"}'
curl -sS -X POST http://127.0.0.1:8000/monitors/UUID/check
curl -sS http://127.0.0.1:8000/monitors/UUID/status
Configuration
Settings are validated at startup and use the MONITOR_ prefix: LOG_LEVEL (default INFO), TOTAL_TIMEOUT_SECONDS (10, max 60), CONNECT_TIMEOUT_SECONDS (3), READ_TIMEOUT_SECONDS (5), MAX_REDIRECTS (5, max 10), and USER_AGENT. Connect/read values cannot exceed total timeout.
Security behavior
Every hop is resolved and all returned addresses must be globally routable. A mixed public/private DNS response is rejected. A dedicated connector receives only the validated addresses for that hop, so the HTTP library cannot perform a second unvalidated lookup; TLS still verifies the URL hostname. Redirects are followed manually only after the next URL passes the same policy. Literal IPs are classified identically, URL credentials are rejected, environment proxies are ignored, redirect count is bounded, and the entire operation (including DNS and all hops) has a total timeout. Logs contain a redacted URL with no query, fragment, or credentials.
The checks in tests/test_security.py provide inspectable evidence for private/special IP blocking, mixed-answer rejection, address pinning, per-hop redirect validation, total timeout mapping, and query redaction. tests/test_atomic.py proves revision-based atomic status application. Outbound HTTP is faked in tests; tests never depend on the public network.
Docker
docker build -t endpoint-monitor:local .
docker run --rm -p 8000:8000 endpoint-monitor:local
# or
docker compose up --build
The image runs as a non-root user with exactly one Uvicorn worker and a healthcheck.
Layout
SPEC.md— route/status/error/security contract and architectureapp/main.py— FastAPI routes and dependency wiringapp/store.py— lock-protected volatile state and compare-and-set statusapp/security.py— URL redaction, DNS resolution, and IP policyapp/checker.py— pinned transport, redirects, timeouts, latency/error mappingtests/— API, policy, timeout, logging, and concurrency tests
Operational limitation
There is deliberately no database. All monitors vanish on restart and multiple processes would have divergent state. Keep one worker, as the container does. For durability or horizontal scaling, replace MonitorStore with shared transactional storage before deployment. There is no authentication; place the service behind suitable network and identity controls.