clawd/memory/shannon-scan-2026-02-14/deliverables/authz_analysis_deliverable.md

17 KiB

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:

// /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:

// /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:

// /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:

// /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:
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:

// /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