Skip to content

Secure HTTP server startup instead of disabling auth by default #4

Description

@Justinabox

Summary

The built-in HTTP server can still start unauthenticated while binding to all interfaces. APIKeyAuth exists, but server.main() calls create_app(modem) without API keys and APIKeyAuth explicitly disables itself when the key list is empty.

For a Raspberry Pi modem that can send SMS and USSD, this means python server.py exposes POST /sms/send, POST /ussd/send, webhook subscription, and message listing to anyone who can reach port 8080 on the LAN/WAN.

Evidence

Affected code:

  • server.py:19-20 binds the service to 0.0.0.0:8080.
  • server.py:46-50 sets self.enabled = bool(self._keys), so an empty key list disables auth.
  • server.py:94-96 wires auth middleware but only with keys passed to create_app(...).
  • server.py:192 starts the real entry point with app = create_app(modem), i.e. no keys.
  • tests/test_api_auth.py:37-44 currently asserts that no keys pass through and auth is disabled by default.

Repository health checks from this scout run:

git diff --check
# exit 0

PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/ -q
# 288 passed in 3.94s

Duplicate check:

gh issue list --repo Justinabox/Callstack --state open --limit 100
# []

gh search issues --repo Justinabox/Callstack "unauthenticated API keys server.py 0.0.0.0" --state open
# []

Expected behavior

The production/server entry point should fail closed for networked APIs:

  • If no API key is configured, python server.py should refuse to start, bind only to localhost in explicit dev mode, or expose only non-sensitive health endpoints.
  • SMS send, USSD send, subscription, and message list endpoints should require authentication by default.
  • README quick-start docs should show how to configure the key without committing secrets.

Actual behavior

server.main() starts the app with no key material. Because auth is disabled when no keys are supplied, sensitive modem-control endpoints are reachable without an Authorization header.

Suggested fix direction

  • Add config/env loading for API keys, e.g. CALLSTACK_API_KEYS or a secrets file path.
  • Make the server entry point fail startup when binding to non-localhost without keys, unless an explicit CALLSTACK_INSECURE_DEV_SERVER=1 style override is set.
  • Keep create_app(modem, api_keys=None) usable for tests if needed, but make the production main() path safe by default.
  • Update tests to cover the production startup/config behavior, not only middleware in isolation.

Acceptance criteria

  • Starting python server.py with default 0.0.0.0 and no API key refuses to serve sensitive endpoints or exits with an actionable error.
  • With a configured key, unauthenticated requests to /sms/send, /ussd/send, /sms/subscribe, /sms/messages, and /sms/delivery-reports return 401/403.
  • README documents secure server startup and a local-only insecure/dev mode if one remains.
  • No API key or secret value is logged.

Verification gates

git diff --check
PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/ -q

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions