# Authorization Analysis Report

## 1. Executive Summary

- **Analysis Status:** Complete
- **Key Outcome:** Multiple critical authorization vulnerabilities discovered across horizontal IDOR, vertical privilege escalation, and permission management. All high-confidence findings have been passed to the exploitation phase via the machine-readable exploitation queue.
- **Purpose of this Document:** This report provides the strategic context, dominant patterns, and architectural intelligence necessary to effectively exploit the vulnerabilities listed in the queue. It is intended to be read alongside the JSON deliverable.

**Critical Findings Overview:**
- **10 High-Severity Horizontal IDOR vulnerabilities** - Users can access other users' complete health records, medical images, and audit logs
- **3 Critical Vertical Escalation vulnerabilities** - Users with read-only access can write data; users can self-escalate permissions
- **1 Permission Management bypass** - Users with PermWrite can delete files without PermDelete permission

**Root Cause:** The application uses stub library functions that **completely ignore AccessContext parameters**, creating a single layer of defense at the web/API layer. When this layer is missing or incomplete, no secondary authorization exists.

## 2. Dominant Vulnerability Patterns

### Pattern 1: Stub Functions Ignore AccessContext (Horizontal + Vertical)

**Description:** The application's core library in `/repos/inou-portal/lib/stubs.go` contains stub implementations that accept `AccessContext` parameters for authorization but completely ignore them, directly querying the database without any permission checks.

**Affected Functions:**
- `lib.DossierGet(ctx, id)` - Ignores `ctx`, directly calls `entryQuery()` (lines 196-218)
- `lib.EntryQuery(ctx, ...)` - Ignores `ctx`, directly calls `entryQuery()` (lines 92-102)
- `lib.EntryList(accessorID, ...)` - Ignores `accessorID`, directly calls `entryQuery()` (lines 104-113)
- `lib.EntryGet(ctx, id)` - Stub returns "not implemented" (lines 87-90)
- `lib.AuditList(filter)` - No AccessContext parameter at all (lines 480-483)

**Implication:** Any endpoint that relies solely on these stub functions for authorization has no actual access control. The web/API layer must enforce all authorization, creating a single point of failure.

**Representative Vulnerabilities:**
- AUTHZ-VULN-01: GET /dossier/{id}/export bypasses authorization
- AUTHZ-VULN-02: GET /api/v1/dossiers/{id} bypasses authorization
- AUTHZ-VULN-03: GET /api/v1/dossiers/{id}/entries bypasses authorization
- AUTHZ-VULN-08: GET /dossier/{id}/audit bypasses authorization

**Code Evidence:**
```go
// /repos/inou-portal/lib/stubs.go:196-218
func DossierGet(ctx *AccessContext, id string) (*Dossier, error) {
    entries, err := entryQuery(id, &Filter{Category: 0})  // ctx parameter IGNORED
    if err != nil || len(entries) == 0 {
        return nil, fmt.Errorf("dossier not found: %s", id)
    }
    // ... returns data without any authorization check
}
```

### Pattern 2: Passing nil or SystemAccessorID Bypasses All Checks (Horizontal)

**Description:** Several endpoints explicitly pass `nil` as the AccessContext or use `lib.SystemAccessorID`, which causes the RBAC system in `lib.CheckAccess()` to immediately return `true`, bypassing all permission checks.

**Bypass Mechanism:**
```go
// /repos/inou-portal/lib/rbac.go:19-24
func CheckAccess(accessorID, dossierID, entryID string, perm int) bool {
    if accessorID == "" || accessorID == SystemAccessorID {
        return true  // BYPASS ALL CHECKS
    }
    // ... actual permission checks
}
```

**Affected Endpoints:**
- `GET /contact-sheet.webp/{id}` - Passes `nil` to `EntryGet(nil, series.ParentID)` and `DossierGet(nil, dossierID)`
- `GET /api/v1/images/{id}` - Passes `nil` to `EntryGet(nil, id)` and `ImageGet(nil, id, opts)`
- `GET /api/v1/dossiers/{id}/entries` - Uses `lib.SystemAccessorID` for `EntryList(SystemAccessorID, ...)`

**Representative Vulnerabilities:**
- AUTHZ-VULN-05: GET /contact-sheet.webp/{id} passes nil context
- AUTHZ-VULN-06: GET /api/v1/images/{id} passes nil context
- AUTHZ-VULN-03: GET /api/v1/dossiers/{id}/entries uses SystemAccessorID

### Pattern 3: Missing Write Permission Checks (Vertical Escalation)

**Description:** Write and modify endpoints fail to validate PermWrite permission before performing state-changing operations, allowing users with only PermRead (view-only) access to create, update, or delete data.

**Implication:** Users granted read-only access to view a family member's health records can modify that data, violating the principle of least privilege.

**Representative Vulnerabilities:**
- AUTHZ-VULN-11: POST /dossier/{id}/trackers/respond - No authorization check at all
- AUTHZ-VULN-12: POST /api/entries - No PermWrite validation before create/update/delete

**Code Evidence:**
```go
// /repos/inou-portal/portal/trackers.go:348-399
func handleTrackerRespond(w http.ResponseWriter, r *http.Request) {
    p := getLoggedInDossier(r)  // Only checks authentication
    if p == nil {
        http.Redirect(w, r, "/", http.StatusSeeOther)
        return
    }
    // NO CHECK for PermWrite on targetHex!
    // Directly allows POST to API without authorization
}
```

### Pattern 4: Permission Escalation via Self-Granting (Vertical Escalation)

**Description:** The permission management endpoint allows users with PermManage to grant permissions to themselves, and to grant permissions they don't possess, enabling privilege escalation.

**Implication:** A user with limited PermManage access (e.g., on a child's health record) can grant themselves PermDelete to erase medical history, without proper oversight.

**Representative Vulnerabilities:**
- AUTHZ-VULN-13: POST /dossier/{id}/permissions allows self-escalation
- AUTHZ-VULN-14: DELETE /dossier/{id}/files/{fileId}/delete allows PermWrite users to delete

**Code Evidence:**
```go
// /repos/inou-portal/portal/main.go:1403-1442
// No check that grantee.DossierID != p.DossierID
// No validation that ops_to_grant ⊆ grantor_current_ops
if role == "custom" {
    if r.FormValue("op_w") == "1" { ops |= lib.PermWrite }
    if r.FormValue("op_d") == "1" { ops |= lib.PermDelete }  // Can grant PermDelete without having it
    if r.FormValue("op_m") == "1" { ops |= lib.PermManage }
}
```

## 3. Strategic Intelligence for Exploitation

### Session Management Architecture

**Session Token Storage:**
- Portal uses cookie named `login` containing plaintext dossier ID (16-char hex)
- Cookie attributes: `HttpOnly=true, Secure=true, SameSite=Lax`
- Mobile API uses session tokens stored in `Dossier.SessionToken` field (64-char hex, never expires)
- OAuth uses encrypted access tokens (15-minute expiry) with AES-256-GCM

**Critical Finding:** The `login` cookie value is the dossier ID itself, making it predictable if the ID generation algorithm is weak. Session tokens for mobile API never expire, creating long-lived access.

**Token Extraction Points:**
- Portal: `getLoggedInDossier(r)` reads `login` cookie at `/repos/inou-portal/portal/main.go:279-285`
- API: `getAccessContextOrFail(w, r)` reads Bearer token from `Authorization` header at `/repos/inou-portal/api/auth.go:76-86`
- V1 API: `v1GetAuth(r)` extracts from Bearer token or `X-API-Token` header at `/repos/inou-portal/api/api_v1.go:27-59`

### Role/Permission Model

**Permission Bitmask System:**
- PermRead = 1 (bit 0) - View dossier data
- PermWrite = 2 (bit 1) - Modify dossier data
- PermDelete = 4 (bit 2) - Delete entries
- PermManage = 8 (bit 3) - Grant access to others

**Permission Storage:**
- Database table: `access` (columns: AccessorID, DossierID, GranteeID, EntryID, Relation, Ops, CreatedAt)
- Encrypted at rest with AES-256-GCM
- No caching - permissions checked on every request via database query

**Critical Finding:**
1. Self-access (when `accessorID == dossierID`) grants implicit full permissions without database lookup
2. `SystemAccessorID` (value: `7b3a3ee1c2776dcd`) bypasses all RBAC checks
3. Empty string `""` as accessorID also bypasses all checks
4. Localhost requests (127.0.0.1 or ::1) automatically get SystemContext

**Permission Check Functions:**
- Core RBAC: `CheckAccess(accessorID, dossierID, entryID, perm)` at `/repos/inou-portal/lib/rbac.go:19-54`
- V1 API: `v1CanAccess(authID, targetID)` at `/repos/inou-portal/api/api_v1.go:61-70` (checks ANY access grant exists)
- V1 API: `v1CanWrite(authID, targetID)` at `/repos/inou-portal/api/api_v1.go:72-86` (checks PermWrite bit)
- Portal: `lib.CanManageDossier(accessorID, dossierID)` at `/repos/inou-portal/lib/rbac.go:56-58` (checks PermManage bit)

### Resource Access Patterns

**Dossier ID Format:**
- 16-character hexadecimal string (e.g., `a1b2c3d4e5f67890`)
- Generated via `lib.NewID()` using `crypto/rand` - cryptographically random
- **However:** Demo dossier uses hardcoded ID `1111111111111111`

**Entry ID Format:**
- Same 16-character hex format as dossier IDs
- Used for medical records, files, imaging studies, journal entries

**Critical Finding:** While IDs are cryptographically random, they are exposed in:
- URL paths (e.g., `/dossier/{id}`)
- API responses (e.g., list endpoints return entry IDs)
- Redirect URLs after login
- Error messages may leak IDs through timing attacks

**Database Access Pattern:**
- Most vulnerable endpoints call `entryQuery(dossierID, filter)` which directly executes:
```sql
SELECT EntryID, DossierID, ParentID, Category, Type, Value, Summary, Ordinal,
       Timestamp, TimestampEnd, Status, Tags, Data, SearchKey
FROM entries WHERE DossierID = ?
```
- No additional WHERE clauses for authorization - relies on application layer

### Localhost Bypass Mechanism

**Critical System-Level Bypass:**
```go
// /repos/inou-portal/api/auth.go:151-156
func systemContextForLocalhost(r *http.Request) *lib.AccessContext {
    if isLocalhost(r) {
        return lib.SystemContext  // Full system access
    }
    return nil
}

func isLocalhost(r *http.Request) bool {
    ip := strings.Split(r.RemoteAddr, ":")[0]
    return ip == "127.0.0.1" || ip == "[::1]"
}
```

**Implication:** Any request from localhost bypasses ALL authorization checks. This is used by:
- `/api/access` endpoint (localhost only)
- `/api/entries` endpoint (calls `getAccessContextOrSystem`)
- Internal service-to-service calls

**Exploitation Note:** If `X-Forwarded-For` or similar proxy headers are trusted without validation, remote attackers could spoof localhost IP to gain system access.

### Workflow Implementation

**Authentication Flow:**
1. User submits email → 6-digit code sent
2. User submits code → validated (or backdoor code `250365` always works)
3. Session cookie created with dossier ID
4. Redirect to /dashboard or /onboard

**Critical Finding:** Hardcoded backdoor verification code `250365` exists in production:
- Location: `/repos/inou-portal/lib/dbcore.go:347` (portal), `/repos/inou-portal/portal/api_mobile.go:128` (mobile API)
- Impact: Anyone can authenticate as any email address without actually receiving the verification code

**File Upload → Processing Flow:**
1. Upload file → Entry created with status="uploaded"
2. Call `/dossier/{id}/process-imaging` → Status changed to "processing"
3. External DICOM binary processes files
4. Status updated to "done"

**Critical Finding:** Files can be processed multiple times if endpoint called concurrently (no locking mechanism). Status transitions are not atomic.

## 4. Vectors Analyzed and Confirmed Secure

These authorization checks were traced and confirmed to have robust, properly-placed guards. They are **low-priority** for further testing.

| **Endpoint** | **Guard Location** | **Defense Mechanism** | **Verdict** |
|--------------|-------------------|----------------------|-------------|
| `GET /dossier/{id}` | `/repos/inou-portal/lib/dbcore.go:127` | lib.EntryRead enforces PermRead BEFORE database query via CheckAccess | SAFE |
| `POST /onboard` | `/repos/inou-portal/portal/main.go:594-596` | Requires valid login cookie (email verification completed) | SAFE |
| `POST /oauth/token` | `/repos/inou-portal/portal/oauth.go:144-296` | OAuth 2.0 flow with PKCE validation, single-use codes, expiry checks | SAFE |
| `GET /api/v1/dossiers/{id}/journal/{entryId}` | `/repos/inou-portal/lib/journal.go:195-197` | lib.GetJournal validates entry.DossierID matches requested dossierID | SAFE |
| `POST /api/v1/dossiers/{id}/journal` | `/repos/inou-portal/api/api_v1.go:810-813` | v1CanWrite validates PermWrite via access table query | SAFE |
| `PATCH /api/v1/dossiers/{id}/journal/{entryId}` | `/repos/inou-portal/api/api_v1.go:870-873` | v1CanWrite validates PermWrite before update | SAFE |
| `POST /dossier/{id}/edit` | `/repos/inou-portal/portal/main.go:986-996` | Checks CanEdit (PermWrite) via getAccess before allowing profile edits | SAFE |
| `POST /dossier/{id}/upload` | `/repos/inou-portal/portal/upload.go:166-172` | Checks CanEdit (PermWrite) via getAccess before allowing file uploads | SAFE |

**Note on "SAFE" Verdicts:** These endpoints enforce authorization at the web/API layer. While they lack defense-in-depth (the underlying lib functions are stubs or ignore context), the primary authorization layer is correctly implemented and sufficient to block unauthorized access under normal conditions.

## 5. Analysis Constraints and Blind Spots

### Limitations Encountered During Analysis

**1. Stub Function Implementations:**
- Multiple critical library functions (`DossierGet`, `EntryGet`, `EntryQuery`, `EntryList`) are marked as stubs with comments like `[STUB]` or "needs migration to EntryRead"
- These stubs either return errors or ignore security parameters
- Impossible to determine if future implementations will maintain current security posture
- **Impact:** Some vulnerabilities may be mitigated when stubs are replaced with proper implementations

**2. External Service Authorization:**
- The application makes HTTP calls to internal services (e.g., `http://localhost:8082/api/*`)
- Authorization for these backend APIs was analyzed, but the network boundary between portal and API server could not be verified
- **Assumption:** Internal API endpoints are only accessible from localhost (not directly from internet)
- **Impact:** If internal APIs are exposed, additional attack surface exists

**3. Concurrent Access and Race Conditions:**
- Static code analysis cannot definitively identify race conditions in permission checks
- Example: File processing status transitions are not atomic
- Example: Permission grants could have TOCTOU vulnerabilities if checked and used separately
- **Impact:** Some timing-based bypasses may exist but were not confirmed

**4. Dynamic Permission System:**
- The `access` table can be modified at runtime to grant/revoke permissions
- The analysis assumes permissions are relatively static during a request
- If permissions change mid-request, behavior is undefined
- **Impact:** Complex multi-step workflows may have consistency issues

**5. OAuth Client Registration:**
- OAuth clients can be dynamically registered via `/register` endpoint
- Analysis focused on authorization, not OAuth client validation
- Malicious client registration could potentially bypass intended access patterns
- **Impact:** OAuth-specific attacks (client impersonation, redirect URI manipulation) were not fully explored

**6. Encrypted Data Access:**
- All database data and uploaded files are encrypted with AES-256-GCM
- Authorization analysis assumes encryption keys are properly protected
- If master key at `/tank/inou/master.key` is compromised, all data is exposed regardless of authorization
- **Impact:** Physical/infrastructure security is out of scope but critical

**7. Localhost Trust Boundary:**
- Many endpoints trust requests from 127.0.0.1/::1 as "system" access
- Analysis assumes no proxy header spoofing is possible
- If `X-Forwarded-For`, `X-Real-IP`, or similar headers are trusted, localhost bypass may be remotely exploitable
- **Impact:** Need to verify reverse proxy configuration in production

### Areas Not Fully Analyzed

**GraphQL Endpoints:**
- Reconnaissance report mentions potential GraphQL, but no GraphQL-specific endpoints were found in the codebase analyzed
- If GraphQL exists, it may have separate authorization logic

**WebSocket/SSE Connections:**
- Real-time features may use WebSockets or Server-Sent Events
- Authorization for long-lived connections was not analyzed
- Permission changes during active connections may not be enforced

**MCP (Model Context Protocol) Authorization:**
- The `/mcp` endpoint provides AI assistant access via OAuth
- MCP-specific tools and prompts have their own authorization
- This analysis focused on dossier/entry access, not MCP-specific operations

**Background Job Authorization:**
- File processing, genome parsing, and LLM queries run asynchronously
- Authorization context for background jobs was not fully traced
- Jobs may retain elevated permissions even after user permissions are revoked





