# 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
```go
if code != 250365 && (d.AuthCode != code || ...) // TODO: Remove backdoor
```
- Returns: `{token: session_token, needs_onboard: bool}`
- Session token persisted indefinitely in database
### Session Management & Cookie Security
**Login Cookie Configuration** (`/repos/inou-portal/portal/main.go:311-317`):
```go
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`:
```go
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`:
```go
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:
```sql
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:
```go
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`):
```go
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`):
```go
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`):
```go
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`:
```go
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:
```sql
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`:
```go
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`):
```go
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`):
```go
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:
```go
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`):
```go
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`):
```go
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`):
```go
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`):
```go
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:
```go
/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`):
```go
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`):
```bash
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**:
```go
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`):
```go
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`):
```go
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**:
```http
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`):
```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`):
```go
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:
```go
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:
```go
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."
```go
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:
```go
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
```javascript
headerContent = '' + seriesList[0].series_desc + ' (' + seriesList[0].slice_count + ')';
```
- **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**: `
`
- **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
```javascript
seriesList.map(s => '').join('')
```
- **Sink Type**: `innerHTML` (HTML Element Context)
- **Attack Vector**: Same as line 442, but rendered in `