Skip to content

Plan v0.3 hardening sequence before realtime/dashboard #9

Description

@Justinabox

v0.3 Hardening Sequence Implementation Plan

For Hermes: Use subagent-driven-development skill to implement this plan task-by-task.

Goal: Deliver v0.3 in a safe sequence: secure network write surfaces first, then complete robust SMS ingest/reassembly, then expose realtime/metrics without expanding the attack surface or hiding modem quirks.

Architecture: Keep Modem + service classes + typed EventBus as the core boundary. Treat server.py and any future WebSocket/dashboard as authenticated adapters over stable service events, not as places to invent modem state. Add SMS reassembly inside the SMS layer with deterministic mock-transport tests before requiring real hardware validation.

Tech Stack: Python 3.11, asyncio, aiohttp, aiosqlite, pyserial-asyncio, pytest, pytest-asyncio, pytest-aiohttp, mock transports.


Recent context

Dependency order

  1. Land/review narrow correctness fixes first: fix: handle answer active URC race #7 and fix: encode outbound SMS with GSM charset #8 reduce real-modem state/SMS corruption risk and should stay ahead of feature expansion.
  2. Resolve Secure HTTP server startup instead of disabling auth by default #4 before any realtime/dashboard/network expansion: WebSocket feeds and dashboards should inherit an auth model that is secure by default.
  3. Do SMS multipart reality discovery before service integration: The existing UDH parser is useful, but text-mode modem behavior may hide UDH or auto-join parts. Confirm whether reliable reassembly requires PDU-mode receive support.
  4. Add realtime/metrics only after event semantics are stable: WebSocket and Prometheus should expose cleaned service events/metrics, not raw partial modem lines with PII-heavy labels.
  5. Add modem auto-detection/capability profiles before broader voice/PBX assumptions: Voice/audio commands and port assignments vary by device family.

Slice 1 — HTTP startup is secure by default

Objective: Close #4 and establish the policy future /ws and dashboard endpoints must follow.

Files likely touched:

  • server.py
  • tests/test_api_auth.py
  • README.md or a new small HTTP server usage section

Acceptance criteria:

  • Starting server.py on 0.0.0.0 without configured API keys fails closed, or explicitly binds to loopback-only in a documented dev mode.
  • Tests cover: no-key non-loopback startup, loopback/dev behavior if allowed, authenticated SMS send, authenticated USSD send, and unauthenticated rejection.
  • Documentation explains how to provide API keys without logging or committing them.
  • No secrets, SIM numbers, or message bodies are printed in startup/auth errors.

Risk notes:

  • This is the one place where a product/security choice may be needed: fail startup without keys versus permit loopback-only dev mode.
  • Prefer fail-closed for Raspberry Pi deployments because write endpoints can send SMS/USSD.

Slice 2 — Finish outbound SMS charset boundary

Objective: Land #8 or equivalent so outbound SMS cannot silently mutate bodies.

Files likely touched:

  • callstack/sms/service.py
  • tests/test_sms_service.py
  • README.md troubleshooting/SMS limitations section if not already updated

Acceptance criteria:

  • GSM 03.38 basic and extension-table characters are sent as exact modem payload bytes.
  • Unsupported UCS2-required bodies are rejected before AT+CMGS, with a clear error that PDU/UCS2 send is not implemented yet.
  • Stored sent-message metadata cannot claim success for a body different from what was handed to the modem.
  • Mock tests assert raw bytes for representative ASCII, accented GSM characters, extension-table characters, and rejected Unicode.

Risk notes:

  • Rejecting UCS2 is safer than corruption, but it is not a complete international SMS solution. Track UCS2/PDU send as a future capability if real users need it.

Slice 3 — Multipart SMS receive/reassembly

Objective: Turn UDH metadata groundwork into one complete public incoming-message event per logical long SMS.

Files likely touched:

  • callstack/sms/types.py
  • callstack/sms/service.py
  • callstack/sms/store.py
  • callstack/sms/pdu.py
  • tests/test_sms_service.py
  • tests/test_sms_pdu.py

Acceptance criteria:

  • A discovery/test task records whether the supported receive path exposes concatenation metadata in text mode; if not, the implementation uses PDU-mode receive for multipart messages.
  • Parts are keyed by sender + concatenation reference + total parts, tolerate out-of-order arrival, and emit exactly one IncomingSMSEvent when complete.
  • Partial groups have deterministic expiry/cleanup so the process cannot grow unbounded.
  • Durable storage can distinguish partial parts from completed messages, or explicitly documents that partial persistence is out of scope for v0.3.
  • Tests cover: 8-bit refs, 16-bit refs, out-of-order parts, duplicate parts, missing part timeout, and normal single-part SMS unaffected.

Risk notes:

  • Do not rely on one modem family's text-mode behavior unless capability profiles document it.
  • Avoid emitting partial bodies to webhooks/WebSocket subscribers as final messages.

Slice 4 — Authenticated WebSocket realtime feed

Objective: Add /ws only after Slice 1 auth policy is merged.

Files likely touched:

  • server.py or a new callstack/http.py if the server module is split
  • tests/test_websocket.py
  • README.md

Acceptance criteria:

  • /ws requires the same auth policy as write endpoints.
  • Broadcasts sanitized event envelopes for SMS, delivery reports, call state, signal quality, and USSD responses.
  • Message bodies and phone numbers are included only where intentionally needed by the API contract; logs avoid PII.
  • Tests use pytest-aiohttp and mock events, no hardware.

Risk notes:

  • A realtime feed without auth is effectively a message/call metadata exfiltration endpoint.
  • WebSocket should not become a second event system; it should adapt the typed EventBus.

Slice 5 — PII-safe metrics endpoint

Objective: Add /metrics with operational counters/gauges that help unattended Pi deployments without leaking private data.

Files likely touched:

  • server.py or a new HTTP metrics helper
  • tests/test_metrics.py
  • README.md

Acceptance criteria:

  • Exposes counters/gauges for sent/received/failed SMS, delivery-report outcomes, active call state, signal quality, reconnect count, and uptime.
  • No phone numbers, SMS bodies, webhook URLs, SIM identifiers, or API keys appear in metric labels/values.
  • Tests assert both metric presence and absence of PII-like dynamic labels.

Risk notes:

  • Metrics endpoints are often scraped without auth on LANs; decide whether this endpoint follows the same auth policy or is loopback-only by default.

Slice 6 — Modem detection + capability profiles

Objective: Make hardware-specific assumptions explicit before broader voice/PBX work.

Files likely touched:

  • callstack/config.py
  • callstack/modem.py
  • new callstack/hardware/ module if useful
  • tests/test_modem_detection.py
  • README.md troubleshooting/hardware section

Acceptance criteria:

  • Detection scans candidate serial ports, probes with safe commands such as ATI, and reports model/vendor/candidate roles without sending SMS/calls.
  • A capability profile records which features are known/supported/unknown: audio port role, DTMF command shape, CPCMREG availability, SMS mode, delivery reports, USSD, GNSS.
  • Default behavior remains explicit and overridable; auto-detection must not break current ModemConfig(at_port=..., audio_port=...) users.
  • Tests use fake ports/transports and do not require real hardware.

Risk notes:

  • Avoid baking SIMCOM-only assumptions into the public API before Quectel/Huawei/Sierra-class profiles exist.

Human decisions requested

  1. For Secure HTTP server startup instead of disabling auth by default #4/Slice 1, confirm preferred no-key startup policy for python server.py:
    • Recommended: fail closed when binding non-loopback without API keys.
    • Alternative: automatically bind loopback-only in a documented development mode.
  2. For metrics, decide whether /metrics should require API-key auth or default to loopback-only.

Definition of done for v0.3

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