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
Summary
The built-in HTTP server can still start unauthenticated while binding to all interfaces.
APIKeyAuthexists, butserver.main()callscreate_app(modem)without API keys andAPIKeyAuthexplicitly disables itself when the key list is empty.For a Raspberry Pi modem that can send SMS and USSD, this means
python server.pyexposesPOST /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-20binds the service to0.0.0.0:8080.server.py:46-50setsself.enabled = bool(self._keys), so an empty key list disables auth.server.py:94-96wires auth middleware but only with keys passed tocreate_app(...).server.py:192starts the real entry point withapp = create_app(modem), i.e. no keys.tests/test_api_auth.py:37-44currently asserts that no keys pass through and auth is disabled by default.Repository health checks from this scout run:
Duplicate check:
Expected behavior
The production/server entry point should fail closed for networked APIs:
python server.pyshould refuse to start, bind only to localhost in explicit dev mode, or expose only non-sensitive health endpoints.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 anAuthorizationheader.Suggested fix direction
CALLSTACK_API_KEYSor a secrets file path.CALLSTACK_INSECURE_DEV_SERVER=1style override is set.create_app(modem, api_keys=None)usable for tests if needed, but make the productionmain()path safe by default.Acceptance criteria
python server.pywith default0.0.0.0and no API key refuses to serve sensitive endpoints or exits with an actionable error./sms/send,/ussd/send,/sms/subscribe,/sms/messages, and/sms/delivery-reportsreturn 401/403.Verification gates