Security
Hiras runs a server on your phone, so its security model matters. The design goal is simple: the gateway is reachable only from your own network, only with your key, and your data never leaves the device. Here’s exactly how that’s enforced.
The local-network guard
Before anything else — before the API key is even checked — Hiras inspects the caller’s address. Only private ranges are allowed through:
- Loopback —
127.0.0.0/8 - RFC 1918 —
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 - RFC 6598 (carrier-grade NAT / VPN overlays) —
100.64.0.0/10 - Link-local —
169.254.0.0/16
Any other address — anything from the public internet — is rejected with HTTP 403:
{ "error": "Forbidden: local network access only" }
Because this runs before authentication, a caller from outside your network never even reaches the
key check. The 100.64.0.0/10 range is what lets a VPN peer connect — see
VPN access.
Authentication
Every endpoint except GET /health requires the sk_local_ bearer key. The comparison is
constant-time, so it doesn’t leak information through timing. A missing, malformed, or duplicated
Authorization header is treated as unauthorized (401) — never a server error.
The key is generated on the device and stored in Android Keystore-backed secure storage. It never leaves the phone unless you choose to share it (for example, by scanning the pairing QR onto a client). You can regenerate it at any time; see Authentication.
Request limits
Two independent guards keep the gateway from being overwhelmed, and both are fixed in this version:
- Send path — 60 SMS segments per minute, plus a minimum of 1 second between sends. Over the
window returns
429withRetry-AfterandretryAfterSeconds; a single message that could never fit the per-minute ceiling returns400 message_too_long. - Per-caller burst — roughly 10 requests per second (with a small burst allowance) on every
endpoint, including
/health. Over that returns429.
Request bodies are capped at 16 KB; anything larger is rejected with 413 before it’s buffered,
so an oversized or open-ended request can’t exhaust memory.
Transport
On the local network the API is served over plain HTTP, not HTTPS. Hiras is honest about this: treat the API key like any LAN secret. Inbound webhooks are the exception — they’re signed with an HMAC and the key is never transmitted, so the send-capable key is never exposed over the wire even when Hiras is calling out to your backend.
Your data stays on the device
- Send history is stored in on-device SQLite, capped at the newest 1,000 messages, and is never uploaded. You can clear it at any time; uninstalling removes it.
- Hiras collects no analytics, no telemetry, and no crash reports. There is no cloud service behind it to send anything to.
Next
- Authentication — the API key in full.
- Troubleshooting — what the
401/403/429responses mean in practice.