Security & Trust Centre

Built Secure
From the Ground Up.

Rxkhoj handles sensitive health-related data for patients and pharmacies across India. This page explains every security mechanism we've built to protect that data — transparently and in full technical detail.

🔐
TLS 1.3
All data in transit
🔑
bcrypt
Password hashing
JWT + Refresh
Short-lived access tokens
📵
Zero Logs
No PII in server logs
HTTPS everywhere
OTP-only login · no passwords
Data stored in India
No third-party analytics SDKs
No data sold, ever
🔒
01 · Transport Security

All data encrypted in transit

Every byte transferred between a user's device and Rxkhoj servers is encrypted using industry-standard TLS. There is no unencrypted HTTP fallback — all plain HTTP requests are permanently redirected to HTTPS.

🔒
TLS 1.2 / 1.3 Only
We enforce TLS 1.2 minimum with TLS 1.3 preferred. Weak cipher suites (RC4, DES, MD5) are explicitly disabled at the server level.
Encryption
↪️
HTTP → HTTPS Redirect
The web server issues a 301 permanent redirect for any request arriving on port 80. The mobile app hardcodes HTTPS endpoints and rejects any unencrypted response.
Policy
📜
Valid SSL Certificate
rxkhoj.com maintains a valid SSL/TLS certificate with automated renewal. Certificate expiry monitoring prevents accidental lapses.
Certificate
📱
Mobile App SSL Pinning
The Rxkhoj Android app is configured to only communicate with the declared API host. Man-in-the-middle proxy attacks are rejected at the network layer.
Mobile
🪪
02 · Authentication

No passwords. OTP-only patient login.

Rxkhoj patients authenticate exclusively via mobile OTP — there are no passwords to steal, reuse, or phish. Pharmacy staff log in with email + password where bcrypt is applied before any storage occurs.

Why OTP-only? Password-based logins are the leading cause of account compromise via credential stuffing, phishing, and weak password reuse. Eliminating passwords entirely for patients removes the largest attack surface for credential-based attacks.
📱

Patient enters mobile number

The app sends the mobile number to the backend over HTTPS. No password is ever requested or stored.

🔢

Cryptographically secure OTP generated

A 6-digit OTP is generated using secrets.choice() — Python's CSPRNG — not the predictable random module. The OTP is stored in Redis with a 10-minute TTL.

📲

OTP delivered via transactional SMS

Delivered through a DLT-registered OTP route (not promotional) ensuring reliable transactional delivery. The OTP is never logged on our servers.

🔐

Constant-time OTP comparison

OTP verification uses secrets.compare_digest() — a timing-safe comparison that prevents timing-oracle attacks where an attacker could infer character correctness from response latency.

OTP deleted after successful verification

Once verified, the OTP is immediately deleted from Redis. It cannot be replayed. Failed attempts are rate-limited and counted against a maximum attempt ceiling.

🛡️
Brute-force protection: A maximum attempt limit is enforced per OTP per mobile number. Exceeding this limit invalidates the OTP entirely, requiring a fresh request after the TTL expires.

Pharmacy Staff Passwords

password hashing · Python/passlib
# bcrypt with cost factor 12 — ~250ms intentional slowdown per hash from passlib.context import CryptContext pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") # Storing: plain password never touches the database hashed = pwd_context.hash(plain_password) # Verifying: always constant-time regardless of match/no-match is_valid = pwd_context.verify(plain_password, hashed_from_db)
🎫
03 · Token Architecture

Short-lived JWT with rotating refresh tokens

After authentication, Rxkhoj issues a pair of tokens: a short-lived access token and a longer-lived refresh token. This architecture limits the blast radius of any compromised token.

TokenLifetimeStoragePurpose
Access Token (JWT) 15 minutes Memory (app state) Authorise every API request via Authorization: Bearer header
Refresh Token 7 days AsyncStorage (mobile) / httpOnly-equivalent (web) Obtain a new access token without re-authentication
⏱️
Short Access Token TTL
Even if an access token is intercepted, it is valid for at most 15 minutes. Attackers have a tiny window before the token expires automatically.
Time-Bound
🔄
Auto-Refresh on Expiry
The mobile app transparently refreshes the access token before it expires — users never notice, but the security boundary resets every 15 minutes.
Seamless UX
🚪
Force Logout on Invalid Refresh
If the refresh token is invalid or expired (e.g., after account reset), the app automatically clears all stored tokens and redirects the user to login. No stale sessions persist.
Auto-Eviction
🔏
HS256 Signed JWTs
Every JWT is cryptographically signed with a secret key stored in server environment variables — never in code or version control. Tampered tokens are rejected instantly.
Signed
🛡️
04 · Data Protection

Minimal data. Maximum protection.

Rxkhoj is designed on the principle of data minimisation — we collect only what the product absolutely requires and nothing more. Sensitive fields receive additional protection.

We never collect: Aadhaar numbers · PAN numbers · Medical history · Prescriptions · Biometric data · Payment card numbers · Bank details · User browsing behaviour

What Each Role Can See

Data FieldPatient SeesPharmacy SeesAdmin Sees
Patient mobile number✓ Own onlyOnly after patient confirms an offer✓ (operational)
Patient full name✓ Own only✗ Never✗ Not collected
Patient exact location✓ Own requestsApproximate distance only (e.g., "1.2 km away")✗ Aggregated only
Medicine request details✓ Own requests✓ All in service area✓ Aggregated reports
Pharmacy contact detailsOnly after confirming offer✓ Own profile✓ (operational)
Other patients' data✗ Never✗ NeverAnonymised only
🔍
Location privacy: Patient GPS coordinates are captured at request submission only — not tracked continuously or stored beyond the request lifecycle. Pharmacies see only computed distance, never raw coordinates.
🔧
05 · API Security

Multi-layered API defence

Every API endpoint in Rxkhoj is protected by multiple independent layers of validation, authorisation, and injection prevention.

🚦
Role-Based Access Control
Every endpoint declares a required role (PATIENT, PHARMACY, ADMIN). A pharmacy cannot access patient-only endpoints, and vice versa. Roles are extracted from the signed JWT — never from user-supplied parameters.
RBAC
💉
SQL Injection Prevention
Rxkhoj uses SQLAlchemy ORM throughout — all database queries are parameterised by the library. Raw SQL strings with user input are never used. The ORM handles escaping automatically.
ORM-Protected
📐
Schema Validation
All request bodies are validated against Pydantic schemas before any processing occurs. Invalid fields, wrong types, and unexpected keys are rejected at the boundary — malformed requests never reach business logic.
Pydantic
⏱️
Multi-Layer Rate Limiting
Every sensitive endpoint is rate-limited using atomic Redis counters: OTP requests (5/5min per IP · 3/5min per mobile), login attempts (10/15min per IP), admin login (5/15min per IP), and medicine request creation (15/min per IP). Returns HTTP 429 with a Retry-After header.
Anti-Abuse
🌐
CORS Policy
Cross-Origin Resource Sharing headers are configured with an explicit allowlist of trusted origins. Requests from unknown origins are blocked by the browser before they reach the API.
CORS
🪪
Resource Ownership Checks
Every request for user-specific data (e.g., request details, billing records) verifies that the authenticated user is the owner of that resource — preventing horizontal privilege escalation (IDOR attacks).
Anti-IDOR
🏗️
06 · Infrastructure

Hardened server environment

Rxkhoj runs on a dedicated VPS with a hardened configuration. Access to the production server is strictly controlled and logged.

🔑
SSH Key-Only Access
Password-based SSH is disabled. The production server only accepts connections from authorised SSH key holders. The root account is locked for direct login.
Server Hardening
🔥
Firewall Rules
Only ports 80, 443, and the SSH port are open. All other ports are blocked at the firewall level. Database and Redis ports are bound to localhost only — never exposed to the internet.
Network
🔒
Secrets in Environment Variables
JWT secret keys, database passwords, SMS API keys, and all credentials are stored in server-side environment variables — never committed to version control or visible in source code.
12-Factor App
🇮🇳
Data Stored in India
All patient and pharmacy data is stored on servers physically located in India, consistent with emerging data localisation requirements and ensuring data remains under Indian jurisdiction.
Data Residency
🐘
PostgreSQL with PostGIS
The primary database runs PostgreSQL — a battle-tested, security-patched relational database. Database users have least-privilege access: the application user cannot DROP tables or modify schema.
Database
Redis with TTL Enforcement
Redis stores OTPs and session state with mandatory TTL (time-to-live). Expired keys are automatically purged — no stale credentials can linger in memory.
Cache
🕵️
07 · Privacy by Design

Privacy baked in, not bolted on

Security and privacy at Rxkhoj are architectural decisions, not afterthoughts. Every system design choice was made with privacy in mind.

✅ No Google Analytics ✅ No Facebook Pixel ✅ No advertising SDKs ✅ No third-party cookie tracking ✅ Patient data never sold ✅ Pharmacy data never sold ✅ Aggregated-only demand reports ✅ Location point-in-time only ✅ Data minimisation enforced ✅ No continuous location tracking
📍
Location policy: The mobile app requests location permission only when a patient actively submits a medicine request. We use getLastKnownPositionAsync() which avoids waking the GPS radio unnecessarily — fast, battery-efficient, and privacy-respecting. Location is never tracked in the background.
📊
Weekly demand reports: The Monday pharmacy report contains only aggregated medicine demand counts — e.g., "Metformin 500mg was requested 14 times in your area." No patient identifiers, mobile numbers, or location data is included in any report.
📡
08 · WebSocket Security

Authenticated real-time connections

Rxkhoj uses WebSockets to push real-time notifications to both patients (when a pharmacy responds) and pharmacies (when a new request arrives). These connections are authenticated and isolated per user.

🔐
Token-Authenticated Connections
WebSocket connections must present a valid JWT token during the handshake. Unauthenticated connection attempts are rejected before any data exchange occurs.
Auth Required
🧱
Isolated Connection Channels
Each connected pharmacy receives only messages relevant to their registered service area. Cross-pharmacy data leakage is architecturally impossible — each connection is scoped to its authenticated identity.
Isolation
🔄
WSS (Encrypted WebSocket)
All WebSocket connections use WSS (WebSocket Secure) — the encrypted equivalent of HTTPS. Plain WS connections are not permitted in production.
Encrypted
Automatic Reconnection
The client implements exponential backoff for reconnection attempts. Stale connections are detected and cleaned up server-side to prevent resource exhaustion.
Resilience
📋
09 · Logging Policy

Zero PII in server logs

Server logs are essential for debugging but are a common source of accidental data exposure. Rxkhoj's logging is designed to capture operational events without recording any personally identifiable information.

structured logging · Python/structlog
# ✅ SAFE — logs only non-PII suffix for correlation log.info("sms_sent", provider="2factor", mobile_suffix=number[-4:]) # Output: {"event": "sms_sent", "provider": "2factor", "mobile_suffix": "7890"} # ✅ SAFE — logs request ID, not patient data log.info("request_created", request_id=req.id, urgency=req.urgency) # ❌ NEVER — mobile numbers, OTPs, tokens never logged # log.info("otp_sent", mobile=mobile, otp=otp) ← this does not exist in our codebase
Data TypeLogged?If Yes, What Exactly
Mobile phone numbers✗ NeverLast 4 digits only (for correlation in support cases)
OTP codes✗ Never
JWT tokens✗ Never
GPS coordinates✗ Never
Passwords / hashes✗ Never
API request events✓ YesHTTP method, path, status code, duration — no body content
Error events✓ YesException type, stack trace — no user data in trace
SMS delivery✓ YesProvider name + last 4 digits of mobile only
📣
10 · Responsible Disclosure

Found a vulnerability? Tell us first.

Security Vulnerability Reporting

We take security seriously and genuinely appreciate researchers and users who help us improve. If you've found a potential security issue, please follow responsible disclosure and give us a chance to fix it before making it public.

1
Email us at admin@rxkhoj.com with the subject line "Security Disclosure".
2
Describe the vulnerability, affected component, and steps to reproduce. Attach screenshots or a proof-of-concept if possible.
3
We will acknowledge your report within 48 hours and aim to resolve valid issues within 14 days.
4
We will credit you in our changelog (with your consent) once the fix is deployed.
📧 Report a Vulnerability

Please do not publicly disclose potential vulnerabilities before we've had a chance to address them. We promise to act swiftly and transparently.