AirPost's docker-compose.yml ships development defaults so the demo runs out of the
box. Those defaults are insecure on purpose. This checklist is what must change before any
deployment beyond a laptop, grouped by concern. Status reflects the current code.
- REST API is JWT-gated by default (
AUTH_ENABLEDdefaults ON inapplication). -
POST /auth/loginis the only public route; it issues HS256 JWTs. - Role split: infrastructure CRUD (sinks/nodes/logic/topics/events) is admin only; deliveries/tracking are per-user with ownership checks.
- Login compares credentials in constant time (
hmac.Equal) to avoid timing leaks. - Replace seeded accounts (
ADMIN_*/USER_*env) and the devJWT_SECRETwith real, rotated secrets — the compose values are placeholders. - Add token expiry/refresh and per-user accounts backed by storage (today: env accounts).
- Put TLS in front of the API (terminate HTTPS at a reverse proxy); JWTs over plaintext are sniffable.
- All secrets come from env vars (
JWT_SECRET,DB_PASS,SMTP_*, account creds) — none are hard-coded in Go source. - Do not commit real secrets. The compose file's values are dev-only; supply real ones via a secrets manager / Docker secrets / CI secrets, not the YAML.
- Rotate
JWT_SECRETand DB credentials on a schedule; rotatingJWT_SECRETinvalidates issued tokens (intended).
-
applicationrestricts CORS to a single origin (UI_ORIGIN, default the UI's URL) with credentials — a wildcard*+ credentials is rejected by browsers and avoided. - Set
UI_ORIGINto the exact production UI origin (scheme + host + port). -
health-check's WebSocket currently accepts any Origin (CheckOriginreturns true) — restrict it to the UI origin before exposing it.
- Broker is isolated on the compose network; only 1883 is exposed for the local sim.
-
allow_anonymous trueis dev-only. Enable authentication (username/password or client certs) and an ACL so only the application and drone can publish/subscribe. - Use TLS (mqtts/8883) for any non-loopback broker traffic.
- Scope topics: the delivery request/status topics should not be world-writable.
- Don't publish infra ports (MySQL 3306, Kafka 9092, ES 9200) on a real host — keep them on the internal network only; expose just the UI (+ API behind TLS).
- Elasticsearch/Kibana run with security disabled (dev image config) — enable ES security + auth before exposing.
- Delivery records and tracking numbers tie a person to a parcel/location — treat them as PII: restrict reads to the owner/admin (ownership checks exist; keep them on).
- "Delivered" emails contain recipient addresses — in dev they go to MailHog (captured, never sent). Point SMTP at a real, authenticated relay only in production.
- Avoid logging full sensor payloads / coordinates / emails at info level in prod.
- Set retention/TTL on the
airpost-*Elasticsearch indices so location/sensor history isn't kept indefinitely.
- Go images are multi-stage and run a static binary on a minimal Alpine base (no toolchain at runtime).
- Pin/scan base images and dependencies; rebuild for CVEs.
These were found in git history and scrubbed via history rewrite + force-push (verified 0 occurrences across all submodule branches). Anyone who already saw them must still rotate the value:
- Leaked Gmail app password (logic-core, in history) — removed; rotate the account password.
- Hardcoded Kakao Maps JS API key (legacy UI) — removed; rotate in the Kakao console.
- Reverse-SSH backdoor host/IP (drone + station scripts) — removed.
- Hardcoded plaintext login accounts (legacy
ui/auth.js) — removed; auth is now server-side JWT, and the legacyui/app has been retired in favour ofui-next.
Reporting: open a private security advisory on the repo rather than a public issue.