Skip to content

fix(gsm): secure and harden SMS delivery - #362

Merged
Adamskiee merged 16 commits into
developfrom
bugfix/gsm-outbound-queue-252
Aug 15, 2026
Merged

fix(gsm): secure and harden SMS delivery#362
Adamskiee merged 16 commits into
developfrom
bugfix/gsm-outbound-queue-252

Conversation

@Adamskiee

@Adamskiee Adamskiee commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Hardens the complete SMS delivery path across the GSM gateway, main server, and mobile app.

  • Bounds outbound GSM admission with a configurable first-in, first-out queue and rejects saturation with HTTP 503 and QUEUE_FULL.
  • Prevents requests that expire before serial transmission from being written later, while allowing in-flight writes to wait for modem confirmation.
  • Requires GSM_SECRET in the GSM service and validates X-GSM-Secret before POST /sms/send can create a log row or occupy the modem.
  • Sends X-GSM-Secret from the main server for every direct gateway send.
  • Reconciles orphaned pending log rows at startup as failed/SERVICE_CRASHED before the serial worker starts.
  • Records rejected inbound messages with NO_ACCOUNT, BANNED_SENDER, or UNVERIFIED_SENDER.
  • Preserves gateway HTTP 502 and 503 responses through the main server and increases proxy timeout and connection headroom for the bounded queue.
  • Adds typed mobile GSM errors, keeps failed chat messages available for manual retry, and prevents verification or onboarding flows from reporting false success.
  • Updates security, architecture, deployment, API, requirements, design, and testing documentation.

Why

The GSM modem is a physical serial bottleneck that takes seconds to process each message. Unbounded or unauthenticated access can occupy the only outbound disaster-response channel and cause a denial of service.

Power loss can also leave sms_log rows permanently pending. Startup marks those rows failed instead of replaying them because the modem may already have transmitted the SMS before the crash, and replay could duplicate emergency messages.

Behavior

Gateway admission

  • SMS_SEND_QUEUE_MAXSIZE accepts values from 1 through 20 and defaults to 10 waiting requests.
  • One additional request may be active.
  • Saturated requests return HTTP 503 with QUEUE_FULL.
  • Shutdown rejects queued and active requests with SERVICE_STOPPING.
  • Queue wait and modem-readiness wait share a pre-write deadline.
  • Once serial transmission starts, the request waits for confirmation or the post-write timeout.

Service authentication

Both HTTP directions use the same shared secret:

  • Main server to GSM gateway: POST /sms/send with X-GSM-Secret
  • GSM gateway to main server: POST /gsm/inbound with X-GSM-Secret

Both services fail startup when their required secret is missing. Port 8001 remains restricted to host loopback or the trusted Compose network as an additional boundary.

Crash recovery and observability

Before constructing SerialWorker, gateway startup changes every orphaned pending row to:

status=failed
failure_reason=SERVICE_CRASHED

Inbound sender eligibility failures are retained as rejected audit records rather than appearing as successfully received traffic.

Validation

  • GSM gateway suite: 36 passed
  • Main-server GSM proxy and health tests: 12 passed
  • OpenAPI documentation drift check: passed
  • Database documentation drift check: passed
  • Mobile Jest run: 1,116 passed, with one unrelated parallel-run timeout
  • Timed-out mobile auth suite rerun alone: 15 passed
  • Mobile TypeScript check: passed
  • Mobile ESLint: passed with one warning in generated .expo/types/router.d.ts
  • Expo Doctor: 18/18 checks passed
  • git diff --check: passed

The full server suite still depends on correctly isolated Redis, WebSocket lifespan, and database test services. The GSM-focused server tests pass.

Deployment notes

Set the same strong GSM_SECRET in the main server and GSM gateway environment files before restarting either service. Existing installed systemd units and restricted files under /etc/sapot/ still require the normal operator deployment procedure.

Do not automatically replay SERVICE_CRASHED messages. Operators should inspect them before deciding whether a manual resend is safe.

Additional QA coverage

  • Adds the verified-phone testing scenario, which seeds qa_phone_verified with a valid Philippine phone number and a PhoneVerified record.
  • Adds qa_phone_verified to the testing login-fixture allowlist for phone-verification-gated flows.
  • Documents the scenario and covers both the seed state and /testing/login-as/qa_phone_verified.

Focused verification: ./venv/bin/pytest tests/test_qa_scenarios.py tests/test_testing_endpoints.py (28 passed).

A full server-suite run remains unavailable in this environment because the configured redis and db Docker hostnames cannot be resolved.

@Adamskiee
Adamskiee changed the base branch from develop to main August 11, 2026 08:15
@Adamskiee
Adamskiee changed the base branch from main to develop August 11, 2026 08:15
@Adamskiee Adamskiee changed the title fix(gsm-queue): bound outbound SMS admission fix(gsm): secure and harden SMS delivery Aug 14, 2026
@Adamskiee
Adamskiee marked this pull request as ready for review August 14, 2026 09:10
@Adamskiee
Adamskiee merged commit e1ce39a into develop Aug 15, 2026
5 checks passed
@Adamskiee
Adamskiee deleted the bugfix/gsm-outbound-queue-252 branch August 15, 2026 12:05
Adamskiee added a commit that referenced this pull request Aug 16, 2026
…rict environment config

This release merges the latest changes from `develop` into `main`, bringing in significant enhancements and bug fixes across the server, GSM module, and mobile components.

Key Changes:

GSM & Networking:
- GSM Emulator: Added a new virtual modem development stack for testing without physical hardware (#373).
- GSM Security & Routing: Secured SMS delivery (#362), required verified phone numbers for outgoing SMS (#375), and routed gateway traffic correctly on the Docker network (#372).

Server & Environment:
- Typed Configuration: Introduced strict typed environment configuration (#363), failing fast when GSM_SECRET is unset (#361), and replacing example secrets in configs (#366).
- API & Data Integrity: Hardened error handling logs, made role writes atomic (#377), ignored activity writes for deleted users (#374), and hardened QA route guards (#367).

Mobile:
- Connectivity: Refactored connectivity to remove the background signaling task (#364).
- EAS Debugging: Enabled the debug menu in preview builds (#360).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(gsm): bound the outbound SMS queue and return 503 on saturation

1 participant