# Reconnaissance Deliverable: Inou Health Portal Attack Surface Analysis ## 0) HOW TO READ THIS This reconnaissance report provides a comprehensive map of the application's attack surface, with special emphasis on authorization and privilege escalation opportunities for the Authorization Analysis Specialist. **Key Sections for Authorization Analysis:** - **Section 4 (API Endpoint Inventory):** Contains authorization details for each endpoint - focus on "Required Role" and "Object ID Parameters" columns to identify IDOR candidates. - **Section 6.4 (Guards Directory):** Catalog of authorization controls - understand what each guard means before analyzing vulnerabilities. - **Section 7 (Role & Privilege Architecture):** Complete role hierarchy and privilege mapping - use this to understand the privilege lattice and identify escalation targets. - **Section 8 (Authorization Vulnerability Candidates):** Pre-prioritized lists of endpoints for horizontal, vertical, and context-based authorization testing. **How to Use the Network Mapping (Section 6):** The entity/flow mapping shows system boundaries and data sensitivity levels. Pay special attention to flows marked with authorization guards and entities handling PII/sensitive data. **Priority Order for Testing:** Start with Section 8's High-priority horizontal candidates, then vertical escalation endpoints for each role level, finally context-based workflow bypasses. ## 1. Executive Summary Inou Health is a health data management platform that enables users to securely organize and share their medical records with AI assistants. The application uses a **Go-based backend** with custom frameworks, passwordless email authentication, and a permission-based authorization model. **Core Technology Stack:** - **Frontend:** Server-side rendered HTML templates (Go html/template) - **Backend:** Go (Golang) with standard library net/http - **Infrastructure:** Self-hosted on Linux (/tank/inou directory structure) - **Database:** SQLite with AES-256-GCM encryption at rest - **Authentication:** Custom passwordless email (6-digit verification codes) - **Authorization:** Custom RBAC with bitmask permissions (read/write/delete/manage) **Primary User-Facing Components:** - Public marketing pages (/, /pricing, /demo) - Passwordless authentication flow (/start, /send-code, /verify) - Dossier (health record) management dashboard - File upload system for medical imaging, lab results, genetics data - OAuth 2.0 server for third-party AI integrations (Claude Desktop, ChatGPT) - Model Context Protocol (MCP) server for AI assistant integrations - RESTful JSON API (v1) for mobile/programmatic access **Attack Surface Scope:** Network-accessible web application endpoints only. Local CLI tools, build scripts, and development utilities are out of scope. ## 2. Technology & Service Map ### Frontend - **Framework:** Server-side rendered Go templates (html/template package) - **JavaScript:** Minimal vanilla JS for UI interactions - **Authentication UI:** Custom passwordless login forms - **Key Libraries:** None identified (vanilla HTML/CSS/JS) ### Backend - **Language:** Go (Golang) 1.x - **Framework:** Standard library net/http with http.ServeMux router - **Key Dependencies:** - `golang.org/x/crypto` - AES-256-GCM encryption, bcrypt - `modernc.org/sqlite` - Pure Go SQLite implementation - Standard library only (minimal external dependencies) - **DICOM Processing:** External binary at `/tank/inou/bin/import-dicom` - **LLM Integration:** Google Gemini API for medical data parsing ### Infrastructure - **Hosting:** Self-hosted Linux server - **Web Server:** Go net/http (ports 8443 for portal, 8082 for API) - **Database:** SQLite (inou.db for main data, auth.db for OAuth) - **File Storage:** Local filesystem at `/tank/inou/uploads/` (encrypted) - **Encryption:** FIPS 140-3 compliant AES-256-GCM with master key at `/tank/inou/master.key` - **DICOM Viewer:** Separate service on localhost:8765 (Orthanc or similar) ### Identified Subdomains - **Primary Domain:** inou.com - **No subdomains discovered** - single-domain application ### Open Ports & Services - **Port 8443 (HTTPS):** Main portal web server (public-facing) - **Port 8082 (HTTP):** Internal API server (localhost only, proxied by portal) - **Port 8765 (HTTP):** DICOM viewer service (localhost only, proxied by portal) **Note:** External port scanning not performed per scope. Information derived from source code analysis. ## 3. Authentication & Session Management Flow ### Entry Points - **GET /start** - Passwordless login page (email entry) - **POST /send-code** - Sends 6-digit verification code to email - **POST /verify** - Validates verification code and creates session - **GET /oauth/authorize** - OAuth 2.0 authorization endpoint for third-party apps - **POST /oauth/token** - OAuth 2.0 token exchange endpoint ### Mechanism - Step-by-Step Process #### Web Portal Authentication Flow 1. **Email Submission** (`POST /send-code`) - User enters email at /start - Bot detection via nonce parameter (must be >= 2000ms) - Email normalized: `strings.ToLower(strings.TrimSpace(email))` - 6-digit verification code generated via crypto/rand - Code stored encrypted in database with 10-minute expiry - Code sent via email (SMTP) - **Code Location:** `/repos/inou-portal/portal/main.go:540-557` 2. **Code Verification** (`POST /verify`) - User submits email + 6-digit code - Code validated against database (single-use) - Backdoor code `250365` exists for testing (SECURITY NOTE) - **Code Location:** `/repos/inou-portal/portal/main.go:560-590`, `/repos/inou-portal/lib/dbcore.go:330-353` 3. **Session Creation** - 30-day persistent cookie named `login` created - Cookie value: plain dossier ID (16-char hex) - Cookie attributes: `HttpOnly=true, Secure=true, SameSite=Lax` - No server-side session expiration tracking - **Code Location:** `/repos/inou-portal/portal/main.go:311-313` 4. **Profile Completeness Check** - Redirect to `/onboard` if name/DOB/sex not set - Redirect to `/dashboard` if profile complete - **Code Location:** `/repos/inou-portal/portal/main.go:582-589` #### Mobile API Authentication Flow 1. **Code Request** (`POST /api/v1/auth/send`) - JSON API endpoint for mobile clients - Generates 6-digit code with 10-minute expiry - Stores code in `Dossier.AuthCode` field - Returns success/failure JSON - **Code Location:** `/repos/inou-portal/portal/api_mobile.go:55-103` 2. **Token Generation** (`POST /api/v1/auth/verify`) - Validates 6-digit code (same backdoor exists) - Generates or reuses 64-char hex session token - Session token stored in `Dossier.SessionToken` field (never expires) - Returns `{"token": "...", "needs_onboard": true/false}` - **Code Location:** `/repos/inou-portal/portal/api_mobile.go:105-149` #### OAuth 2.0 Flow (for AI Integrations) 1. **Authorization Request** (`GET /oauth/authorize`) - Supports Authorization Code flow with PKCE (S256) - Parameters: client_id, redirect_uri, code_challenge, state - Validates client_id and redirect_uri against whitelist - Redirects to /start if user not logged in - Generates 10-minute authorization code (64-char hex) - **Code Location:** `/repos/inou-portal/portal/oauth.go:52-140` 2. **Token Exchange** (`POST /oauth/token`) - Grant types: `authorization_code`, `refresh_token` - PKCE validation: SHA256(code_verifier) == code_challenge - Returns encrypted access token (15 minutes) + refresh token (30 days) - Access token format: AES-encrypted JSON `{d: dossierID, exp: timestamp}` - Refresh token: 64-char hex stored in auth.db (PLAINTEXT) - **Code Location:** `/repos/inou-portal/portal/oauth.go:144-296` ### Code Pointers **Authentication Logic:** - Main login handler: `/repos/inou-portal/portal/main.go:658-662` (handleLogin) - Send code: `/repos/inou-portal/portal/main.go:540-557` (handleSendCode) - Verify code: `/repos/inou-portal/portal/main.go:560-590` (handleVerify) - Code generation: `/repos/inou-portal/lib/dbcore.go:291-327` (DossierLogin) - Code validation: `/repos/inou-portal/lib/dbcore.go:330-353` (DossierVerify) **Session Management:** - Cookie creation: `/repos/inou-portal/portal/main.go:311-313` (setLoginCookie) - Session validation: `/repos/inou-portal/portal/main.go:279-285` (getLoggedInDossier) - Session token lookup: `/repos/inou-portal/lib/dbcore.go:356-364` (DossierGetBySessionToken) **OAuth Implementation:** - Authorization endpoint: `/repos/inou-portal/portal/oauth.go:52-140` - Token endpoint: `/repos/inou-portal/portal/oauth.go:144-296` - Token creation: `/repos/inou-portal/lib/crypto.go:174-182` (TokenCreate) - Token validation: `/repos/inou-portal/lib/crypto.go:186-207` (TokenParse) - PKCE verification: `/repos/inou-portal/lib/db_queries.go:861-875` ### 3.1 Role Assignment Process **Role Determination:** This application does NOT use traditional roles. Instead, it uses a **permission-based system** with bitmask capabilities. **Permission Assignment on Authentication:** - New users get implicit **owner** permissions on their own dossier (self-access) - Self-access check: `if accessorID == dossierID { return true }` (automatic, no database record) - **Code Location:** `/repos/inou-portal/lib/rbac.go:23-25` **Permission Grant Process:** - Users with PermManage (bit 8) can grant permissions to others - Permissions granted via `/dossier/{id}/permissions` page - Permission templates available: parent/guardian (read+write+manage), caregiver/medical (read+write), custom - Templates converted to bitmask at grant time - **Code Location:** `/repos/inou-portal/portal/main.go:1376-1464` **Default Permissions:** - Self (owner): All permissions (implicit, not stored) - Newly granted access: Depends on role template selected by grantor **Permission Upgrade Path:** - Only users with PermManage can grant/modify permissions - No self-service upgrade mechanism - No automatic privilege escalation ### 3.2 Privilege Storage & Validation **Storage Location:** - **Database Table:** `access` table in inou.db - **Schema:** `{AccessID, DossierID, GranteeID, EntryID, Relation, Ops, CreatedAt}` - **Ops Field:** Integer bitmask (1=read, 2=write, 4=delete, 8=manage) - **Encryption:** Access records encrypted at rest via AES-256-GCM - **Code Location:** `/repos/inou-portal/lib/types.go:189-197` **Alternative Storage (for authentication only):** - **Session Cookies:** Contain dossier ID only (no permissions stored) - **OAuth Access Tokens:** Encrypted JSON with `{d: dossierID, exp: timestamp}` (no permissions) - **Mobile Session Tokens:** Random 64-char hex (lookup dossier ID only) **Validation Points:** - **API Middleware:** `requireDossierAccess()`, `requireEntryAccess()`, `requireManageAccess()` - Location: `/repos/inou-portal/api/auth.go:87-131` - **V1 API:** `v1CanAccess()`, `v1CanWrite()` - Location: `/repos/inou-portal/api/api_v1.go:61-86` - **Core RBAC:** `CheckAccess(accessorID, dossierID, entryID, perm)` - Location: `/repos/inou-portal/lib/rbac.go:19-54` **Validation Process:** 1. Extract dossier ID from cookie/token/header 2. Query `access` table for grants where `GranteeID = accessorID AND DossierID = targetDossierID` 3. Check if `(grant.Ops & requestedPermission) != 0` 4. Special cases: self-access (owner), SystemContext, empty accessorID bypass checks **Cache/Session Persistence:** - **No caching:** Permissions checked on every request via database query - **Session Duration:** - Portal cookies: 30 days - Mobile session tokens: Never expire - OAuth access tokens: 15 minutes (then must refresh) ### 3.3 Role Switching & Impersonation **Impersonation Features:** NONE - Not implemented **Role Switching:** NONE - Not implemented **Privilege Escalation Mechanisms:** NONE - No "sudo mode" or temporary elevation **Audit Trail:** - Audit log exists at `/dossier/{id}/audit` showing access grants/revokes - No logging of impersonation (feature doesn't exist) - **Code Location:** `/repos/inou-portal/portal/main.go:1262-1278` (handleAuditLog) **System Context Bypass:** - **SystemAccessorID:** Hardcoded value `7b3a3ee1c2776dcd` bypasses all RBAC - **Empty AccessorID:** Empty string `""` also bypasses all checks - **Localhost Bypass:** Requests from 127.0.0.1 or ::1 get SystemContext (full access) - **Code Locations:** - SystemAccessorID: `/repos/inou-portal/lib/config.go:27` - Localhost bypass: `/repos/inou-portal/api/auth.go:151-156` - Empty accessor bypass: `/repos/inou-portal/lib/rbac.go:20-22` ## 4. API Endpoint Inventory **Network Surface Focus:** Only includes API endpoints accessible through the target web application. Excludes development/debug endpoints, local-only utilities, and components unreachable via network requests. ### Portal Server Endpoints (Port 8443) | Method | Endpoint Path | Required Role | Object ID Parameters | Authorization Mechanism | Description & Code Pointer | |---|---|---|---|---|---| | GET/POST | `/` | anon | None | None | Public landing page. `/repos/inou-portal/portal/main.go:516` | | POST | `/send-code` | anon | None | Bot detection (nonce) | Sends 6-digit verification code. `/repos/inou-portal/portal/main.go:540` | | POST | `/verify` | anon | None | None | Validates verification code, creates session. `/repos/inou-portal/portal/main.go:560` | | GET/POST | `/onboard` | user | None | Cookie `login` required | Profile completion (name/DOB/sex). `/repos/inou-portal/portal/main.go:593` | | GET | `/logout` | anon | None | None | Clears login cookie. `/repos/inou-portal/portal/main.go:638` | | POST | `/set-lang` | anon | None | None | Sets language preference. `/repos/inou-portal/portal/main.go:643` | | GET | `/start` | anon | None | None | Login page. `/repos/inou-portal/portal/main.go:658` | | GET | `/dashboard` | user | None | Cookie `login` | User dashboard showing all dossiers. `/repos/inou-portal/portal/main.go:831` | | GET | `/connect` | anon | None | None | AI integration setup instructions. `/repos/inou-portal/portal/main.go:664` | | GET/POST | `/invite` | user | None | Cookie `login` | Invite management page. `/repos/inou-portal/portal/main.go:795` | | GET | `/demo` | anon | None | None | Demo dossier (ID: 1111111111111111). `/repos/inou-portal/portal/main.go:867` | | GET | `/pricing` | anon | None | None | Pricing tiers page. `/repos/inou-portal/portal/main.go:694` | | GET | `/privacy-policy` | anon | None | None | Privacy policy. `/repos/inou-portal/portal/main.go:689` | | GET | `/legal/terms` | anon | None | None | Terms of service. `/repos/inou-portal/portal/main.go:709` | | GET | `/legal/dpa` | anon | None | None | Data Processing Agreement. `/repos/inou-portal/portal/main.go:704` | | GET/POST | `/dossier/add` | user | None | Cookie `login` | Create new dossier. `/repos/inou-portal/portal/main.go:906` | | GET | `/dossier/{id}` | user | `id` (dossierID) | PermRead on dossier | View dossier details. `/repos/inou-portal/portal/dossier_sections.go:678` | | GET/POST | `/dossier/{id}/edit` | user | `id` | PermWrite on dossier | Edit dossier profile. `/repos/inou-portal/portal/main.go:977` | | GET/POST | `/dossier/{id}/share` | user | `id` | PermManage on dossier | Share access via email invitation. `/repos/inou-portal/portal/main.go:1193` | | POST | `/dossier/{id}/revoke` | user | `id`, `granteeId` (POST param) | PermManage on dossier | Revoke access from user. `/repos/inou-portal/portal/main.go:1326` | | GET | `/dossier/{id}/audit` | user | `id` | PermRead on dossier | View access audit log. `/repos/inou-portal/portal/main.go:1262` | | GET | `/dossier/{id}/export` | user | `id` | PermRead on dossier | Download dossier data as JSON. `/repos/inou-portal/portal/main.go:1100` | | GET/POST | `/dossier/{id}/permissions` | user | `id` | PermManage on dossier | Manage permissions (grant/revoke access). `/repos/inou-portal/portal/main.go:1376` | | GET/POST | `/dossier/{id}/rbac/{role}` | user | `id`, `role` (path param) | PermManage on dossier | Edit role-based permissions. `/repos/inou-portal/portal/main.go:1531` | | GET/POST | `/dossier/{id}/access/{granteeId}` | user | `id`, `granteeId` | PermManage on dossier | Edit specific access grant. `/repos/inou-portal/portal/main.go:1669` | | GET | `/dossier/{id}/trackers` | user | `id` | PermRead on dossier | Daily check-in trackers (vitals, medications, etc.). `/repos/inou-portal/portal/trackers.go:78` | | GET | `/dossier/{id}/trackers/card/{trackerId}` | user | `id`, `trackerId` | PermRead | Render tracker card HTML. `/repos/inou-portal/portal/trackers.go:473` | | POST | `/dossier/{id}/trackers/respond` | user | `id`, `tracker_id` (POST) | PermWrite | Submit tracker response. `/repos/inou-portal/portal/trackers.go:348` | | GET | `/dossier/{id}/upload` | user | `id` | PermRead | File upload page. `/repos/inou-portal/portal/upload.go:117` | | POST | `/dossier/{id}/upload` | user | `id` | PermWrite | Upload medical files (imaging, labs, genetics). `/repos/inou-portal/portal/upload.go:153` | | DELETE | `/dossier/{id}/files/{fileId}/delete` | user | `id`, `fileId` | PermDelete | Delete uploaded file. `/repos/inou-portal/portal/upload.go:261` | | POST | `/dossier/{id}/files/{fileId}/update` | user | `id`, `fileId` | PermWrite | Update file metadata. `/repos/inou-portal/portal/upload.go:306` | | GET | `/dossier/{id}/files/{fileId}/status` | user | `id`, `fileId` | PermRead | Check file processing status. `/repos/inou-portal/portal/main.go:1970` | | POST | `/dossier/{id}/process-imaging` | user | `id` | PermWrite | Process uploaded DICOM imaging files. `/repos/inou-portal/portal/upload.go:364` | | GET | `/image/{id}` | user | `id` (entryID) | PermRead on entry | Retrieve medical image (proxied to API). `/repos/inou-portal/portal/api_proxy.go:57` | | GET | `/contact-sheet.webp/{id}` | user | `id` (studyID) | PermRead on entry | Contact sheet preview image. `/repos/inou-portal/portal/api_proxy.go:57` | | GET | `/api` | anon | None | None | API documentation page. `/repos/inou-portal/portal/main.go:717` | | POST | `/api/token/generate` | user | None | Cookie `login` | Generate long-lived API token. `/repos/inou-portal/portal/main.go:726` | | POST | `/api/token/regenerate` | user | None | Cookie `login` | Regenerate API token (invalidates old). `/repos/inou-portal/portal/main.go:747` | | GET | `/api/openapi.yaml` | anon | None | None | OpenAPI specification. `/repos/inou-portal/portal/main.go:768` | | GET | `/api/docs` | anon | None | None | API documentation (Swagger). `/repos/inou-portal/portal/main.go:773` | ### Mobile API Endpoints (Portal Server) | Method | Endpoint Path | Required Role | Object ID Parameters | Authorization Mechanism | Description & Code Pointer | |---|---|---|---|---|---| | POST | `/api/v1/auth/send` | anon | None | None | Mobile: Send verification code. `/repos/inou-portal/portal/api_mobile.go:55` | | POST | `/api/v1/auth/verify` | anon | None | None | Mobile: Verify code, get session token. `/repos/inou-portal/portal/api_mobile.go:105` | | GET | `/api/v1/dashboard` | user | None | Bearer token | Mobile: Dashboard data (all dossiers). `/repos/inou-portal/portal/api_mobile.go:165` | | GET | `/api/v1/trackers` | user | `dossier` (query) | Bearer token | Mobile: Get trackers for dossier. `/repos/inou-portal/portal/api_mobile.go:209` | | POST | `/api/v1/trackers/respond` | user | `dossier` (query) | Bearer token | Mobile: Submit tracker response. `/repos/inou-portal/portal/api_mobile.go:220` | ### OAuth 2.0 Endpoints | Method | Endpoint Path | Required Role | Object ID Parameters | Authorization Mechanism | Description & Code Pointer | |---|---|---|---|---|---| | GET | `/oauth/authorize` | user | None | Cookie `login` (redirects if missing) | OAuth authorization endpoint (PKCE supported). `/repos/inou-portal/portal/oauth.go:52` | | POST | `/oauth/token` | anon | None | Client credentials | Token exchange (authorization_code, refresh_token grants). `/repos/inou-portal/portal/oauth.go:144` | | GET | `/oauth/userinfo` | user | None | Bearer token | UserInfo endpoint (OpenID Connect compatible). `/repos/inou-portal/portal/oauth.go:298` | | POST | `/oauth/revoke` | anon | None | Client credentials | Revoke refresh token. `/repos/inou-portal/portal/oauth.go:337` | ### MCP (Model Context Protocol) Endpoints | Method | Endpoint Path | Required Role | Object ID Parameters | Authorization Mechanism | Description & Code Pointer | |---|---|---|---|---|---| | GET | `/.well-known/oauth-protected-resource` | anon | None | None | OAuth resource metadata. `/repos/inou-portal/portal/mcp_http.go:26` | | GET | `/.well-known/oauth-authorization-server` | anon | None | None | OAuth server metadata. `/repos/inou-portal/portal/mcp_http.go:54` | | POST | `/register` | anon | None | None | Dynamic client registration (OAuth). `/repos/inou-portal/portal/mcp_http.go:88` | | POST | `/mcp` | user | None | Bearer token | MCP JSON-RPC endpoint (AI tools/prompts). `/repos/inou-portal/portal/mcp_http.go:178` | ### API Server Endpoints (Port 8082 - Proxied via Portal) | Method | Endpoint Path | Required Role | Object ID Parameters | Authorization Mechanism | Description & Code Pointer | |---|---|---|---|---|---| | GET | `/api/version` | anon | None | None | API version info. `/repos/inou-portal/api/api_version.go:8` | | GET | `/api/dossiers` | user | None | Bearer/session token | List accessible dossiers. `/repos/inou-portal/api/api_dossiers.go:10` | | GET/POST | `/api/dossier` | user/system | `dossier` (query) | Bearer/session/localhost | Get/create dossier. `/repos/inou-portal/api/api_dossier.go:29` | | GET | `/api/studies` | user | `dossier` (query) | Bearer/session + PermRead | List medical imaging studies. `/repos/inou-portal/api/api_studies.go:10` | | GET | `/api/series` | user | `dossier`, `study` (query) | Bearer/session + PermRead | List imaging series. `/repos/inou-portal/api/api_series.go:12` | | GET | `/api/slices` | user | `dossier`, `series` (query) | Bearer/session + PermRead | List image slices. `/repos/inou-portal/api/api_slices.go:10` | | GET | `/image/{id}` | user | `id` (entryID) | PermRead on entry | Retrieve medical image file. `/repos/inou-portal/api/api_image.go:57` | | GET | `/contact-sheet.webp/{id}` | user | `id` (studyID) | PermRead on entry | Generate contact sheet image. `/repos/inou-portal/api/api_contact_sheet.go:61` | | GET | `/api/labs/tests` | user | `dossier` (query) | PermRead | List lab test types. `/repos/inou-portal/api/api_labs.go:11` | | GET | `/api/labs/results` | user | `dossier` (query) | PermRead | Get lab results. `/repos/inou-portal/api/api_labs.go:39` | | GET | `/api/access` | system | None | Localhost only | List access grants (internal). `/repos/inou-portal/api/api_access.go:49` | | POST | `/api/access` | system | None | Localhost only | Grant/revoke access (internal). `/repos/inou-portal/api/api_access.go:101` | | GET/POST | `/api/audit` | user | `dossier` (query) | PermRead | Get audit log entries. `/repos/inou-portal/api/api_audit.go:25` | | GET | `/api/entries` | user | `dossier` (query) | PermRead | List entries (medical records). `/repos/inou-portal/api/api_entries.go:45` | | POST | `/api/entries` | user/system | None | Bearer/session/localhost + PermWrite | Create/update entries. `/repos/inou-portal/api/api_entries.go:45` | | GET/POST | `/api/trackers` | user | `dossier` (query) | Bearer/session | Get/update trackers. `/repos/inou-portal/api/api_trackers.go:177` | | POST | `/api/trackers/freeform` | user | `dossier` (query) | Bearer/session + PermWrite | Submit freeform tracker entry. `/repos/inou-portal/api/api_trackers.go:319` | ### V1 RESTful API Endpoints (API Server) | Method | Endpoint Path | Required Role | Object ID Parameters | Authorization Mechanism | Description & Code Pointer | |---|---|---|---|---|---| | GET | `/api/v1/health` | anon | None | None | Health check endpoint. `/repos/inou-portal/api/api_v1.go:702` | | POST | `/api/v1/token` | user | None | Bearer token | Generate new access token. `/repos/inou-portal/api/api_v1.go:105` | | GET | `/api/v1/dossiers` | user | None | Bearer token | List accessible dossiers. `/repos/inou-portal/api/api_v1.go:136` | | GET | `/api/v1/dossiers/{id}` | user | `id` | Bearer token + PermRead | Get dossier details. `/repos/inou-portal/api/api_v1.go:189` | | GET | `/api/v1/dossiers/{id}/entries` | user | `id` | Bearer token + PermRead | List entries in dossier. `/repos/inou-portal/api/api_v1.go:212` | | GET | `/api/v1/dossiers/{id}/entries/{entryId}` | user | `id`, `entryId` | Bearer token + PermRead | Get specific entry. `/repos/inou-portal/api/api_v1.go:277` | | GET | `/api/v1/dossiers/{id}/access` | user | `id` | Bearer token + PermRead | List access grants for dossier. `/repos/inou-portal/api/api_v1.go:335` | | GET | `/api/v1/dossiers/{id}/audit` | user | `id` | Bearer token + PermRead | Get audit log. `/repos/inou-portal/api/api_v1.go:365` | | GET | `/api/v1/dossiers/{id}/trackers` | user | `id` | Bearer token + PermRead | Get trackers. `/repos/inou-portal/api/api_v1.go:563` | | GET | `/api/v1/dossiers/{id}/journal` | user | `id` | Bearer token + PermRead | List journal entries. `/repos/inou-portal/api/api_v1.go:738` | | GET | `/api/v1/dossiers/{id}/journal/{entryId}` | user | `id`, `entryId` | Bearer token + PermRead | Get journal entry. `/repos/inou-portal/api/api_v1.go:777` | | POST | `/api/v1/dossiers/{id}/journal` | user | `id` | Bearer token + PermWrite | Create journal entry. `/repos/inou-portal/api/api_v1.go:804` | | PATCH | `/api/v1/dossiers/{id}/journal/{entryId}` | user | `id`, `entryId` | Bearer token + PermWrite | Update journal entry. `/repos/inou-portal/api/api_v1.go:864` | | POST | `/api/v1/dossiers/{id}/parse` | user | `id` | Bearer token + PermRead | Parse medical data with LLM. `/repos/inou-portal/api/api_v1.go:444` | | GET | `/api/v1/images/{id}` | user | `id` | Bearer token + PermRead | Get image file. `/repos/inou-portal/api/api_v1.go:608` | | GET | `/api/v1/categories` | user | None | Bearer token (optional) | List entry categories. `/repos/inou-portal/api/api_v1.go:667` | | GET | `/api/v1/categories/{name}/types` | anon | `name` | None | List types within category. `/repos/inou-portal/api/api_v1.go:684` | **Total Network-Accessible Endpoints:** 80+ endpoints across portal, API, OAuth, and MCP servers. ## 5. Potential Input Vectors for Vulnerability Analysis **Network Surface Focus:** Only includes input vectors accessible through the target web application's network interface. Excludes inputs from local-only scripts, build tools, development utilities, or components unreachable via network requests. ### URL Parameters **Dossier/Object Identifiers:** - `id` (path parameter) - Dossier ID in `/dossier/{id}/*` routes - `dossierId` (path parameter) - Dossier ID in various routes - `fileId` (path parameter) - File ID in `/dossier/{id}/files/{fileId}/*` - `trackerId` (path parameter) - Tracker ID in tracker routes - `entryId` (path parameter) - Entry ID in V1 API routes - `granteeId` (path parameter) - User ID in access management routes - `dossier` (query parameter) - Filter parameter in API endpoints **Filtering & Display:** - `token` - Authentication token (query parameter fallback) - `lang` - Language code (defaults to "en") - `wc` - Window center for image rendering (float) - `ww` - Window width for image rendering (float) - `maxdim` - Max dimension for image scaling (integer) - `full` - Full image flag (boolean "1") - `add` - Tracker type to add (e.g., `?add=vital`) **OAuth Parameters:** - `client_id` - OAuth client identifier - `redirect_uri` - OAuth redirect URL (whitelist validated) - `response_type` - OAuth response type (must be "code") - `state` - OAuth state parameter (passed through) - `code_challenge` - PKCE code challenge - `code_challenge_method` - PKCE method (must be "S256") - `code` - Authorization code - `code_verifier` - PKCE code verifier **File Locations:** `/repos/inou-portal/api/api_v1.go:27`, `/repos/inou-portal/api/api_image.go:131-141`, `/repos/inou-portal/portal/oauth.go:59-64` ### POST Body Fields (Form-Encoded) **Authentication:** - `email` - Email address (normalized: toLowerCase + trimSpace) - `/repos/inou-portal/portal/main.go:543` - `code` - 6-digit verification code (converted to int) - `/repos/inou-portal/portal/main.go:563,566` - `nonce` - Bot detection timing value (must be >= 2000) - `/repos/inou-portal/portal/main.go:542` **Profile Management:** - `name` - User display name (trimSpace) - `/repos/inou-portal/portal/main.go:601` - `dob` - Date of birth (YYYY-MM-DD format, validated >= 1900-01-01) - `/repos/inou-portal/portal/main.go:602,608-611` - `sex` - Gender (converted: "" → 0, "M" → 1, "F" → 2) - `/repos/inou-portal/portal/main.go:603,622-623` - `email_lang` - Email language preference - `/repos/inou-portal/portal/main.go:920` - `relation` - Relationship type (converted to int) - `/repos/inou-portal/portal/main.go:921,937` - `is_care_receiver` - Care receiver flag (boolean "1" check) - `/repos/inou-portal/portal/main.go:922` **Access Sharing:** - `recipient_name` - Name of person being granted access (trimSpace) - `/repos/inou-portal/portal/main.go:1216` - `can_edit` - Edit permission flag (boolean "1" check) - `/repos/inou-portal/portal/main.go:1220` - `action` - Grant or revoke action - `/repos/inou-portal/portal/main.go:1396` - `role` - Permission role template (parent/guardian/caregiver/medical/custom) - `/repos/inou-portal/portal/main.go:1401` - `op_w`, `op_d`, `op_m` - Write/delete/manage permission flags (boolean "1") - `/repos/inou-portal/portal/main.go:1420-1422` **File Upload:** - `file` - Multipart file upload (max 10GB, no type validation) - `/repos/inou-portal/portal/upload.go:174` - `path` - Relative file path (defaults to filename, uses filepath.Base) - `/repos/inou-portal/portal/upload.go:182-186` - `category` - File category (genetics triggers genome processing) - `/repos/inou-portal/portal/upload.go:187` **Tracker Responses:** - `tracker_id` - Tracker ID being responded to - `/repos/inou-portal/portal/trackers.go:368` - `action` - Tracker action - `/repos/inou-portal/portal/trackers.go:369` - `response_raw` - JSON tracker response data - `/repos/inou-portal/portal/trackers.go:384` **OAuth:** - `grant_type` - OAuth grant type (authorization_code/refresh_token) - `/repos/inou-portal/portal/oauth.go:169` - `client_secret` - OAuth client secret (bcrypt validated) - `/repos/inou-portal/portal/oauth.go:173` - `refresh_token` - Refresh token for token rotation - `/repos/inou-portal/portal/oauth.go:174` ### POST Body Fields (JSON-Encoded) **Mobile Authentication:** ```json POST /api/v1/auth/send { "email": "string" // No validation } ``` **File:** `/repos/inou-portal/portal/api_mobile.go:66` **Entry/Journal Creation:** ```json POST /api/v1/dossiers/{id}/journal { "dossier": "string", // Dossier ID "category": "string", // Must be valid category (validated) "type": "string", // Entry type (no validation) "value": "string", // Entry value (no validation) "summary": "string", // Entry summary (no validation) "data": "string/json", // JSON data (no validation) "timestamp": int64, // Unix timestamp (no validation) "delete": bool, // Deletion flag "delete_category": bool // Delete all in category flag } ``` **File:** `/repos/inou-portal/api/api_entries.go:71-86` **Access Grant/Revoke:** ```json POST /api/access (localhost only) { "accessor": "string", // Hex ID (required) "target": "string", // Hex ID (required) "relation": int, // Relation type "can_edit": bool, // Permission flag "delete": bool, // Revoke flag "touch": bool // Update timestamp flag } ``` **File:** `/repos/inou-portal/api/api_access.go:108-142` **Dossier Creation:** ```json POST /api/dossier { "email": "string", // Required, no format validation "invited_by": "string" // Optional inviter ID } ``` **File:** `/repos/inou-portal/api/api_dossier.go:82-85` **MCP JSON-RPC:** ```json POST /mcp { "method": "string", // tools/list, tools/call, prompts/get, etc. "params": {...} // Varies by method } ``` **File:** `/repos/inou-portal/portal/mcp_http.go:249` ### HTTP Headers **Authentication:** - `Authorization: Bearer {token}` - Primary authentication method for APIs - Accepts OAuth access tokens (encrypted JSON) OR session tokens (64-char hex) - **File:** `/repos/inou-portal/api/auth.go:28-38` **CORS (MCP/Mobile API):** - `Origin` - Not validated (wildcard CORS: `Access-Control-Allow-Origin: *`) - **File:** `/repos/inou-portal/portal/mcp_http.go:180`, `/repos/inou-portal/portal/api_mobile.go:21` **Custom Headers:** - None identified in source code ### Cookie Values **Authentication:** - `login` - Portal session cookie (30-day expiry) - Value: Plain dossier ID (16-char hex) - Attributes: HttpOnly, Secure, SameSite=Lax - **File:** `/repos/inou-portal/portal/main.go:311-313` **Session State:** - `session` - Alternative session cookie (used by some API endpoints) - Value: Session token (64-char hex) - **File:** `/repos/inou-portal/api/auth.go:41-47` **OAuth Flow State:** - `oauth_return` - Stores return URL during OAuth login flow - **File:** `/repos/inou-portal/portal/oauth.go:109-115` **Preferences:** - No preference cookies identified (language stored server-side) ### File Upload Vectors **Multipart Form Data:** - **Endpoint:** `POST /dossier/{id}/upload` - **Field:** `file` (multipart file) - **Max Size:** 10GB (10 << 30 bytes) - **Type Validation:** NONE - All file types accepted (.exe, .sh, .dll, etc.) - **Extension Filtering:** NONE - **MIME Validation:** NONE - **Processing:** Encrypted and stored at `/tank/inou/uploads/{dossierID}/{fileID}` - **Special Handling:** - `category=genetics` → Triggers genome CSV parsing (async goroutine) - DICOM files → Processed by external binary `/tank/inou/bin/import-dicom` - **File:** `/repos/inou-portal/portal/upload.go:153-259` **Genome File Format (CSV):** - Comma or tab delimited - Columns: rsid, chromosome, position, genotype - No schema validation before parsing - **File:** `/repos/inou-portal/portal/genome.go:24-31` ## 6. Network & Interaction Map **Network Surface Focus:** Only maps components that are part of the deployed, network-accessible infrastructure. Excludes local development environments, build CI systems, local-only tools, or components unreachable through the target application's network interface. ### 6.1 Entities | Title | Type | Zone | Tech | Data | Notes | |---|---|---|---|---|---| | Internet-Users | ExternAsset | Internet | Browser/Mobile | N/A | External users accessing the application | | Portal-Server | Service | Edge | Go/net-http | PII, Tokens, Secrets | Main web application server (port 8443) | | API-Server | Service | App | Go/net-http | PII, Medical, Tokens | Backend API service (port 8082, localhost) | | DICOM-Viewer | Service | App | Orthanc-like | Medical-Imaging | Medical imaging viewer (port 8765, localhost) | | SQLite-Main | DataStore | Data | SQLite | PII, Medical, Tokens | Main database (inou.db) with AES-256-GCM encryption | | SQLite-Auth | DataStore | Data | SQLite | OAuth-Tokens | OAuth database (auth.db) - PLAINTEXT refresh tokens | | Filesystem-Uploads | DataStore | Data | Linux-FS | Medical-Files | Encrypted file storage (/tank/inou/uploads/) | | Master-Key-File | DataStore | Data | Linux-FS | Secrets | AES master key (/tank/inou/master.key) | | DICOM-Processor | Service | App | External-Binary | Medical-Imaging | DICOM import tool (/tank/inou/bin/import-dicom) | | SMTP-Server | ThirdParty | ThirdParty | Email | Public | Email delivery for verification codes | | Gemini-API | ThirdParty | ThirdParty | Google-AI | Medical | LLM for parsing medical data | | Claude-Desktop | ExternAsset | Internet | Anthropic-MCP | N/A | AI assistant connecting via OAuth/MCP | | ChatGPT | ExternAsset | Internet | OpenAI-MCP | N/A | AI assistant (potential integration) | ### 6.2 Entity Metadata | Title | Metadata | |---|---| | Portal-Server | Hosts: `https://inou.com:8443`; Endpoints: `/`, `/dossier/*`, `/oauth/*`, `/mcp`, `/api/v1/auth/*`; Auth: Cookie, Bearer; Dependencies: API-Server, SQLite-Main, SQLite-Auth, Filesystem-Uploads, DICOM-Viewer | | API-Server | Hosts: `http://127.0.0.1:8082`; Endpoints: `/api/*`, `/image/*`; Auth: Bearer, Session, Localhost-Bypass; Dependencies: SQLite-Main, Filesystem-Uploads; Exposure: Localhost-Only | | DICOM-Viewer | Hosts: `http://127.0.0.1:8765`; Endpoints: `/viewer/*`, `/data/*`; Auth: Proxied; Dependencies: Filesystem-Uploads; Exposure: Localhost-Only | | SQLite-Main | Path: `/tank/inou/inou.db`; Tables: entries, access, dossiers; Encryption: AES-256-GCM; Consumers: Portal-Server, API-Server; Schema: Custom ORM with struct tags | | SQLite-Auth | Path: `/tank/inou/auth.db`; Tables: oauth_clients, oauth_codes, oauth_refresh_tokens; Encryption: NONE (plaintext tokens); Consumers: Portal-Server; Risk: High | | Filesystem-Uploads | Path: `/tank/inou/uploads/{dossierID}/{fileID}`; Encryption: AES-256-GCM; Types: DICOM, PDF, Images, Genetics-CSV; Max-Size: 10GB; Validation: None | | Master-Key-File | Path: `/tank/inou/master.key`; Format: 32-byte AES key; Access: Portal-Server, API-Server on startup; Protection: Filesystem permissions | | DICOM-Processor | Path: `/tank/inou/bin/import-dicom`; Args: dossierID, tempDir; Trigger: POST /dossier/{id}/process-imaging; Risk: Command injection potential | | SMTP-Server | Protocol: SMTP; Usage: Sends 6-digit verification codes; Config: Environment variables; Rate-Limiting: None | | Gemini-API | Endpoint: `https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent`; Auth: API key; Usage: Parse medical documents; Data-Sent: Medical-PII | | Claude-Desktop | OAuth-Client: Anthropic; Redirect-URIs: `claude.ai/api/mcp/auth_callback`, `localhost:6274/oauth/callback`; Grant: authorization_code + PKCE; Scope: Read medical data | ### 6.3 Flows (Connections) | FROM → TO | Channel | Path/Port | Guards | Touches | |---|---|---|---|---| | Internet-Users → Portal-Server | HTTPS | :8443 / | None | Public | | Internet-Users → Portal-Server | HTTPS | :8443 /start | None | Public | | Internet-Users → Portal-Server | HTTPS | :8443 /send-code | None | Public | | Internet-Users → Portal-Server | HTTPS | :8443 /verify | None | Public | | Internet-Users → Portal-Server | HTTPS | :8443 /dashboard | auth:user | PII | | Internet-Users → Portal-Server | HTTPS | :8443 /dossier/{id} | auth:user, ownership:read | PII, Medical | | Internet-Users → Portal-Server | HTTPS | :8443 /dossier/{id}/upload | auth:user, ownership:write | Medical-Files | | Internet-Users → Portal-Server | HTTPS | :8443 /dossier/{id}/permissions | auth:user, ownership:manage | PII | | Internet-Users → Portal-Server | HTTPS | :8443 /dossier/{id}/share | auth:user, ownership:manage | PII | | Internet-Users → Portal-Server | HTTPS | :8443 /oauth/authorize | auth:user | Tokens | | Internet-Users → Portal-Server | HTTPS | :8443 /oauth/token | client-auth | Tokens | | Internet-Users → Portal-Server | HTTPS | :8443 /mcp | auth:bearer | PII, Medical | | Claude-Desktop → Portal-Server | HTTPS | :8443 /oauth/authorize | auth:user | Tokens | | Claude-Desktop → Portal-Server | HTTPS | :8443 /mcp | auth:bearer | PII, Medical | | Portal-Server → API-Server | HTTP | :8082 /api/* | proxy-only | PII, Medical, Tokens | | Portal-Server → DICOM-Viewer | HTTP | :8765 /viewer/* | proxy-only | Medical-Imaging | | Portal-Server → SQLite-Main | File | inou.db | None | PII, Medical, Tokens, Secrets | | Portal-Server → SQLite-Auth | File | auth.db | None | OAuth-Tokens | | Portal-Server → Filesystem-Uploads | File | /uploads/* | None | Medical-Files | | Portal-Server → Master-Key-File | File | master.key (startup) | None | Secrets | | Portal-Server → SMTP-Server | SMTP | :25/:587 | None | Public (codes only) | | API-Server → SQLite-Main | File | inou.db | None | PII, Medical, Tokens | | API-Server → Filesystem-Uploads | File | /uploads/* | None | Medical-Files | | API-Server → Master-Key-File | File | master.key (startup) | None | Secrets | | API-Server → DICOM-Processor | Process | exec /bin/import-dicom | localhost-only | Medical-Imaging | | API-Server → Gemini-API | HTTPS | generativelanguage.googleapis.com | api-key | Medical-PII | | DICOM-Viewer → Filesystem-Uploads | File | /uploads/* | None | Medical-Imaging | | Localhost → API-Server | HTTP | :8082 /api/access | localhost-bypass | PII, Tokens | | Localhost → API-Server | HTTP | :8082 /api/entries | localhost-bypass | PII, Medical | ### 6.4 Guards Directory | Guard Name | Category | Statement | |---|---|---| | auth:user | Auth | Requires valid user session via cookie `login` or Bearer token with dossier ID | | auth:bearer | Auth | Requires Authorization: Bearer header with encrypted access token or session token | | client-auth | Auth | Requires OAuth client_id and client_secret (bcrypt validated for confidential clients) | | ownership:read | ObjectOwnership | Requires PermRead (bit 1) on target dossier - checks `access` table or self-access | | ownership:write | ObjectOwnership | Requires PermWrite (bit 2) on target dossier - allows data modification | | ownership:delete | ObjectOwnership | Requires PermDelete (bit 4) on target dossier - allows entry/file deletion | | ownership:manage | ObjectOwnership | Requires PermManage (bit 8) on target dossier - allows granting access to others | | proxy-only | Network | Only accessible via reverse proxy from Portal-Server, not directly from internet | | localhost-only | Network | Restricted to requests from 127.0.0.1 or ::1 (localhost bypass grants SystemContext) | | localhost-bypass | Authorization | **CRITICAL:** Localhost requests bypass all RBAC checks and get full database access | | api-key | Protocol | Requires API key for Google Gemini LLM service | | cors:wildcard | Network | **WARNING:** Access-Control-Allow-Origin: * on MCP and mobile API endpoints | ## 7. Role & Privilege Architecture This section maps the application's authorization model. The application uses a **capability-based RBAC system** with bitmask permissions rather than traditional roles. ### 7.1 Discovered Roles **Important:** This application does NOT use traditional roles. Instead, it uses a permission bitmask system with relationship labels. | Role Name | Privilege Level | Scope/Domain | Code Implementation | |---|---|---|---| | anon | 0 | Global | No authentication - public endpoints only | | user (authenticated) | 1 | Global | Any authenticated user with valid session/token | | owner (self-access) | 10 | Per-Dossier | Implicit full permissions when accessorID == dossierID | | viewer (PermRead) | 3 | Per-Dossier | Permission bit 1 - can read dossier entries | | editor (PermWrite) | 5 | Per-Dossier | Permission bits 1+2 - can read and write | | deleter (PermDelete) | 6 | Per-Dossier | Permission bits 1+2+4 - can read, write, delete | | manager (PermManage) | 8 | Per-Dossier | Permission bits 1+2+8 - can read, write, grant access | | system (SystemContext) | 99 | Global | Full unrestricted access (localhost bypass) | **Relationship Labels** (stored as `Relation` field, not enforced): - 0: Custom/unspecified - 1: Parent - 4: Guardian - 5: Caregiver - 7: Medical professional - 99: Demo access **Code Locations:** - Permission bits: `/repos/inou-portal/lib/rbac.go:10-14` - Relationship labels: `/repos/inou-portal/portal/main.go:1432-1435` - Self-access check: `/repos/inou-portal/lib/rbac.go:23-25` - CheckAccess function: `/repos/inou-portal/lib/rbac.go:19-54` ### 7.2 Privilege Lattice ``` Privilege Ordering (→ means "can access resources of"): System Context (localhost) → [Full Bypass] Owner (self-access) → [All Permissions on Own Dossier] Manager (PermManage = 8) → [Can grant permissions to others] Deleter (PermDelete = 4) → [Can delete entries] Editor (PermWrite = 2) → [Can modify data] Viewer (PermRead = 1) → [Can read data] User (authenticated) → [Can access own dossier only] Anon → [Public pages only] Permission Combination Rules: - PermManage typically includes PermRead + PermWrite (bitmask 11 = 1+2+8) - PermDelete includes PermRead + PermWrite (bitmask 7 = 1+2+4) - Permissions are checked via bitwise AND: (grant.Ops & requestedPerm) != 0 Isolation: - Dossiers are isolated by default - Access requires explicit grant in `access` table - No cross-dossier permission inheritance ``` **No hierarchical role system** - each dossier maintains independent access control. ### 7.3 Role Entry Points | Role | Default Landing Page | Accessible Route Patterns | Authentication Method | |---|---|---|---| | anon | `/` | `/`, `/start`, `/pricing`, `/demo`, `/privacy-policy`, `/legal/*` | None | | user | `/dashboard` | `/dashboard`, `/onboard`, `/invite`, `/connect`, `/api`, `/dossier/add` | Cookie `login` or Bearer token | | viewer | `/dossier/{id}` | `/dossier/{id}` (read-only), `/dossier/{id}/audit`, `/dossier/{id}/export` | PermRead check | | editor | `/dossier/{id}` | All viewer routes + `/dossier/{id}/edit`, `/dossier/{id}/upload`, `/dossier/{id}/trackers/respond` | PermWrite check | | manager | `/dossier/{id}/permissions` | All editor routes + `/dossier/{id}/permissions`, `/dossier/{id}/share`, `/dossier/{id}/revoke`, `/dossier/{id}/access/*` | PermManage check | | owner | `/dossier/{id}` | All routes for owned dossier (implicit full permissions) | Self-access (accessorID == dossierID) | | system | Any | All endpoints (RBAC bypassed) | Localhost (127.0.0.1 or ::1) | ### 7.4 Role-to-Code Mapping | Role/Permission | Middleware/Guards | Permission Checks | Storage Location | |---|---|---|---| | anon | None | No checks | N/A | | user | `getLoggedInDossier()` (portal), `getAccessContextOrFail()` (API) | Cookie or token exists | Cookie `login` or `Dossier.SessionToken` field | | viewer (PermRead) | `requireDossierAccess()`, `v1CanAccess()` | `CheckAccess(accessor, dossier, dossier, PermRead)` | `access` table: `Ops & 1 != 0` | | editor (PermWrite) | `requireEntryAccess(..., 'w')`, `v1CanWrite()` | `CheckAccess(accessor, dossier, entry, PermWrite)` | `access` table: `Ops & 2 != 0` | | deleter (PermDelete) | `requireEntryAccess(..., 'd')` | `CheckAccess(accessor, dossier, entry, PermDelete)` | `access` table: `Ops & 4 != 0` | | manager (PermManage) | `requireManageAccess()`, `CanManageDossier()` | `CheckAccess(accessor, dossier, dossier, PermManage)` | `access` table: `Ops & 8 != 0` | | owner (self) | None (implicit) | `accessorID == dossierID` | No storage (logic-based) | | system | `systemContextForLocalhost()`, `getAccessContextOrSystem()` | `RemoteAddr` starts with `127.0.0.1` or `[::1]` | No storage (IP-based) | **Code Locations:** - Portal auth: `/repos/inou-portal/portal/main.go:279-285` - API auth middleware: `/repos/inou-portal/api/auth.go:76-131` - V1 API auth: `/repos/inou-portal/api/api_v1.go:16-86` - Core RBAC: `/repos/inou-portal/lib/rbac.go:19-54` - Localhost bypass: `/repos/inou-portal/api/auth.go:151-156` ## 8. Authorization Vulnerability Candidates This section identifies specific endpoints and patterns that are prime candidates for authorization testing, organized by vulnerability type. ### 8.1 Horizontal Privilege Escalation Candidates Ranked list of endpoints with object identifiers that could allow access to other users' resources. | Priority | Endpoint Pattern | Object ID Parameter | Data Type | Sensitivity | Notes | |---|---|---|---|---|---| | **HIGH** | `GET /dossier/{id}` | `id` (dossierID) | Medical + PII | Complete health records | View other users' complete health dossiers | | **HIGH** | `GET /dossier/{id}/export` | `id` | Medical + PII | Data export | Download other users' complete data as JSON | | **HIGH** | `GET /api/v1/dossiers/{id}` | `id` | Medical + PII | Dossier metadata | API access to dossier details | | **HIGH** | `GET /api/v1/dossiers/{id}/entries` | `id` | Medical + PII | Medical records | List all medical entries in dossier | | **HIGH** | `GET /api/v1/dossiers/{id}/entries/{entryId}` | `id`, `entryId` | Medical + PII | Specific record | Access individual medical records | | **HIGH** | `GET /image/{id}` | `id` (entryID) | Medical-Imaging | DICOM images | Access medical imaging files (MRI, CT, X-ray) | | **HIGH** | `GET /contact-sheet.webp/{id}` | `id` (studyID) | Medical-Imaging | Image preview | Contact sheet for imaging studies | | **HIGH** | `GET /api/v1/images/{id}` | `id` | Medical-Imaging | Image file | API access to medical images | | **MEDIUM** | `GET /dossier/{id}/audit` | `id` | Audit logs | Access history | See who has accessed a dossier | | **MEDIUM** | `GET /api/v1/dossiers/{id}/access` | `id` | Access grants | Permission list | View access grants for dossier | | **MEDIUM** | `GET /dossier/{id}/trackers` | `id` | Daily trackers | Vitals, symptoms | Access daily health check-ins | | **MEDIUM** | `GET /api/v1/dossiers/{id}/trackers` | `id` | Tracker data | Health metrics | API access to tracker responses | | **MEDIUM** | `GET /api/v1/dossiers/{id}/journal` | `id` | Journal entries | Health notes | Access journal/diary entries | | **MEDIUM** | `GET /api/v1/dossiers/{id}/journal/{entryId}` | `id`, `entryId` | Journal entry | Personal notes | Specific journal entry | | **MEDIUM** | `GET /api/studies` | `dossier` (query) | Imaging studies | Medical metadata | List imaging studies for dossier | | **MEDIUM** | `GET /api/series` | `dossier` (query) | Imaging series | Medical metadata | List imaging series | | **MEDIUM** | `GET /api/slices` | `dossier`, `series` (query) | Image slices | Medical metadata | List individual image slices | | **MEDIUM** | `GET /api/labs/tests` | `dossier` (query) | Lab tests | Medical data | List lab test types | | **MEDIUM** | `GET /api/labs/results` | `dossier` (query) | Lab results | Medical data | Get lab test results | | **LOW** | `GET /dossier/{id}/files/{fileId}/status` | `id`, `fileId` | Processing status | Metadata | File processing status | **Testing Strategy:** 1. Create two user accounts (User A, User B) 2. User A creates a dossier with medical data 3. User B attempts to access User A's dossier using User A's dossier ID 4. Check if authorization is properly validated **Key IDOR Vectors:** - Dossier IDs are 16-character hex strings (e.g., `a1b2c3d4e5f67890`) - Entry IDs follow same format - IDs are predictable and enumerable - No rate limiting on ID enumeration ### 8.2 Vertical Privilege Escalation Candidates List endpoints requiring higher privileges, organized by target permission level. #### Escalation to PermWrite (from PermRead) | Endpoint Pattern | Functionality | Risk Level | |---|---|---| | `POST /dossier/{id}/edit` | Modify dossier profile (name, DOB, sex) | HIGH | | `POST /dossier/{id}/upload` | Upload medical files | HIGH | | `POST /dossier/{id}/trackers/respond` | Submit tracker responses | MEDIUM | | `POST /dossier/{id}/files/{fileId}/update` | Update file metadata | MEDIUM | | `POST /api/v1/dossiers/{id}/journal` | Create journal entries | MEDIUM | | `PATCH /api/v1/dossiers/{id}/journal/{entryId}` | Update journal entries | MEDIUM | | `POST /api/trackers/freeform` | Submit freeform tracker data | MEDIUM | | `POST /api/entries` | Create/update medical entries | HIGH | #### Escalation to PermDelete (from PermWrite) | Endpoint Pattern | Functionality | Risk Level | |---|---|---| | `DELETE /dossier/{id}/files/{fileId}/delete` | Delete uploaded files | HIGH | | `POST /api/entries` with `delete: true` | Delete medical entries | HIGH | | `POST /api/entries` with `delete_category: true` | Delete entire category of entries | CRITICAL | #### Escalation to PermManage (from PermDelete) | Endpoint Pattern | Functionality | Risk Level | |---|---|---| | `POST /dossier/{id}/permissions` | Grant access to other users | CRITICAL | | `POST /dossier/{id}/share` | Send access invitations | CRITICAL | | `POST /dossier/{id}/revoke` | Revoke access from users | HIGH | | `POST /dossier/{id}/rbac/{role}` | Edit role-based permissions | CRITICAL | | `POST /dossier/{id}/access/{granteeId}` | Edit specific access grants | CRITICAL | #### Escalation to Owner (from PermManage) | Attack Vector | Functionality | Risk Level | |---|---|---| | Modify own access grant to include all permissions | Self-escalation via permission manipulation | CRITICAL | | Grant PermManage to colluding user, have them grant back higher perms | Circular permission escalation | HIGH | ### 8.3 Context-Based Authorization Candidates Multi-step workflow endpoints that assume prior steps were completed or check state incorrectly. | Workflow | Endpoint | Expected Prior State | Bypass Potential | Risk | |---|---|---|---|---| | Onboarding | `POST /onboard` | Email verification completed | Direct POST with cookie of unverified dossier | MEDIUM | | File Processing | `POST /dossier/{id}/process-imaging` | Files uploaded via /upload | Direct call without upload, manipulate file paths | HIGH | | OAuth Authorization | `POST /oauth/token` | Authorization code issued | Code replay, code injection, PKCE bypass | MEDIUM | | Access Grant | `POST /dossier/{id}/permissions` | User has PermManage | Check if permission check happens before or after grant | HIGH | | Email Verification | `POST /verify` | Code sent via /send-code | Backdoor code 250365 always works (CRITICAL) | CRITICAL | | File Upload → Processing | 1) Upload, 2) Process | Files in database | Path traversal in `relPath` parameter between steps | CRITICAL | **Workflow State Bypass Opportunities:** 1. **Email Verification Backdoor:** Code `250365` always validates (testing backdoor left in production) - Location: `/repos/inou-portal/lib/dbcore.go:347`, `/repos/inou-portal/portal/api_mobile.go:128` - Impact: Complete authentication bypass 2. **File Processing Path Traversal:** Upload stores user-controlled `relPath`, later used unsafely in file operations - Location: `/repos/inou-portal/portal/upload.go:222` (storage), Line 455 (unsafe use) - Impact: Arbitrary file write on server filesystem 3. **OAuth State Validation:** Check if `state` parameter is properly validated to prevent CSRF - Location: `/repos/inou-portal/portal/oauth.go:62` - Impact: OAuth CSRF attacks 4. **Permission Modification While Accessing:** Check if concurrent permission changes affect ongoing requests - Impact: Time-of-check-time-of-use (TOCTOU) race conditions ### 8.4 Additional Authorization Testing Targets **SystemContext Bypass Vulnerabilities:** | Attack Vector | Location | Impact | |---|---|---| | Localhost IP spoofing (X-Forwarded-For, X-Real-IP headers) | `/repos/inou-portal/api/auth.go:151-156` | Full system access if headers trusted | | SystemAccessorID reuse (`7b3a3ee1c2776dcd`) | `/repos/inou-portal/lib/config.go:27`, `/repos/inou-portal/lib/rbac.go:20-22` | RBAC bypass if ID can be injected | | Empty accessorID injection | `/repos/inou-portal/lib/rbac.go:20-22` | RBAC bypass if validation missing | **Cross-Dossier Access:** | Scenario | Endpoints | Test | |---|---|---| | Multi-entry operations | `POST /api/entries` (bulk create) | Check if all entries validated for same dossier | | Access grant modification | `POST /dossier/{id}/access/{granteeId}` | Modify grants for dossiers not owned | | Audit log access | `GET /api/audit` | Access audit logs for other dossiers | ## 9. Injection Sources **TASK AGENT FINDING:** Comprehensive injection source analysis completed. Only network-accessible endpoints included. ### 9.1 Path Traversal / File Inclusion #### **CRITICAL: File Upload Path Traversal** **Severity:** HIGH **Endpoint:** `POST /dossier/{id}/upload` → `POST /dossier/{id}/process-imaging` **Network-Accessible:** ✅ Yes **Data Flow:** 1. **Input Vector:** `path` form parameter in file upload - File: `/repos/inou-portal/portal/upload.go:182-186` - Sanitization: `filepath.Base()` applied, BUT... 2. **Storage:** User-controlled path stored in `UploadData.RelPath` - File: `/repos/inou-portal/portal/upload.go:222` 3. **Dangerous Sink:** Path used unsafely in file write operation - File: `/repos/inou-portal/portal/upload.go:455` - Code: `outPath := filepath.Join(tempDir, relPath)` - Vulnerability: No validation that result stays within `tempDir` **Attack Payload:** ``` POST /dossier/1234567890abcdef/upload Content-Type: multipart/form-data ------WebKitFormBoundary Content-Disposition: form-data; name="path" ../../../../etc/cron.d/malicious ------WebKitFormBoundary Content-Disposition: form-data; name="file"; filename="evil.sh" * * * * * root /tmp/backdoor.sh ------WebKitFormBoundary-- ``` **Impact:** Arbitrary file write, potential remote code execution **Remediation Location:** `/repos/inou-portal/portal/upload.go:455` - Add path validation before `os.WriteFile()` --- ### 9.2 Command Injection #### **MEDIUM: Unvalidated Command Argument** **Severity:** MEDIUM **Endpoint:** `POST /dossier/{id}/process-imaging` **Network-Accessible:** ✅ Yes **Data Flow:** 1. **Input Vector:** `{id}` path parameter (extracted from URL) - File: `/repos/inou-portal/portal/upload.go:388` - Code: `targetID := parts[2]` 2. **Dangerous Sink:** Passed to external command without validation - File: `/repos/inou-portal/portal/upload.go:472` - Code: `cmd := exec.Command("/tank/inou/bin/import-dicom", targetID, tempDir)` **Mitigation Present:** Go's `exec.Command()` does NOT use shell interpretation, preventing classic command injection (`;`, `|`, `&&`, etc.) **Remaining Risk:** If `/tank/inou/bin/import-dicom` interprets `targetID` unsafely, exploitation possible **Recommendation:** Validate `targetID` is 16-character hexadecimal: `/repos/inou-portal/portal/upload.go:388` --- ### 9.3 SQL Injection **Status:** ✅ NOT VULNERABLE **Finding:** All database queries use parameterized statements **Safe Pattern Example:** ```go // /repos/inou-portal/lib/dbcore.go:166-202 q := "SELECT ... FROM entries WHERE DossierID = ?" args := []any{dossierID} if f.Type != "" { q += " AND Type = ?" args = append(args, Pack([]byte(f.Type))) } rows, err := db.Query(q, args...) ``` **Safety Mechanisms:** - User input passed via `?` placeholders - Dynamic SQL construction limited to table/column names (not user-controlled) - ORM layer encrypts data before parameterization **Code Locations:** All queries in `/repos/inou-portal/lib/db_queries.go`, `/repos/inou-portal/lib/dbcore.go` --- ### 9.4 Server-Side Template Injection (SSTI) **Status:** ✅ NOT VULNERABLE **Finding:** Templates pre-loaded from disk, user data passed as structured objects **Safe Pattern:** ```go // /repos/inou-portal/portal/main.go:230 templates = template.Must(template.New("").Funcs(funcs).ParseGlob(filepath.Join(tmplDir, "*.tmpl"))) // /repos/inou-portal/portal/upload.go:150 templates.ExecuteTemplate(w, "base.tmpl", data) ``` **Safety Mechanisms:** - Templates loaded at startup (not runtime) - User data in `PageData` structs rendered with auto-escaping - No dynamic template parsing of user input --- ### 9.5 Unsafe Deserialization **Status:** ✅ NOT VULNERABLE **Finding:** Go's `encoding/json` library is safe (no code execution during unmarshal) **Analysis:** ```go // /repos/inou-portal/api/api_entries.go:71-72 var requests []EntryRequest decoder := json.NewDecoder(r.Body) if err := decoder.Decode(&requests); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return } ``` **Why Safe:** Unlike Python pickle or Java serialization, Go's JSON unmarshaling only populates struct fields without executing code --- ### 9.6 Other Injection Vectors #### **LDAP/XML/XPath Injection** **Status:** ✅ NOT APPLICABLE - No LDAP, XML, or XPath processing found #### **NoSQL Injection** **Status:** ✅ NOT APPLICABLE - Uses SQLite with parameterized queries only #### **Email Header Injection** **Status:** ⚠️ LOW RISK - Email library likely sanitizes, but not verified - Email sending: `/repos/inou-portal/portal/main.go:429-436` (sendCodeEmail) - User-controlled: Email address and verification code - Recommendation: Verify SMTP library sanitizes headers --- ### 9.7 Summary of Injection Findings | Type | Status | Severity | Location | Network-Accessible | |---|---|---|---|---| | Path Traversal | ❌ VULNERABLE | HIGH | `/repos/inou-portal/portal/upload.go:455` | ✅ Yes | | Command Injection | ⚠️ PARTIAL | MEDIUM | `/repos/inou-portal/portal/upload.go:472` | ✅ Yes | | SQL Injection | ✅ SAFE | N/A | All queries use parameterization | ✅ Yes | | SSTI | ✅ SAFE | N/A | Templates pre-loaded | ✅ Yes | | Deserialization | ✅ SAFE | N/A | Go JSON is safe | ✅ Yes | | Email Header Injection | ⚠️ UNKNOWN | LOW | `/repos/inou-portal/portal/main.go:429` | ✅ Yes | **Critical Remediation Needed:** 1. File upload path traversal validation (HIGH priority) 2. Command argument validation (MEDIUM priority) 3. Email header sanitization verification (LOW priority)