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

74 KiB

Penetration Test Scope & Boundaries

Primary Directive: This analysis is strictly limited to the network-accessible attack surface of the application. All subsequent tasks must adhere to this scope. Before reporting any finding (e.g., an entry point, a vulnerability sink), it must first meet the "In-Scope" criteria.

In-Scope: Network-Reachable Components

A component is considered in-scope if its execution can be initiated, directly or indirectly, by a network request that the deployed application server is capable of receiving. This includes:

  • Publicly exposed web pages and API endpoints.
  • Endpoints requiring authentication via the application's standard login mechanisms.
  • Any developer utility, debug console, or script that has been mistakenly exposed through a route or is otherwise callable from other in-scope, network-reachable code.

Out-of-Scope: Locally Executable Only

A component is out-of-scope if it cannot be invoked through the running application's network interface and requires an execution context completely external to the application's request-response cycle. This includes tools that must be run via:

  • A command-line interface (e.g., go run ./cmd/..., python scripts/...).
  • A development environment's internal tooling (e.g., a "run script" button in an IDE).
  • CI/CD pipeline scripts or build tools (e.g., Dagger build definitions).
  • Database migration scripts, backup tools, or maintenance utilities.
  • Local development servers, test harnesses, or debugging utilities.
  • Static files or scripts that require manual opening in a browser (not served by the application).

1. Executive Summary

The inou-portal is a comprehensive medical data management platform (Healthcare EHR/PHR system) built in Go 1.24, serving as a hybrid architecture combining a web portal, RESTful API backend, and Model Context Protocol (MCP) server for AI integration. The application handles highly sensitive Protected Health Information (PHI) including DICOM medical imaging, laboratory results, genomic data, medications, and patient demographics.

From a security perspective, the application demonstrates strong defense-in-depth for data at rest through comprehensive AES-256-GCM field-level encryption and a well-designed RBAC permission model with entry-level granularity. However, critical vulnerabilities exist in authentication mechanisms (hardcoded backdoor authentication code), secret management (plaintext API keys in URLs and filesystem), and transport security (HTTP-only deployment without TLS). The OAuth 2.0 implementation follows modern standards with PKCE support, but refresh tokens are stored in plaintext rather than hashed.

Most Critical Security Findings:

  1. CRITICAL: Hardcoded authentication bypass code (250365) allows universal access without valid OTP
  2. HIGH: Deterministic nonce generation in AES-GCM encryption violates cryptographic best practices and enables pattern analysis attacks
  3. HIGH: XSS vulnerabilities in DICOM viewer via unsanitized database content in innerHTML operations
  4. HIGH: API keys transmitted in URL query parameters (visible in logs, browser history, referer headers)
  5. HIGH: Localhost authentication bypass grants unrestricted system access to any process on 127.0.0.1
  6. HIGH: HTTP-only deployment exposes session cookies and OAuth tokens in plaintext during transit

The attack surface includes 40+ API endpoints across two servers (Portal on port 8080, API on port 8082), with OAuth 2.0 integration for Claude AI assistants and external LLM APIs (Google Gemini, Anthropic Claude). The application processes user-uploaded DICOM medical imaging files and implements complex multi-tenant data isolation, making it a high-value target for both credential theft and healthcare data exfiltration.


2. Architecture & Technology Stack

Application Architecture Overview

The inou-portal implements a monolithic application with microservices-like component separation, designed specifically for personal health record management with HIPAA/GDPR compliance considerations. The architecture consists of three primary runtime components: (1) Portal Server - user-facing web application with OAuth/session management on port 8080, (2) API Server - RESTful backend for medical data access on port 8082, and (3) MCP HTTP Server - Claude AI integration endpoint implementing RFC 9728 OAuth Protected Resource specification.

The Portal Server acts as a reverse proxy to the internal API backend, forwarding requests from /api/* to http://127.0.0.1:8082 over unencrypted HTTP localhost connections. This design creates an important trust boundary: the portal handles user authentication and session management, while the API backend enforces RBAC permissions on data access. The internal proxy pattern allows unified external interface while separating concerns, but relies on the security assumption that localhost communication cannot be intercepted.

The application follows a serverless-adjacent deployment model without containerization - no Docker, Kubernetes, or orchestration infrastructure was found in the codebase. Deployment appears designed for bare-metal or VM-based hosting with hardcoded filesystem paths (/tank/inou/) suggesting dedicated storage volumes. This architectural decision simplifies deployment but increases operational security requirements, as the application depends entirely on host OS security controls for process isolation and filesystem permissions.

Technology Stack Analysis

Core Technologies:

  • Backend Language: Go 1.24.0 (toolchain 1.24.4) with standard library net/http server
  • Frontend: Vanilla JavaScript with server-side Go html/template rendering (no React/Vue/Angular framework detected)
  • Database: SQLite3 v1.14.22 with Write-Ahead Logging (WAL) mode enabled for concurrent access
  • Cryptography: golang.org/x/crypto v0.46.0 with optional FIPS 140-3 mode support

Security-Critical Dependencies:

  • golang.org/x/crypto - Provides AES-GCM encryption primitives for field-level database encryption
  • github.com/mattn/go-sqlite3 - SQLite3 driver with CGo bindings (introduces C code into attack surface)
  • github.com/chai2010/webp - Image processing for medical imaging contact sheets (potential buffer overflow risks in image parsing)
  • golang.org/x/image - Additional image processing libraries

The choice of SQLite as the primary database rather than PostgreSQL/MySQL is architecturally significant for security analysis. SQLite operates entirely in-process without network exposure, eliminating an entire class of network-based SQL injection attacks. However, it creates a single-file database that becomes a high-value target for exfiltration. The application uses three separate SQLite databases for security compartmentalization: (1) inou.db for medical data with field-level encryption, (2) auth.db for ephemeral OAuth tokens and sessions, and (3) reference.db for lab test reference values and genomic data tiers.

Architectural Security Implications

The hybrid monolith + internal API pattern creates both security advantages and risks. Advantages include simplified authentication (single session layer), reduced network attack surface (no external database port), and atomic deployments. However, the localhost proxy pattern introduces a critical vulnerability: the /api/access and /api/audit endpoints implement localhost-only authentication that grants unrestricted SystemContext access to any process originating from 127.0.0.1 or [::1]. This design assumes perfect host-level security - any port hijacking, container escape, or malicious local service can bypass all RBAC controls.

The application implements passwordless authentication using email-based One-Time Passcodes (OTP) rather than traditional password hashing. This architectural decision eliminates password database breaches but introduces email delivery reliability as a security dependency. The OTP implementation uses cryptographically secure random number generation for 6-digit codes with 10-minute expiration windows, providing approximately 1,000,000 possible values per authentication attempt. However, no rate limiting was observed on the /verify endpoint, making brute-force attacks theoretically feasible (though impractical at 0.0001% success per attempt).

The Model Context Protocol (MCP) integration for Claude AI assistants represents a novel attack surface. The application implements dynamic OAuth 2.0 client registration (RFC 7591) allowing Claude to register as an OAuth client and access patient medical data through structured tool calls. This creates a trust boundary where AI assistants can query sensitive health information - the security posture depends entirely on the OAuth scope validation and RBAC enforcement, which appear properly implemented but create a significant data exfiltration risk if OAuth tokens are compromised.


3. Authentication & Authorization Deep Dive

Authentication Mechanisms & API Endpoints

The inou-portal implements four distinct authentication methods serving different client types: (1) Email-based passwordless OTP for web browsers, (2) OAuth 2.0 Authorization Code with PKCE for mobile applications and AI assistants, (3) Long-lived session tokens for API access from the dashboard, and (4) Short-lived encrypted bearer tokens (15 minutes for OAuth, 4 hours for API v1).

Primary Authentication Endpoints:

Email OTP Flow (Web Portal):

  • GET /send-code - Initiates authentication by generating 6-digit OTP and sending via SMTP

    • File: /repos/inou-portal/portal/main.go:540-557
    • Input validation: Email lowercase + trim, nonce parameter ≥2000 (bot protection)
    • Side effects: Creates/loads dossier by email, generates cryptographically random 6-digit code
    • Storage: OTP stored as encrypted Entry in database with 10-minute TTL
    • CRITICAL: Backdoor code 250365 bypasses authentication (line 128 in portal/api_mobile.go)
  • GET /verify - Validates OTP and establishes session

    • File: /repos/inou-portal/portal/main.go:592-630
    • Validation: Constant-time comparison required (line 347 in lib/dbcore.go)
    • Session creation: Sets login cookie (HttpOnly, Secure, SameSite=Lax, 30-day expiration)
    • VULNERABILITY: No rate limiting - allows brute-force attempts on 1M code space

OAuth 2.0 Endpoints (Mobile & AI Integration):

  • GET /oauth/authorize - Authorization endpoint with PKCE support

    • File: /repos/inou-portal/portal/oauth.go:50-140
    • Parameters: client_id, redirect_uri, response_type=code, state, code_challenge, code_challenge_method=S256
    • Validation: Redirect URI whitelist matching (exact string comparison), PKCE required for public clients
    • Code generation: 32-byte random authorization code with 10-minute expiration
    • Security: Implements OAuth 2.0 security best practices (state parameter, PKCE S256)
  • POST /oauth/token - Token issuance endpoint

    • File: /repos/inou-portal/portal/oauth.go:142-294
    • Grant types: authorization_code, refresh_token
    • PKCE verification: SHA-256 hash comparison of code_verifier against stored code_challenge
    • Returns: access_token (15 min encrypted JWT-like token), refresh_token (30 days, database-stored)
    • VULNERABILITY: Refresh tokens stored in plaintext in auth.db rather than hashed
  • GET /oauth/userinfo - User profile endpoint (OpenID Connect compatibility)

    • File: /repos/inou-portal/portal/oauth.go:298-335
    • Authentication: Bearer token required
    • Returns: {sub, email, name, email_verified} (minimal PII exposure)
  • POST /oauth/revoke - Token revocation endpoint

    • File: /repos/inou-portal/portal/oauth.go:337-373
    • Implements refresh token rotation: old token revoked, new token created
    • GAP: Access tokens cannot be revoked (stateless encrypted tokens with no revocation list)

API Token Endpoints:

  • GET /api/token/generate - Creates long-lived session token for API access

    • File: /repos/inou-portal/portal/main.go:726-746
    • Authentication: Session cookie required
    • Storage: Encrypted SessionToken field in Dossier record
    • ISSUE: No expiration or rotation mechanism
  • POST /api/v1/token - Generates 4-hour encrypted access token

    • File: /repos/inou-portal/api/api_v1.go:105-132
    • Authentication: Session token or existing bearer token required
    • Token format: AES-256-GCM encrypted JSON {d: dossier_id, exp: unix_timestamp}
    • VULNERABILITY: Deterministic nonce in encryption (same payload = same ciphertext)

Mobile API Authentication:

  • POST /api/v1/auth/send - Mobile OTP request endpoint

    • File: /repos/inou-portal/portal/api_mobile.go:55-103
    • CORS: Wildcard Access-Control-Allow-Origin: * (excessive permission)
    • Request: {email} (JSON body required)
    • Response: {success: bool} (no timing side-channel protection observed)
  • POST /api/v1/auth/verify - Mobile OTP verification

    • File: /repos/inou-portal/portal/api_mobile.go:105-149
    • CRITICAL VULNERABILITY (Line 128): Hardcoded backdoor authentication
      if code != 250365 && (d.AuthCode != code || ...)  // TODO: Remove backdoor
      
    • Returns: {token: session_token, needs_onboard: bool}
    • Session token persisted indefinitely in database

Login Cookie Configuration (/repos/inou-portal/portal/main.go:311-317):

http.SetCookie(w, &http.Cookie{
    Name:     "login",
    Value:    dossierID,           // Plain dossier GUID (16-char hex)
    Path:     "/",
    MaxAge:   30*24*60*60,         // 30 days
    HttpOnly: true,                // ✓ Prevents JavaScript access
    Secure:   true,                // ✓ HTTPS-only (but app runs HTTP)
    SameSite: http.SameSiteLaxMode, // ✓ Partial CSRF protection
})

Session Security Analysis:

  • Cookie value is the raw dossier ID - predictable if GUID format is known (128-bit hex should be sufficient entropy)
  • HttpOnly flag: Properly set to prevent XSS-based cookie theft
  • Secure flag: Set but ineffective since application runs on HTTP without TLS (line 1964 in portal/main.go)
  • SameSite=Lax: Provides CSRF protection for navigation requests but not AJAX/fetch POST requests
  • No CSRF token validation: State-changing operations rely solely on SameSite cookie attribute
  • No session refresh: 30-day static cookie with no rotation on activity

Session Token Format (Encrypted Bearer Tokens): The application uses AES-256-GCM encrypted tokens storing {dossier_id, expiration} in JSON format. Token creation in /repos/inou-portal/lib/crypto.go:172-182:

func TokenCreate(dossierID string, duration time.Duration) (string, error) {
    token := Token{DossierID: dossierID, Exp: time.Now().Add(duration).Unix()}
    data, _ := json.Marshal(token)
    encrypted := CryptoEncryptBytes(data)  // AES-256-GCM with DETERMINISTIC nonce
    return base64.RawURLEncoding.EncodeToString(encrypted), nil
}

CRITICAL VULNERABILITY: The encryption uses a deterministic nonce derived from plaintext content (lines 46-62 in lib/crypto.go). This violates GCM security requirements - the same plaintext will always produce the same ciphertext, enabling:

  • Pattern analysis to identify identical dossier IDs across tokens
  • Chosen-ciphertext attacks to decrypt token contents
  • Replay attacks with captured tokens (within expiration window)

Authorization Model & RBAC Implementation

The application implements a bitmask-based permission system with four levels defined in /repos/inou-portal/lib/rbac.go:10-15:

const (
    PermRead   = 1  // Bit 0: Read access to entries
    PermWrite  = 2  // Bit 1: Create/update entries
    PermDelete = 4  // Bit 2: Delete entries
    PermManage = 8  // Bit 3: Manage access grants
)

Access Control Logic (CheckAccess function, lines 19-54):

  1. System Accessor Bypass: If accessorID == SystemAccessorID (default 7b3a3ee1c2776dcd), grant all permissions
  2. Owner Access: If accessorID == dossierID, grant all permissions (users control their own data)
  3. Grant Lookup: Query access table for explicit permission grants:
    SELECT * FROM access WHERE GranteeID = ? AND DossierID = ?
    
  4. Entry-Level Permissions: Check if grant applies to specific entry or entire dossier
  5. Bitmask Validation: Verify (grant.Ops & requiredPermission) != 0

Multi-Tenancy Security: The access control model supports fine-grained sharing with relationship types (enumeration in types):

  • RelationParent = 1 - Parent accessing child's records
  • RelationGuardian = 4 - Legal guardian
  • RelationCaregiver = 5 - Authorized caregiver
  • RelationMedical = 7 - Healthcare provider

Each grant can specify permissions at the dossier level (all entries) or entry level (specific medical records), enabling scenarios like "careg iver can view all data but only medical provider can edit diagnoses."

CRITICAL SECURITY GAP - Revocation Functions Are Stubs:

func RevokeAccess(grantor, grantee, target string) error {
    log.Printf("[STUB] RevokeAccess not implemented")
    return nil
}

The revocation implementation is incomplete (/repos/inou-portal/lib/rbac.go:91-104), meaning access grants cannot be removed once created. This violates the principle of least privilege and creates compliance risks for HIPAA's minimum necessary requirement.

SSO/OAuth/OIDC Flow Analysis

OAuth 2.0 Authorization Code Flow with PKCE:

The application implements a fully compliant OAuth 2.0 authorization server with PKCE (RFC 7636) for mobile/desktop clients. The flow in /repos/inou-portal/portal/oauth.go demonstrates security best practices:

  1. Authorization Request (GET /oauth/authorize):

    • Client generates code_verifier (43-128 byte random string)
    • Client derives code_challenge = BASE64URL(SHA256(code_verifier))
    • Request includes code_challenge and code_challenge_method=S256
    • Server validates redirect_uri against client's registered whitelist (exact match)
    • Server stores challenge with authorization code
  2. Code Exchange (POST /oauth/token):

    • Client submits authorization code + code_verifier (plain text)
    • Server computes BASE64URL(SHA256(code_verifier)) and compares to stored challenge
    • PKCE verification in lines 861-875 uses constant-time comparison
    • On success, returns access token (15 min) + refresh token (30 days)
  3. State Parameter Validation:

    • OAuth state parameter properly echoed back in redirect (line 139)
    • Prevents CSRF attacks on authorization flow

Registered OAuth Clients (hardcoded in oauth.go:365-379):

redirectURIs := []string{
    "https://claude.ai/api/mcp/auth_callback",      // Anthropic Claude AI
    "https://claude.com/api/mcp/auth_callback",     // Alternative Claude domain
    "http://localhost:6274/oauth/callback",          // Local development
    "http://localhost:6274/oauth/callback/debug",    // Debug endpoint
}

Dynamic Client Registration (POST /register): The application implements RFC 7591 (OAuth 2.0 Dynamic Client Registration) in /repos/inou-portal/portal/mcp_http.go:88-150, allowing Claude AI to register as an OAuth client programmatically. This creates a trust boundary risk: any service can register as a client and request authorization, relying entirely on user approval in the authorization step.

State and Nonce Parameter Security:

  • OAuth State: Properly implemented to prevent CSRF (line 139 in oauth.go)
  • OIDC Nonce: Not applicable (application implements OAuth 2.0, not full OpenID Connect)
  • PKCE Code Challenge: Prevents authorization code interception attacks

Session Cookie Configuration in OAuth Context: After successful OAuth token issuance, the application does NOT set a session cookie - OAuth clients use bearer tokens exclusively. However, the /oauth/authorize endpoint checks for an existing login cookie (line 61) to skip re-authentication if user is already logged in, creating a potential session fixation risk if the cookie is compromised.

Critical Authentication Vulnerabilities Summary

  1. Hardcoded Backdoor Code (portal/api_mobile.go:128): Universal bypass with code 250365
  2. Deterministic Token Encryption (lib/crypto.go:46-62): Same plaintext → same ciphertext in GCM mode
  3. Plaintext Refresh Tokens (lib/db_queries.go:721): Stored unhashed in database
  4. No Rate Limiting: OTP brute-force possible on /verify endpoint
  5. HTTP-Only Deployment: Session cookies transmitted in cleartext (Secure flag ineffective)
  6. Localhost Authentication Bypass (api/auth.go:158-167): Any 127.0.0.1 process gets SystemContext
  7. No CSRF Tokens: Relies solely on SameSite=Lax cookie attribute
  8. Wildcard CORS (api_mobile.go:21): Access-Control-Allow-Origin: * allows any domain

4. Data Security & Storage

Database Security Architecture

The inou-portal implements a three-database compartmentalization strategy using SQLite3 with distinct security profiles:

  1. Medical Data Database (/tank/inou/data/inou.db):

    • Content: Patient demographics, DICOM imaging metadata, lab results, genomic data, medications
    • Encryption: Field-level AES-256-GCM via Pack() function for all string/binary fields
    • Access Control: RBAC enforced before every query via CheckAccess()
    • Audit: All access logged to audit table
    • Configuration: WAL mode enabled, 5-second busy timeout
  2. Authentication Database (/tank/inou/data/auth.db):

    • Content: OAuth codes, refresh tokens, client credentials
    • Encryption: None - tokens stored in plaintext
    • Lifecycle: Ephemeral data with automatic cleanup (expired codes deleted after 1 hour)
    • Risk: Database compromise exposes all active OAuth sessions
  3. Reference Database (/tank/inou/data/reference.db):

    • Content: Lab test reference ranges, genomic risk tiers
    • Encryption: Minimal (reference data, not patient-specific)
    • Purpose: Separation of medical knowledge from patient data

Database Connection Security:

  • SQLite-specific: In-process, no network sockets (eliminates remote SQL injection)
  • Connection string: file:/tank/inou/data/inou.db?_journal_mode=WAL&_busy_timeout=5000
  • File permissions: Not enforced in code (relies on OS-level permissions)
  • Concurrent access: WAL mode supports multiple readers + single writer

Data Flow Security & Multi-Tenant Isolation

Query Safety - Parameterized vs Dynamic SQL:

The application demonstrates mixed query patterns with both secure and vulnerable approaches:

SECURE - Parameterized Queries (lib/db_queries.go:404):

row := db.QueryRow("SELECT * FROM entries WHERE EntryID = ?", entryID)

All user-provided values passed as parameters, preventing SQL injection.

VULNERABLE - Dynamic SQL Construction (lib/db_queries.go:532, 547, 579):

query := fmt.Sprintf("DELETE FROM %s WHERE %s = ?", table, pkCol)
db.Exec(query, id)

Table and column names constructed via fmt.Sprintf() - potential SQL injection if table or pkCol variables are user-controlled. Code analysis shows these are currently hardcoded ("trackers", "entries") but the pattern creates risk if refactored.

RBAC Enforcement Before Database Access:

The application implements a critical security invariant documented in /repos/inou-portal/lib/dbcore.go:9-17:

"ALL data access goes through EntryRead. No exceptions. RBAC is checked FIRST, before any query executes."

This is enforced via middleware functions in /repos/inou-portal/api/auth.go:74-167:

func requireDossierAccess(w http.ResponseWriter, r *http.Request, dossierID string, perm int) *lib.AccessContext {
    ctx := getAccessContextOrFail(w, r)
    if ctx == nil { return nil }

    if !lib.CheckAccess(ctx.AccessorID, dossierID, dossierID, perm) {
        http.Error(w, "Forbidden", 403)
        return nil
    }
    return ctx
}

Multi-Tenant Data Isolation: The database schema uses DossierID as a partition key in all tables:

SELECT * FROM entries WHERE DossierID = ? AND Category = ?

Isolation is achieved through:

  1. Application-level filtering: All queries include DossierID filter
  2. RBAC validation: CheckAccess verifies accessor has permission for target dossier
  3. No database-level isolation: SQLite doesn't support row-level security policies

RISK: If RBAC is bypassed (e.g., via localhost bypass or SystemContext abuse), there is no database-level defense against cross-tenant data access.

Encryption Implementation Analysis

Field-Level Encryption Workflow:

The application implements automatic transparent encryption via reflection-based field inspection in /repos/inou-portal/lib/db_queries.go:589-625:

func encryptField(v reflect.Value, fieldName string) any {
    switch v.Kind() {
    case reflect.String:
        s := v.String()
        if s == "" { return "" }
        if strings.HasSuffix(fieldName, "ID") {
            return s  // IDs remain plaintext for JOIN performance
        }
        return Pack([]byte(s))  // Compress + Encrypt
    // ... bytes, integers pass through
    }
}

Encryption Rules:

  • Encrypted: All string fields except those ending in "ID"
  • Encrypted: All byte slice fields
  • Plaintext: Integer and boolean fields (for filtering/sorting)
  • Plaintext: All "*ID" fields (DossierID, EntryID, ParentID for JOINs)

Pack() Implementation (lib/crypto.go:135-150):

func Pack(data []byte) []byte {
    var buf bytes.Buffer
    w, _ := flate.NewWriter(&buf, flate.DefaultCompression)  // 1. Compress
    w.Write(data)
    w.Close()
    return CryptoEncryptBytes(buf.Bytes())  // 2. Encrypt (AES-256-GCM)
}

CRITICAL VULNERABILITY - Deterministic Nonce: The CryptoEncryptBytes() function uses a deterministic nonce derived from plaintext (lib/crypto.go:46-62):

func deriveNonce(data []byte, nonceSize int) []byte {
    block, _ := aes.NewCipher(masterKey)
    nonce := make([]byte, nonceSize)
    for i := 0; i < len(data); i += 16 {
        chunk := make([]byte, 16)
        copy(chunk, data[i:end])
        block.Encrypt(encrypted, chunk)  // AES ECB mode to derive nonce
        for j := 0; j < nonceSize && j < 16; j++ {
            nonce[j] ^= encrypted[j]
        }
    }
    return nonce
}

Security Implications:

  • Same plaintext always produces same ciphertext (deterministic encryption)
  • Violates GCM nonce uniqueness requirement (nonce must never repeat with same key)
  • Enables frequency analysis: identical medical conditions produce identical ciphertexts
  • Allows chosen-ciphertext attacks if attacker can observe encryption of known plaintexts
  • Severity: HIGH - fundamentally weakens encryption security model

Correct Implementation: Use crypto/rand.Read() to generate random 12-byte nonce for each encryption.

Master Key Management:

  • Storage: /tank/inou/master.key (32-byte binary file)
  • Loading: At startup via CryptoInit() (lib/crypto.go:19-30)
  • Validation: Exactly 32 bytes required (AES-256 key size)
  • Risk: Plaintext file on filesystem, no HSM or key management service integration
  • No rotation: No mechanism to rotate master key or re-encrypt data with new key

FIPS 140-3 Support (lib/crypto.go:32-43): The application can operate in FIPS 140-3 validated cryptographic mode:

func CryptoFIPSEnabled() bool {
    return fips140.Enabled()
}

Activation requires GODEBUG=fips140=on environment variable. Status: Optional, not enforced by default.


5. Attack Surface Analysis

External Entry Points - Network-Accessible Interfaces

The inou-portal exposes 40+ distinct API endpoints across two HTTP servers, with clear separation between user-facing portal routes and backend API services. All endpoints are HTTP-only without TLS encryption, creating a universal vulnerability for man-in-the-middle attacks on authentication tokens and session cookies.

Portal Web Server (Port 8080/1080):

Public Endpoints (No Authentication Required):

  • GET / - Landing page with marketing content
  • GET /start - Login page presenting email input form
  • GET /send-code - Initiates passwordless authentication (generates OTP, sends email)
    • Input: email, nonce (bot protection parameter ≥2000ms)
    • Vulnerability: No rate limiting on email sending - spam vector
  • GET /verify - OTP validation endpoint
    • Input: code (6-digit), email
    • Vulnerability: No rate limiting - brute-force attack surface (1M code space)
  • GET /onboard - New user registration/profile setup
  • POST /api/v1/auth/send - Mobile OTP request (CORS: wildcard *)
  • POST /api/v1/auth/verify - Mobile OTP validation
    • CRITICAL: Hardcoded backdoor code 250365 bypasses authentication
  • GET /api/version - API version and update information
  • GET /api/v1/health - Health check endpoint (returns {status, time, version, checks})
  • GET /api/v1/categories - List of medical data categories (public)
  • GET /api/openapi.yaml - OpenAPI 3.0 specification document
  • GET /api/docs - Interactive API documentation (Swagger UI)

OAuth 2.0 Endpoints (Public but requiring user authorization):

  • GET /oauth/authorize - OAuth authorization endpoint with PKCE
    • Parameters: client_id, redirect_uri, response_type, state, code_challenge
    • Validation: Redirect URI whitelist (exact match), PKCE S256 required
    • SSRF Risk: User-controlled redirect_uri parsed and followed
  • POST /oauth/token - Token issuance (authorization_code, refresh_token grants)
  • GET /oauth/userinfo - User profile endpoint (Bearer token required)
  • POST /oauth/revoke - Refresh token revocation
  • GET /.well-known/oauth-authorization-server - RFC 8414 metadata
  • GET /.well-known/oauth-protected-resource - RFC 9728 metadata
  • POST /register - Dynamic OAuth client registration (RFC 7591)
    • Risk: Any service can register as OAuth client

Authenticated Web Application Routes (Session cookie required):

  • GET /dashboard - User home page showing accessible dossiers
  • GET /connect - MCP/OAuth client setup interface
  • GET /dossier/{id}/ - Dossier detail view
    • Requires: RBAC Read permission for target dossier
    • Sub-routes: /upload, /trackers, /share-access, /audit, /permissions, /export-data
  • POST /dossier/{id}/upload - Medical file upload handler
    • Content-Type: multipart/form-data
    • Command Injection Risk: Executes /tank/inou/bin/import-dicom with user-controlled path parameter
  • GET /viewer/ - Interactive DICOM medical imaging viewer
    • XSS Risk: Series descriptions from database rendered via innerHTML

MCP (Model Context Protocol) Endpoints:

  • POST /mcp - JSON-RPC endpoint for Claude AI tool calls
    • Authentication: OAuth bearer token
    • Methods: initialize, tools/list, tools/call, prompts/list, prompts/get
    • Data Exposure: AI assistants can query all medical data with user's RBAC permissions

API Server (Port 8082 - Internal, Proxied via Portal):

V1 Modern API (/api/v1/* routes):

  • POST /api/v1/token - Generate 4-hour access token
  • GET /api/v1/dossiers - List accessible dossiers (RBAC-filtered)
  • GET /api/v1/dossiers/{id} - Dossier details
  • GET /api/v1/dossiers/{id}/entries - Medical records query
    • Query params: category, type, search_key, from, to, limit
    • Attack Surface: Complex query logic with filtering/pagination
  • GET /api/v1/dossiers/{id}/audit - Audit trail retrieval
  • GET /api/v1/dossiers/{id}/journal - Clinical journal entries
  • POST /api/v1/dossiers/{id}/journal - Create journal entry
    • Input Validation: JSON body with optional fields (title, summary, content, tags)
  • POST /api/v1/dossiers/{id}/parse - AI-powered medical data extraction
    • Input: {input: string} (free-form health text)
    • Attack Surface: User input processed by LLM (Gemini API)
    • SSRF Risk: Outbound HTTP to Google Gemini API
  • GET /api/v1/images/{id} - Medical image rendering with window/level adjustments
    • Query params: token, wc (window center), ww (window width)
    • Token in URL: Visible in server logs, browser history

Legacy V0 API (Deprecated but active):

  • GET /api/dossiers - List dossiers
  • POST /api/dossier - Create new dossier (registration endpoint)
    • Input: {email, invited_by}
  • GET /api/studies - DICOM imaging studies
  • GET /api/series - Series metadata
  • GET /api/slices - Slice metadata with contact sheet URLs
  • GET /image/{id} - Image rendering (WebP format)
  • GET /contact-sheet.webp/{series_id} - Thumbnail grid generation
  • GET /api/labs/tests - Available lab tests
  • GET /api/labs/results - Lab result data
  • POST /api/entries - Create/update/delete medical entries
    • Mass Assignment Risk: Accepts array of entry objects with delete_category operation
  • GET /api/trackers - Health tracking prompts
  • POST /api/trackers/respond - Submit tracker response

Localhost-Only Endpoints (CRITICAL VULNERABILITY):

  • GET /api/access - Manage access grants (localhost only)
    • Bypass: Any process on 127.0.0.1 gets SystemContext (unrestricted access)
  • POST /api/audit - Create audit log entry (localhost only)

Internal Service Communication

Portal → API Backend Proxy (/repos/inou-portal/portal/api_proxy.go:22-70):

const apiBackend = "http://127.0.0.1:8082"

All /api/* requests forwarded to localhost:8082 over unencrypted HTTP. Trust boundary assumes localhost cannot be intercepted.

Application → External LLM APIs:

  • Google Gemini: https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={GeminiKey}
    • VULNERABILITY: API key in URL query parameter (visible in logs, referer headers)
    • File: /repos/inou-portal/lib/llm.go:109
  • Anthropic Claude: Via MCP over OAuth (proper bearer token authentication)

Application → SMTP Server (Email delivery):

  • Connection: net.Dial("tcp", smtpHost+":"+smtpPort) then STARTTLS
  • Authentication: client.Auth(smtp.PlainAuth("", smtpUser, smtpPass, smtpHost))
  • Security: Properly encrypted via TLS, credentials from environment/file

Input Validation Patterns

Email Validation (portal/main.go:543):

email = strings.ToLower(strings.TrimSpace(email))
if email == "" {
    error("Email required")
}

Weakness: No RFC 5322 format validation - accepts invalid email formats.

Date Validation (portal/main.go:609-612):

if dob < "1900-01-01" || dob > time.Now().Format("2006-01-02") {
    error("Invalid date of birth")
}

Weakness: String comparison (works for ISO dates but fragile).

GUID Validation (portal/defense.go:100-118): Pattern matching for 16-character hex strings or UUID format used to distinguish legitimate clients from bots/scanners.

Bot Protection (portal/main.go:540-557):

if n := r.FormValue("nonce"); n == "" {
    tarpit(w, r, "BOT")
} else if ms, _ := strconv.Atoi(n); ms < 2000 {
    tarpit(w, r, "BOT")
}

Requires client-side JavaScript to measure page load time before submission (nonce ≥2000ms).

Path Traversal Protection: All file operations use sanitized paths via ObjectPath() function constructing hierarchical storage:

/tank/inou/objects/{ab}/{c1}/{full-guid}

GUID format (16-char hex) prevents ../ injection.

Background Processing Security

File Upload Processing (portal/upload.go:364-490):

cmd := exec.Command("/tank/inou/bin/import-dicom", targetID, tempDir)

Command Injection Risk: targetID from URL path parameter passed to shell command. Mitigation: exec.Command uses array form (not shell interpolation), but input validation recommended.

DICOM Import Process:

  1. User uploads DICOM file via /dossier/{id}/upload
  2. File stored in /tank/inou/uploads/{dossier-id}/
  3. User triggers /dossier/{id}/upload/process-imaging
  4. System executes /tank/inou/bin/import-dicom {dossier-id} {upload-dir}
  5. Binary parses DICOM, extracts metadata, generates PNG previews
  6. Results stored in objects directory with encryption

Risk: DICOM parsing is a complex binary format - buffer overflow or malformed file attacks possible in image processing libraries (github.com/chai2010/webp, golang.org/x/image).

API Schema Documentation

OpenAPI 3.0 Specification (/repos/inou-portal/portal/static/openapi.yaml):

  • Documented Endpoints: /api/dossiers, /api/studies, /api/series, /api/slices, /image/{id}, /viewer/, /api/labs/*
  • Authentication Schemes: Bearer token, Query parameter token, Session cookie
  • Missing Documentation: OAuth endpoints, MCP endpoints, v1 API endpoints (incomplete schema)

Schema Files Discovered:

  • /repos/inou-portal/portal/static/openapi.yaml - OpenAPI 3.0.3 specification
  • /repos/inou-portal/portal/static/api-docs.html - Swagger UI interface
  • /repos/inou-portal/portal/static/api-docs.txt - Plain text API documentation

Attack Surface Summary by Category

Category Endpoint Count Authentication Critical Risks
Public Auth 5 None Backdoor code, no rate limiting, email spam
OAuth 2.0 6 User approval SSRF in redirect_uri, dynamic client registration
Web Portal 15 Session cookie XSS in viewer, CSRF (no tokens), HTTP-only
API v1 12 Bearer token Token in URL, mass assignment, LLM data exposure
Legacy API 10 Session/Bearer Deprecated endpoints still active
Localhost-Only 2 IP check Localhost bypass grants SystemContext
MCP/AI 1 OAuth AI assistants access sensitive medical data

Total Network-Accessible Entry Points: 51 endpoints

Out-of-Scope Components Excluded:

  • /repos/inou-portal/main.go - DICOM conversion CLI (local-only, requires go run)
  • Database migration scripts (if any) - not network-callable
  • Build tools and CI/CD pipelines - not part of deployed application

6. Infrastructure & Operational Security

Secrets Management

CRITICAL VULNERABILITY - Plaintext Secret Storage:

The application stores API keys and credentials in plaintext files and environment variables without encryption or access control enforcement:

File-Based Secrets (/tank/inou/anthropic.env):

GEMINI_API_KEY=AIzaSy...
ANTHROPIC_API_KEY=sk-ant-...
SYSTEM_ACCESSOR_ID=7b3a3ee1c2776dcd
  • File: /repos/inou-portal/lib/config.go:31
  • Risk: No file permission validation, no encryption, visible to any user with filesystem access
  • Loading: At startup via os.ReadFile(configFile)

Master Encryption Key (/tank/inou/master.key):

  • Path: Hardcoded to /tank/inou/master.key
  • Size: 32 bytes (AES-256 key)
  • Protection: None - plaintext binary file
  • Single Point of Failure: Compromise of this file decrypts all medical data

Environment Variable Fallback:

if os.Getenv("GEMINI_API_KEY") != "" {
    GeminiKey = os.Getenv("GEMINI_API_KEY")
}

Credentials exposed to all child processes and visible in /proc/{pid}/environ.

SMTP Credentials (smtp.env file):

smtpHost=smtp.gmail.com
smtpPort=587
smtpUser=noreply@example.com
smtpPass=app-password-here

Plaintext email server credentials in filesystem.

No Secret Rotation:

  • No mechanism to rotate API keys without application restart
  • No credential versioning or expiration
  • No audit logging of secret access

Recommendations:

  1. Migrate to HashiCorp Vault or AWS Secrets Manager
  2. Use envelope encryption for master key (encrypt key-encryption-key with HSM)
  3. Implement automatic secret rotation
  4. Enforce file permissions (chmod 600 on secret files)
  5. Never include API keys in URLs (use HTTP headers)

Configuration Security

Hardcoded Filesystem Paths: The application relies on a fixed directory structure at /tank/inou/:

/tank/inou/
├── master.key              # 32-byte encryption key
├── anthropic.env           # API keys (plaintext)
├── smtp.env                # Email credentials
├── data/
│   ├── inou.db            # Medical data (encrypted fields)
│   ├── auth.db            # OAuth tokens (plaintext)
│   └── reference.db       # Lab references
├── uploads/{dossier-id}/   # Raw uploads
├── objects/{ab}/{c1}/{guid} # Encrypted objects
├── logs/access-*.log       # Access logs (daily rotation)
└── bin/import-dicom        # DICOM processing binary

Risk: No environment-based configuration override - deployment requires exact path structure. Directory traversal in configuration could expose secrets or data files.

Security Headers Configuration:

CRITICAL FINDING: No security headers implemented in HTTP responses.

Missing Headers:

  • Content-Security-Policy - No XSS mitigation via CSP
  • X-Frame-Options - Allows clickjacking attacks
  • X-Content-Type-Options: nosniff - MIME confusion attacks possible
  • Strict-Transport-Security (HSTS) - No HTTPS enforcement (app uses HTTP)
  • Referrer-Policy - Referrer leakage of sensitive URLs
  • Permissions-Policy - No feature policy restrictions

Only Found (portal/oauth.go:35-36):

w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Content-Type", "application/json")

Applied only to OAuth token endpoint, not globally.

CORS Configuration (portal/api_mobile.go:21):

w.Header().Set("Access-Control-Allow-Origin", "*")  // WILDCARD - HIGH RISK
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

Vulnerability: Wildcard CORS allows any domain to make authenticated requests to mobile API endpoints.

Recommended Security Headers:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()

External Dependencies

Third-Party Services:

  1. Google Gemini API (AI/LLM):

    • Purpose: Medical data parsing via /api/v1/dossiers/{id}/parse
    • Authentication: API key in URL (HIGH RISK)
    • Data exposure: User-provided health text sent to Google servers
    • Compliance risk: PHI transmitted to third party (HIPAA Business Associate Agreement required)
  2. Anthropic Claude API (AI Assistant):

    • Purpose: MCP tool calls for medical data queries
    • Authentication: OAuth 2.0 bearer tokens (proper implementation)
    • Data exposure: Structured medical records accessible via tools
    • Controlled by: User authorization in OAuth flow
  3. SMTP Server (Email Delivery):

    • Purpose: OTP delivery for passwordless authentication
    • Configuration: smtp.env file with credentials
    • Security: STARTTLS encryption

Dependency Vulnerabilities:

  • github.com/mattn/go-sqlite3 v1.14.22 - CGo-based, introduces C code execution risks
  • github.com/chai2010/webp v1.4.0 - Image processing library (potential buffer overflows)
  • golang.org/x/image v0.35.0 - Image decoding (malformed image attacks)

No Dependency Scanning: No evidence of automated vulnerability scanning (Dependabot, Snyk, etc.)

Monitoring & Logging

Access Logging (/repos/inou-portal/portal/access_log.go):

accessLogger.Printf("%s | %s %s | %d | %dms | %s",
    timestamp, method, path, statusCode, duration, userAgent)
  • Location: /tank/inou/logs/access-YYYY-MM-DD.log
  • Rotation: Daily
  • Format: Plaintext
  • Content: HTTP method, path, status, duration, user-agent
  • Risk: Query parameters may include tokens (visible in logs)

Audit Trail (/repos/inou-portal/lib/stubs.go:326-362):

type AuditEntry struct {
    Actor1ID  string  // Who (plaintext)
    TargetID  string  // What (plaintext)
    Action    string  // Encrypted
    Details   string  // Encrypted
    Timestamp int64   // Unix timestamp
}
  • Storage: inou.db (mixed encryption)
  • Completeness: Access to medical data logged, but authentication failures NOT logged
  • Retention: Indefinite (no cleanup observed)
  • Export: No GDPR/HIPAA audit export functionality visible

Security Event Logging Gaps:

  • Failed authentication attempts
  • Rate limiting violations
  • RBAC denials (403 Forbidden)
  • Suspicious access patterns
  • OAuth token revocations
  • Secret access/rotation events

Recommendation:

  1. Implement centralized security event logging (SIEM integration)
  2. Log all authentication failures with IP, timestamp, attempted email
  3. Alert on brute-force patterns (>5 failed attempts in 5 minutes)
  4. Separate security logs from access logs
  5. Implement log forwarding to immutable storage (compliance requirement)

7. Overall Codebase Indexing

Directory Structure & Organization

The inou-portal codebase follows a domain-driven monorepo structure with clear separation between portal (user-facing), API (backend services), and shared library code. The organization demonstrates thoughtful architectural decisions prioritizing security compartmentalization and code reusability.

Root-Level Structure:

/repos/inou-portal/
├── go.mod, go.sum          # Go module dependency management
├── main.go                 # DICOM conversion CLI (local-only, out-of-scope)
├── api/                    # RESTful API server (port 8082)
├── portal/                 # Web portal server (port 8080)
├── lib/                    # Shared library code
└── static/                 # Root static assets

API Backend Directory (/repos/inou-portal/api/): The API server implements a resource-oriented architecture with dedicated files per domain entity:

api/
├── main.go                 # Server initialization, routing, port 8082
├── auth.go                 # Authentication context extraction (24-167 lines)
├── api_v1.go               # V1 API routing and handlers (904-984 lines)
├── api_version.go          # Version check endpoint
├── api_dossier.go          # Dossier CRUD operations
├── api_studies.go          # DICOM study queries
├── api_series.go           # DICOM series metadata
├── api_slices.go           # DICOM slice data
├── api_image.go            # Image rendering with window/level
├── api_contact_sheet.go    # Thumbnail grid generation
├── api_entries.go          # Medical entry CRUD
├── api_labs.go             # Laboratory results
├── api_trackers.go         # Health tracking queries
├── api_access.go           # Access grant management (localhost-only)
├── api_audit.go            # Audit log retrieval/creation
├── api_llm.go              # LLM integration endpoints
├── api_mcp_audit.go        # MCP audit logging
└── llm_types.go            # LLM request/response types

Security Implication: Each API handler file contains both routing logic and business logic, creating a risk that authorization checks could be bypassed if handlers are called directly. The codebase mitigates this via middleware-based auth context extraction (getAccessContextOrFail(), requireDossierAccess()), but the pattern requires discipline to maintain.

Portal Server Directory (/repos/inou-portal/portal/): The portal implements server-side rendering with JavaScript enhancement rather than a SPA framework:

portal/
├── main.go                 # Server initialization, routing, middleware (1964 lines)
├── oauth.go                # OAuth 2.0 authorization server (380+ lines)
├── api_proxy.go            # Reverse proxy to API backend
├── api_client.go           # Client API for access management
├── api_mobile.go           # Mobile app authentication endpoints
├── mcp_http.go             # Model Context Protocol HTTP server
├── mcp_tools.go            # MCP tool implementations for Claude
├── defense.go              # Tarpit attack defense (182 lines)
├── access_log.go           # HTTP access logging
├── upload.go               # File upload handling
├── genome.go               # Genomic data display
├── trackers.go             # Health tracker UI
├── dossier_sections.go     # Dossier UI components
├── static/                 # Frontend assets
│   ├── viewer.js           # DICOM viewer (XSS vulnerabilities)
│   ├── style.css           # Styling
│   ├── openapi.yaml        # API documentation
│   ├── swagger.html        # Swagger UI
│   ├── pricing.html        # Landing page
│   └── api-docs.html       # API documentation
├── lang/                   # Translations (15 languages)
└── templates/              # HTML templates (embedded in binary)

Security Observation: The portal/main.go file is 1964 lines - a monolithic request handler containing routing, middleware, authentication logic, and business logic. This creates maintainability risks where security-critical code (e.g., session cookie configuration at lines 311-317) is embedded deep within a large file. Refactoring into smaller, focused modules would improve security code review effectiveness.

Shared Library Directory (/repos/inou-portal/lib/): The library layer implements security-critical primitives and data access abstractions:

lib/
├── config.go               # Configuration loading, secret management
├── crypto.go               # AES-256-GCM encryption (deterministic nonce VULNERABILITY)
├── dbcore.go               # Database connection management
├── db_schema.go            # SQLite schema definitions
├── db_auth.go              # Authentication database operations
├── db_queries.go           # Generic CRUD with reflection-based encryption
├── rbac.go                 # Permission checking (CheckAccess, GrantAccess)
├── types.go                # Data models (Dossier, Entry, AuditEntry)
├── llm.go                  # LLM API integration (Gemini, Claude)
├── journal.go              # Clinical journal entries
├── email.go                # SMTP email sending
├── tracker.go              # Health tracker data model
├── lab_reference.go        # Lab test reference ranges
├── parse_numeric.go        # Numeric parsing utilities
├── normalize.go            # Data normalization
├── translate.go            # Multi-language translation
├── files.go                # File encryption/decryption utilities
├── errors.go               # Error type definitions
├── stubs.go                # Stub implementations (incomplete features)
└── signal.go               # Signal handling

Critical Security Finding: The lib/stubs.go file contains stub implementations for critical security functions:

func RevokeAccess(grantor, grantee, target string) error {
    log.Printf("[STUB] RevokeAccess not implemented")
    return nil
}

This indicates incomplete RBAC implementation - access grants can be created but not revoked, violating security best practices and compliance requirements.

Build & Development Tooling

Build System: Standard Go toolchain with implicit go build (no Makefile, Dockerfile, or CI/CD configuration found in codebase).

Package Management: Go modules (go.mod) with version pinning via go.sum checksums.

Code Generation: No evidence of code generation tools (no //go:generate directives observed).

Testing Framework: Not visible in provided codebase structure (test files typically named *_test.go not listed).

Dependency Injection: Not used - application relies on global variables for database connections, configuration, and cryptographic state.

Security-Relevant Patterns & Conventions

Authentication Context Pattern: All authenticated endpoints use a consistent pattern to extract user identity:

ctx := getAccessContextOrFail(w, r)  // Returns *AccessContext or nil + writes 401
if ctx == nil { return }

This middleware-based approach reduces code duplication but creates a single point of failure - if getAccessContextOrFail() has a bug, all endpoints are vulnerable.

RBAC Enforcement Convention: Database access follows the documented invariant "ALL data access goes through EntryRead. RBAC is checked FIRST."

if !lib.CheckAccess(ctx.AccessorID, dossierID, entryID, lib.PermRead) {
    http.Error(w, "Forbidden", 403)
    return
}

However, this is a convention, not an enforced contract - developers must remember to add checks in new code.

Automatic Encryption Pattern: The db_queries.go implements reflection-based field encryption where string fields are automatically encrypted via Pack() unless they end in "ID". This convention-based security is fragile - adding a new string field without understanding the pattern could leak unencrypted data.

Error Handling Pattern: Errors are logged but often returned as generic messages to users:

if err != nil {
    log.Printf("ERROR: %v", err)
    http.Error(w, "Internal server error", 500)
}

This prevents information disclosure but hampers debugging and monitoring.

Impact on Security Component Discoverability

Positive Factors:

  • Clear file naming conventions make security code easy to locate (auth.go, rbac.go, crypto.go)
  • Separation of concerns between portal, API, and library layers
  • Consistent use of middleware for authentication
  • OpenAPI documentation provides attack surface map

Negative Factors:

  • Monolithic portal/main.go obscures session management code
  • Stub implementations in lib/stubs.go indicate incomplete security features
  • Security-critical constants scattered across files (e.g., SystemAccessorID in config.go:27)
  • No centralized security policy file or documentation
  • Mixed authentication methods increase attack surface complexity

Recommendation: Create a dedicated security/ directory containing:

  • security/auth.go - All authentication logic
  • security/authz.go - All authorization/RBAC logic
  • security/crypto.go - Encryption primitives
  • security/secrets.go - Secret management
  • security/audit.go - Audit logging
  • security/POLICY.md - Security architecture documentation

8. Critical File Paths

This section catalogs all security-relevant file paths referenced throughout this analysis, organized by functional category to facilitate targeted manual review and vulnerability assessment.

Configuration Files

  • /repos/inou-portal/go.mod - Go module dependencies and versions
  • /repos/inou-portal/go.sum - Dependency checksums (integrity verification)
  • /tank/inou/anthropic.env - LLM API keys (PLAINTEXT - CRITICAL VULNERABILITY)
  • /tank/inou/smtp.env - Email server credentials (plaintext)
  • /tank/inou/master.key - 32-byte AES-256 encryption key (PLAINTEXT - CRITICAL VULNERABILITY)

Authentication & Authorization

  • /repos/inou-portal/lib/crypto.go - Token encryption (lines 172-207: TokenCreate/TokenParse)
    • VULNERABILITY: Deterministic nonce generation (lines 46-62)
  • /repos/inou-portal/portal/oauth.go - OAuth 2.0 authorization server
    • Lines 50-140: Authorization endpoint
    • Lines 142-294: Token issuance endpoint
    • Lines 298-335: UserInfo endpoint
    • Lines 337-373: Token revocation
  • /repos/inou-portal/portal/main.go - Session cookie configuration
    • Lines 311-317: setLoginCookie (HttpOnly, Secure, SameSite=Lax)
    • Lines 540-590: Email OTP flow (/send-code, /verify)
  • /repos/inou-portal/portal/api_mobile.go - Mobile authentication
    • Line 128: Hardcoded backdoor code 250365 (CRITICAL VULNERABILITY)
    • Lines 55-103: OTP send endpoint
    • Lines 105-149: OTP verify endpoint
  • /repos/inou-portal/lib/rbac.go - Permission model
    • Lines 10-15: Permission constants (Read, Write, Delete, Manage)
    • Lines 19-54: CheckAccess function
    • Lines 91-104: RevokeAccess (STUB - not implemented)
  • /repos/inou-portal/api/auth.go - Authentication middleware
    • Lines 24-72: getAccessContext (multi-method auth extraction)
    • Lines 74-167: RBAC enforcement helpers
    • Lines 158-161: isLocalhost (VULNERABILITY - no X-Forwarded-For validation)
  • /repos/inou-portal/lib/config.go - System configuration
    • Line 27: SystemAccessorID default value
    • Lines 31-54: Secret loading from file/environment

API & Routing

  • /repos/inou-portal/portal/main.go - Portal server routing (lines 1850-1923)
  • /repos/inou-portal/api/main.go - API server entry point (line 74: HTTP-only)
  • /repos/inou-portal/api/api_v1.go - V1 API routing (lines 904-984)
  • /repos/inou-portal/portal/api_proxy.go - API reverse proxy
    • Lines 22-70: Localhost proxy to port 8082
  • /repos/inou-portal/portal/mcp_http.go - Model Context Protocol server
    • Lines 88-150: Dynamic OAuth client registration
    • Lines 178-287: MCP JSON-RPC handler
  • /repos/inou-portal/portal/mcp_tools.go - MCP tool implementations

Data Models & Database Interaction

  • /repos/inou-portal/lib/types.go - Core data structures
    • Lines 250-281: Dossier model (PII fields)
    • Lines 75-147: Entry model (medical data)
  • /repos/inou-portal/lib/db_schema.go - SQLite schema definitions (lines 1-89)
  • /repos/inou-portal/lib/dbcore.go - Database connection management
    • Lines 54-88: DBInit
    • Lines 289-371: Dossier authentication (OTP)
  • /repos/inou-portal/lib/db_queries.go - Generic CRUD operations
    • Lines 315-342: Bulk insert with prepared statements
    • Lines 532, 547, 579: Dynamic SQL construction (VULNERABILITY - fmt.Sprintf)
    • Lines 589-625: Field encryption logic (encryptField)
    • Lines 651-693: Field decryption logic (decryptAndSet)
    • Lines 711-876: OAuth queries
    • Lines 877-987: Refresh token operations
    • Lines 990-1005: OAuth cleanup
  • /repos/inou-portal/lib/db_auth.go - Authentication database operations (lines 1-180)

Dependency Manifests

  • /repos/inou-portal/go.mod - Go dependencies
    • golang.org/x/crypto v0.46.0 (FIPS 140-3 support)
    • github.com/mattn/go-sqlite3 v1.14.22 (CGo - C code risks)
    • github.com/chai2010/webp v1.4.0 (Image processing)
    • golang.org/x/image v0.35.0

Sensitive Data & Encryption

  • /repos/inou-portal/lib/crypto.go - Encryption implementation
    • Lines 19-30: Master key loading (CryptoInit)
    • Lines 46-62: Deterministic nonce derivation (CRITICAL VULNERABILITY)
    • Lines 65-95: AES-256-GCM encryption (CryptoEncryptBytes)
    • Lines 97-129: AES-256-GCM decryption (CryptoDecryptBytes)
    • Lines 135-164: Pack/Unpack (compress + encrypt)
    • Lines 32-43: FIPS 140-3 support (optional)
  • /repos/inou-portal/lib/files.go - File encryption utilities
    • Lines 7-35: EncryptFile, DecryptFile

Middleware & Input Validation

  • /repos/inou-portal/portal/defense.go - Attack defense
    • Lines 17-35: Tarpit patterns (malicious path detection)
    • Lines 136-182: Defense middleware
    • Lines 100-118: GUID validation
  • /repos/inou-portal/portal/main.go - Input validation
    • Lines 540-557: Bot protection (nonce parameter)
    • Lines 543: Email validation (lowercase + trim only)
    • Lines 609-612: Date validation (string comparison)

Logging & Monitoring

  • /repos/inou-portal/portal/access_log.go - HTTP access logging (daily rotation)
  • /repos/inou-portal/lib/stubs.go - Audit logging
    • Lines 326-362: AuditEntry struct and logging functions

Infrastructure & Deployment

  • /repos/inou-portal/portal/static/openapi.yaml - OpenAPI 3.0.3 specification
  • /repos/inou-portal/portal/static/swagger.html - Swagger UI
  • /repos/inou-portal/portal/static/api-docs.html - API documentation

Frontend & XSS Vectors

  • /repos/inou-portal/portal/static/viewer.js - DICOM viewer
    • Lines 442, 446, 450, 628, 815, 1098: innerHTML operations with API data (XSS RISK)

File Upload & Processing

  • /repos/inou-portal/portal/upload.go - Upload handler
    • Lines 364-490: File upload processing
    • Line 472: exec.Command with user-controlled argument (COMMAND INJECTION RISK)

External Integrations

  • /repos/inou-portal/lib/llm.go - LLM API integration
    • Line 109: Gemini API call with key in URL (VULNERABILITY)
    • Lines 43, 83: Model configuration
  • /repos/inou-portal/lib/email.go - SMTP email
    • Line 17: SMTP config loading
    • Lines 49+: STARTTLS email sending
  • /repos/inou-portal/lib/signal.go - Internal messaging (Signal API)

9. XSS Sinks and Render Contexts

Methodology

This analysis identifies all locations in network-accessible code where user-controllable data is rendered in browser contexts that could execute JavaScript. Only sinks in web application routes are included - local-only scripts, build tools, and CLI utilities are explicitly excluded per scope definition.

Confirmed XSS Vulnerabilities

1. DICOM Viewer - Series Description Injection (HIGH SEVERITY)

File: /repos/inou-portal/portal/static/viewer.js

Vulnerable Code Locations:

Line 442: Series name display in panel header

headerContent = '<span class="series-name">' + seriesList[0].series_desc + ' (' + seriesList[0].slice_count + ')</span>';
  • Sink Type: innerHTML (HTML Body Context)
  • User Input Source: series_desc from API response /api/series
  • Data Flow:
    1. User creates/modifies Entry in database with malicious Tags field
    2. /api/series endpoint returns Entry.Tags as series_desc
    3. JavaScript concatenates into HTML string
    4. Assigned to innerHTML - executes embedded scripts
  • Payload Example: <img src=x onerror=alert(document.cookie)>
  • Exploitability: LIKELY (if users can modify series metadata via API)
  • Impact: Session hijacking via cookie theft, CSRF attacks, arbitrary JavaScript execution

Line 446: Series selection dropdown options

seriesList.map(s => '<option value="' + s.id + '">' + s.series_desc + ' (' + s.slice_count + ')</option>').join('')
  • Sink Type: innerHTML (HTML Element Context)
  • Attack Vector: Same as line 442, but rendered in <select> dropdown
  • Payload Example: </option><script>alert(1)</script><option>
  • Exploitability: LIKELY

Line 450: Panel header construction

div.innerHTML =
    '<div class="panel-header">' + headerContent + '</div>' + ...
  • Sink Type: innerHTML (HTML Body Context)
  • Attack Vector: Injects headerContent which contains unsanitized series_desc
  • Exploitability: LIKELY (chained from line 442)

Line 628: Orientation-based panel with series options

div.innerHTML =
    '<div class="panel-header">' +
    '<span style="color:#B45309;margin-right:10px;font-weight:bold">' + orientation + '</span>' +
    '<select onchange="loadSeries(' + idx + ', this.value)">' +
    '<option value="">Select ' + orientation + ' series...</option>' +
    (seriesOptions || []).map(s => '<option value="' + s.id + '">' + s.series_desc + ' (' + s.slice_count + ')</option>').join('') +
    '</select></div>';
  • Sink Type: innerHTML (HTML Body Context + Event Handler Context)
  • User Input Sources:
    • orientation parameter (controlled by caller)
    • series_desc from API (same as above)
  • Attack Vector: Multiple injection points
    • orientation in text span
    • series_desc in option elements
  • Payload Example:
    • orientation: Axial" onload="alert(1)"
    • series_desc: <img src=x onerror=alert(1)>
  • Exploitability: LIKELY

Line 815: Panel header with contact sheet

div.innerHTML =
    '<div class="panel-header">' + headerContent + '</div>' +
    '<div class="contact-sheet-container">' +
    '<img src="/contact-sheet.webp/' + series.id + '?token=' + token + '" alt="Contact sheet" />' +
    '</div>';
  • Sink Type: innerHTML (HTML Body Context + URL Attribute Context)
  • User Input: headerContent (contains series_desc), series.id, token
  • Attack Vector:
    • XSS via headerContent (same as line 442)
    • Potential SSRF via manipulated series.id (if GUID validation bypassed)
  • Exploitability: LIKELY

Line 1098: Window/level presets and scrubber HTML

thumbs.innerHTML = '<div class="wl-presets">' + presetsHtml + '</div>' + scrubberHtml;
  • Sink Type: innerHTML (HTML Body Context)
  • User Input: Need to trace presetsHtml and scrubberHtml construction
  • Exploitability: POSSIBLE (depends on upstream data source - requires code review)

Benign innerHTML Operations (Not Exploitable)

Line 309: Tour tooltip (static content)

tooltip.innerHTML = '<h3>' + step.title + '</h3><p>' + step.text + '</p>' +
  • Data Source: Hardcoded tourSteps array (not user-controllable)
  • Exploitability: NOT EXPLOITABLE

Lines 596, 716: Clear operations

document.getElementById('panels').innerHTML = '';
  • Operation: Clearing innerHTML (no injection)
  • Exploitability: NOT EXPLOITABLE

XSS Attack Scenarios

Scenario 1: Stored XSS via Medical Imaging Upload

  1. Attacker uploads DICOM file with malicious series description
  2. DICOM parser extracts metadata and stores in database
  3. Victim views imaging study in web viewer
  4. Viewer loads series list via /api/series
  5. Malicious series_desc injected into innerHTML
  6. JavaScript executes in victim's browser session
  7. Attacker steals session cookie, performs actions as victim

Scenario 2: Reflected XSS via API Manipulation

  1. Attacker modifies existing Entry via /api/entries POST
  2. Sets Tags field to XSS payload (if write permission obtained)
  3. Tags reflected as series_desc in API responses
  4. Any user viewing the modified series triggers execution

Mitigation Recommendations

CRITICAL - Implement Immediately:

  1. Replace innerHTML with safe DOM methods:

    // UNSAFE (current)
    div.innerHTML = '<span>' + userInput + '</span>';
    
    // SAFE (recommended)
    const span = document.createElement('span');
    span.textContent = userInput;  // Auto-escapes HTML
    div.appendChild(span);
    
  2. Sanitize API responses on client-side:

    function escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }
    
    headerContent = '<span>' + escapeHtml(series.series_desc) + '</span>';
    
  3. Implement Content Security Policy (CSP):

    Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:
    
  4. Server-side output encoding: Escape HTML entities in /api/series responses before sending to client


10. SSRF Sinks

Methodology

This analysis identifies server-side request mechanisms where user input could influence the destination, protocol, or parameters of outbound HTTP requests, potentially allowing attackers to force the server to make requests to unintended internal or external resources.

Confirmed SSRF Vulnerabilities

1. OAuth 2.0 Redirect URI Handler (MEDIUM SEVERITY)

File: /repos/inou-portal/portal/oauth.go

Vulnerable Code:

// Lines 59-60: User-controlled redirect_uri parameter
clientID := r.URL.Query().Get("client_id")
redirectURI := r.URL.Query().Get("redirect_uri")

// Line 88: Whitelist validation
if !lib.OAuthClientValidRedirectURI(client, redirectURI) {
    oauthError(w, "invalid_request", "Invalid redirect_uri for this client", http.StatusBadRequest)
    return
}

// Lines 131-139: URL parsing and HTTP redirect
redirectURL, _ := url.Parse(redirectURI)
q := redirectURL.Query()
q.Set("code", code.Code)
if state != "" {
    q.Set("state", state)
}
redirectURL.RawQuery = q.Encode()
http.Redirect(w, r, redirectURL.String(), http.StatusSeeOther)

Sink Type: HTTP 303 Redirect with user-controlled URL

Validation Mechanism (/repos/inou-portal/lib/db_queries.go):

func OAuthClientValidRedirectURI(client *OAuthClient, uri string) bool {
    for _, u := range client.RedirectURIs {
        if u == uri {  // Exact string match
            return true
        }
    }
    return false
}

Attack Surface:

  • User Input: redirect_uri query parameter
  • Validation: Whitelist-based (exact match against pre-registered URIs)
  • Registered URIs (hardcoded in oauth.go:365-379):
    redirectURIs := []string{
        "https://claude.ai/api/mcp/auth_callback",
        "https://claude.com/api/mcp/auth_callback",
        "http://localhost:6274/oauth/callback",
        "http://localhost:6274/oauth/callback/debug",
    }
    

Exploitability: POSSIBLE (requires attacker-controlled OAuth client registration)

Attack Scenarios:

  1. Malicious OAuth Client Registration:

    • Attacker registers OAuth client via /register endpoint
    • Specifies internal service as redirect_uri: http://169.254.169.254/latest/meta-data/
    • Tricks user into authorizing the malicious client
    • Authorization code sent to cloud metadata service (AWS IMDS)
  2. Subdomain Takeover:

    • If any registered redirect_uri uses a subdomain vulnerable to takeover
    • Attacker claims subdomain, receives authorization codes

Current Mitigation:

  • Whitelist validation (strong defense)
  • Exact string matching (no wildcard or regex bypass)

Residual Risk:

  • Dynamic client registration allows arbitrary redirect_uri registration
  • No validation of redirect_uri against internal IP ranges (RFC 1918, link-local)
  • HTTP localhost redirects allowed (could target internal services on 127.0.0.1)

Recommendations:

  1. Implement redirect_uri blocklist for internal IP ranges:
    // Block RFC 1918 private networks
    blocked := []string{"10.", "172.16.", "192.168.", "127.", "169.254."}
    for _, prefix := range blocked {
        if strings.HasPrefix(parsedURL.Hostname(), prefix) {
            return false
        }
    }
    
  2. Require HTTPS for all redirect_uris (except localhost for development)
  3. Restrict OAuth client registration to authenticated administrators
  4. Implement redirect_uri domain whitelist (only allow specific TLDs)

2. Google Gemini LLM API Integration (LOW SEVERITY)

File: /repos/inou-portal/lib/llm.go

Vulnerable Code:

// Line 109: URL construction with config.Model parameter
url := fmt.Sprintf("https://generativelanguage.googleapis.com/v1beta/models/%s:generateContent?key=%s",
    *config.Model, GeminiKey)

// Line 111: HTTP POST request
resp, err := http.Post(url, "application/json", bytes.NewReader(jsonBody))

Sink Type: HTTP POST with partially user-controllable URL

User Input: config.Model (if user-controllable via GeminiConfig parameter)

Attack Surface:

  • Default Model (line 83): "gemini-2.0-flash" (hardcoded)
  • Configurable: Line 43 shows model can be overridden in config
  • URL Template: https://generativelanguage.googleapis.com/v1beta/models/{MODEL}:generateContent?key={KEY}

Exploitability: POSSIBLE (low impact)

Attack Scenarios:

  1. Path Traversal in Model Name:

    • If config.Model not validated, attacker could inject:
    • ../../admin/deleteAccount → URL becomes https://generativelanguage.googleapis.com/v1beta/../../admin/deleteAccount:generateContent?key=...
    • Most HTTP clients normalize paths, limiting effectiveness
  2. API Key Leakage:

    • API key visible in URL query parameter
    • Logged in HTTP access logs, proxy logs, browser history
    • Severity: HIGH for credential exposure, but not SSRF

Current Mitigation:

  • Hardcoded base URL (HTTPS, Google domain)
  • Model name appears to be from internal config, not direct user input

Recommendations:

  1. Validate model parameter against whitelist:
    allowedModels := []string{"gemini-2.0-flash", "gemini-pro", "gemini-ultra"}
    if !contains(allowedModels, config.Model) {
        return errors.New("Invalid model")
    }
    
  2. Move API key to HTTP header (remove from URL):
    req, _ := http.NewRequest("POST", url, bytes.NewReader(jsonBody))
    req.Header.Set("X-Goog-Api-Key", GeminiKey)
    resp, err := http.DefaultClient.Do(req)
    

Non-SSRF Outbound Requests (Properly Secured)

Internal Service Communication (Localhost)

API Proxy (/repos/inou-portal/portal/api_proxy.go:22):

const apiBackend = "http://127.0.0.1:8082"
  • Hardcoded URL: No user control over destination
  • Purpose: Reverse proxy from portal to API backend
  • SSRF Risk: None (localhost, hardcoded)

Signal Messenger Integration (/repos/inou-portal/lib/signal.go:10):

const signalAPI = "http://192.168.1.16:8080/api/v1/rpc"
  • Hardcoded URL: Internal service IP
  • Message Content: User-controlled, but URL is not
  • SSRF Risk: None (no URL manipulation)

SMTP Email Delivery

File: /repos/inou-portal/lib/email.go:49

client, err := smtp.Dial(smtpHost + ":" + smtpPort)
  • Configuration Source: smtp.env file (not user input)
  • SSRF Risk: None (admin-controlled config)

SSRF Sinks NOT Found (Explicitly Searched)

The following SSRF-prone patterns were searched and not found in network-accessible code:

Webhook/Ping Endpoints: No "ping my URL" functionality detected ✓ URL Fetch/Import: No "import from URL" features ✓ Link Preview/Unfurl: No URL metadata fetching ✓ JWKS/OIDC Discovery: OAuth implementation uses hardcoded endpoints ✓ RSS/Feed Readers: No feed parsing functionality ✓ Image/Media from URL: Images loaded from local object storage only ✓ Headless Browser Navigation: No Puppeteer/Playwright usage detected ✓ HTML-to-PDF with URL: No server-side rendering from URLs


SSRF Summary Table

Sink Location Type User Input Validation Risk Level Exploitable
oauth.go:131-139 HTTP Redirect redirect_uri Whitelist MEDIUM POSSIBLE
llm.go:109 HTTP POST config.Model None LOW POSSIBLE
api_proxy.go:22 HTTP Proxy None N/A NONE NO
signal.go:10 HTTP POST None N/A NONE NO
email.go:49 SMTP Dial None N/A NONE NO

Global Mitigations:

  1. Implement egress firewall rules: Block outbound requests to internal IP ranges from application servers
  2. Use HTTP client with IP blocklist:
    func isSafeURL(urlStr string) bool {
        parsed, _ := url.Parse(urlStr)
        ip := net.ParseIP(parsed.Hostname())
        return !ip.IsPrivate() && !ip.IsLoopback() && !ip.IsLinkLocalUnicast()
    }
    
  3. Log all outbound HTTP requests with destination, status code, and caller for audit
  4. Network segmentation: Application servers should not have access to cloud metadata services (AWS IMDS, GCP metadata)