# API Security Research: Content Protection for Glidr ## Threat Model What we are protecting: - A question bank (981 questions with explanations) that represents significant editorial work - Not financial data, medical data, or personal information - Business goal: prevent competitors from bulk-downloading questions for free Realistic threats: 1. **Casual scraping** — someone writes a script to download all content 2. **Competitor copying** — another app developer bulk-downloads the question bank 3. NOT a concern: sophisticated nation-state attackers, reverse engineers with unlimited resources Conclusion: We need "good enough" security, not military-grade protection. A determined attacker with the app binary can always extract keys, but we want to raise the bar above casual scraping. --- ## Security Options Evaluated ### Option 1: API Key in App Binary (Basic, Recommended for v1) **How it works:** - A shared secret (e.g., `X-Glidr-Key: abc123...`) is embedded in the app - Server validates this header on all content endpoints - Key is obfuscated in the binary using compile-time string splitting or encryption **Implementation:** ```dart // Store as environment variable injected at build time // Never hardcode in plain text in source const apiKey = String.fromEnvironment('GLIDR_API_KEY'); // In HTTP client dio.options.headers['X-Glidr-Key'] = apiKey; ``` **Server side (PHP example):** ```php $key = $_SERVER['HTTP_X_GLIDR_KEY'] ?? ''; if ($key !== getenv('GLIDR_API_KEY')) { http_response_code(403); die('Forbidden'); } ``` **Pros:** Simple, zero infrastructure cost, stops casual scrapers **Cons:** Key is extractable from app binary by determined attacker **Verdict:** Sufficient for protecting a $49.99 exam app's question bank --- ### Option 2: Certificate Pinning **How it works:** - App validates that tekmidian.com presents the exact SSL certificate it expects - Prevents man-in-the-middle attacks even on compromised networks **Implementation in Dio:** ```dart (dio.httpClientAdapter as IOHttpClientAdapter).onHttpClientCreate = (client) { client.badCertificateCallback = (cert, host, port) => false; SecurityContext context = SecurityContext(); context.setTrustedCertificatesBytes(pinnedCertBytes); return HttpClient(context: context); }; ``` **Pros:** Prevents MITM interception of API key **Cons:** Certificate rotation causes app to break; requires app update when cert expires **Verdict:** Nice-to-have, add in v2 if abuse becomes a problem --- ### Option 3: Apple App Attest + Google Play Integrity **How it works:** - Apple/Google cryptographically verify the app is genuine and unmodified - Backend receives attestation token, validates with Apple/Google servers - Only genuine, unmodified app instances can download content **Pros:** Strongest protection available; cannot be bypassed without jailbreak **Cons:** - Requires backend infrastructure to validate attestation tokens - Apple App Attest has rate limits (free tier: limited attestations/day) - Adds significant implementation complexity - Overkill for a small hobby/niche app **Verdict:** Not needed for v1. Consider for v2 if piracy becomes a real problem. --- ### Option 4: JWT Tokens with Device Fingerprinting **How it works:** - App registers with backend using device ID + purchase receipt - Backend issues a JWT valid for this device - JWT is used for all content requests - Content is tied to a specific "account" **Pros:** Can revoke access per device; enables future account features **Cons:** Requires user accounts infrastructure; adds backend complexity **Verdict:** Not needed for v1 one-time purchase model with no accounts --- ## Recommended Security Architecture for Glidr v1 ### Layer 1: HTTPS (mandatory, baseline) All API traffic over HTTPS. tekmidian.com must have a valid SSL certificate. ### Layer 2: API Key Header - Obfuscated compile-time constant injected via `--dart-define` - Never committed to source control - Stored in CI/CD environment variables - Rotatable by releasing a new app version ### Layer 3: Rate Limiting on Server - Limit to 100 requests/hour per IP - Limit to 10 full-bank downloads per day globally - Simple nginx or PHP-level throttling ### Layer 4: Content Structure (Defense in Depth) - Serve individual subject JSON files, not one giant file - App downloads only what it needs (avoids one-request full dump) ### Layer 5: Monitoring - Server access logs reviewed periodically - Alert on unusual download patterns --- ## API Endpoint Design Base URL: `https://tekmidian.com/glidr/api/v1/` All requests require header: `X-Glidr-Key: {secret}` ``` GET /manifest.json Response: { "version": "1.2.0", "subjects": { "air_law": "abc123hash", ... } } GET /subjects/{subject_id}.json Response: Full subject JSON with all questions Example: /subjects/air_law.json GET /figures/{filename} Response: Image file (PNG or SVG) Example: /figures/bazl_30_q08_ask21_speed_polar.png ``` ### Manifest Response Example ```json { "version": "1.2.0", "updated_at": "2026-03-15T00:00:00Z", "subjects": { "air_law": { "hash": "sha256:abc...", "question_count": 110, "size_bytes": 45000 }, "meteorology": { "hash": "sha256:def...", "question_count": 110, "size_bytes": 52000 } }, "figures": { "hash": "sha256:ghi...", "count": 58, "size_bytes": 4200000 } } ``` App caches subject hashes locally. On launch, compares cached hash vs remote manifest. Downloads only changed subjects. --- ## Server Setup on tekmidian.com Minimal PHP implementation: ``` /glidr/ api/ v1/ .htaccess # auth check + routing auth.php # API key validation manifest.json # pre-generated static file subjects/ air_law.json meteorology.json ... (9 files) figures/ *.png *.svg ``` `.htaccess`: ```apache RewriteEngine On RewriteCond %{HTTP:X-Glidr-Key} !={GLIDR_API_KEY} RewriteRule ^ - [F,L] ``` This is the simplest possible implementation — Apache validates the key before serving any file.