# 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)

