60 KiB
Reconnaissance Deliverable: Inou Health Portal Attack Surface Analysis
0) HOW TO READ THIS
This reconnaissance report provides a comprehensive map of the application's attack surface, with special emphasis on authorization and privilege escalation opportunities for the Authorization Analysis Specialist.
Key Sections for Authorization Analysis:
- Section 4 (API Endpoint Inventory): Contains authorization details for each endpoint - focus on "Required Role" and "Object ID Parameters" columns to identify IDOR candidates.
- Section 6.4 (Guards Directory): Catalog of authorization controls - understand what each guard means before analyzing vulnerabilities.
- Section 7 (Role & Privilege Architecture): Complete role hierarchy and privilege mapping - use this to understand the privilege lattice and identify escalation targets.
- Section 8 (Authorization Vulnerability Candidates): Pre-prioritized lists of endpoints for horizontal, vertical, and context-based authorization testing.
How to Use the Network Mapping (Section 6): The entity/flow mapping shows system boundaries and data sensitivity levels. Pay special attention to flows marked with authorization guards and entities handling PII/sensitive data.
Priority Order for Testing: Start with Section 8's High-priority horizontal candidates, then vertical escalation endpoints for each role level, finally context-based workflow bypasses.
1. Executive Summary
Inou Health is a health data management platform that enables users to securely organize and share their medical records with AI assistants. The application uses a Go-based backend with custom frameworks, passwordless email authentication, and a permission-based authorization model.
Core Technology Stack:
- Frontend: Server-side rendered HTML templates (Go html/template)
- Backend: Go (Golang) with standard library net/http
- Infrastructure: Self-hosted on Linux (/tank/inou directory structure)
- Database: SQLite with AES-256-GCM encryption at rest
- Authentication: Custom passwordless email (6-digit verification codes)
- Authorization: Custom RBAC with bitmask permissions (read/write/delete/manage)
Primary User-Facing Components:
- Public marketing pages (/, /pricing, /demo)
- Passwordless authentication flow (/start, /send-code, /verify)
- Dossier (health record) management dashboard
- File upload system for medical imaging, lab results, genetics data
- OAuth 2.0 server for third-party AI integrations (Claude Desktop, ChatGPT)
- Model Context Protocol (MCP) server for AI assistant integrations
- RESTful JSON API (v1) for mobile/programmatic access
Attack Surface Scope: Network-accessible web application endpoints only. Local CLI tools, build scripts, and development utilities are out of scope.
2. Technology & Service Map
Frontend
- Framework: Server-side rendered Go templates (html/template package)
- JavaScript: Minimal vanilla JS for UI interactions
- Authentication UI: Custom passwordless login forms
- Key Libraries: None identified (vanilla HTML/CSS/JS)
Backend
- Language: Go (Golang) 1.x
- Framework: Standard library net/http with http.ServeMux router
- Key Dependencies:
golang.org/x/crypto- AES-256-GCM encryption, bcryptmodernc.org/sqlite- Pure Go SQLite implementation- Standard library only (minimal external dependencies)
- DICOM Processing: External binary at
/tank/inou/bin/import-dicom - LLM Integration: Google Gemini API for medical data parsing
Infrastructure
- Hosting: Self-hosted Linux server
- Web Server: Go net/http (ports 8443 for portal, 8082 for API)
- Database: SQLite (inou.db for main data, auth.db for OAuth)
- File Storage: Local filesystem at
/tank/inou/uploads/(encrypted) - Encryption: FIPS 140-3 compliant AES-256-GCM with master key at
/tank/inou/master.key - DICOM Viewer: Separate service on localhost:8765 (Orthanc or similar)
Identified Subdomains
- Primary Domain: inou.com
- No subdomains discovered - single-domain application
Open Ports & Services
- Port 8443 (HTTPS): Main portal web server (public-facing)
- Port 8082 (HTTP): Internal API server (localhost only, proxied by portal)
- Port 8765 (HTTP): DICOM viewer service (localhost only, proxied by portal)
Note: External port scanning not performed per scope. Information derived from source code analysis.
3. Authentication & Session Management Flow
Entry Points
- GET /start - Passwordless login page (email entry)
- POST /send-code - Sends 6-digit verification code to email
- POST /verify - Validates verification code and creates session
- GET /oauth/authorize - OAuth 2.0 authorization endpoint for third-party apps
- POST /oauth/token - OAuth 2.0 token exchange endpoint
Mechanism - Step-by-Step Process
Web Portal Authentication Flow
-
Email Submission (
POST /send-code)- User enters email at /start
- Bot detection via nonce parameter (must be >= 2000ms)
- Email normalized:
strings.ToLower(strings.TrimSpace(email)) - 6-digit verification code generated via crypto/rand
- Code stored encrypted in database with 10-minute expiry
- Code sent via email (SMTP)
- Code Location:
/repos/inou-portal/portal/main.go:540-557
-
Code Verification (
POST /verify)- User submits email + 6-digit code
- Code validated against database (single-use)
- Backdoor code
250365exists for testing (SECURITY NOTE) - Code Location:
/repos/inou-portal/portal/main.go:560-590,/repos/inou-portal/lib/dbcore.go:330-353
-
Session Creation
- 30-day persistent cookie named
logincreated - Cookie value: plain dossier ID (16-char hex)
- Cookie attributes:
HttpOnly=true, Secure=true, SameSite=Lax - No server-side session expiration tracking
- Code Location:
/repos/inou-portal/portal/main.go:311-313
- 30-day persistent cookie named
-
Profile Completeness Check
- Redirect to
/onboardif name/DOB/sex not set - Redirect to
/dashboardif profile complete - Code Location:
/repos/inou-portal/portal/main.go:582-589
- Redirect to
Mobile API Authentication Flow
-
Code Request (
POST /api/v1/auth/send)- JSON API endpoint for mobile clients
- Generates 6-digit code with 10-minute expiry
- Stores code in
Dossier.AuthCodefield - Returns success/failure JSON
- Code Location:
/repos/inou-portal/portal/api_mobile.go:55-103
-
Token Generation (
POST /api/v1/auth/verify)- Validates 6-digit code (same backdoor exists)
- Generates or reuses 64-char hex session token
- Session token stored in
Dossier.SessionTokenfield (never expires) - Returns
{"token": "...", "needs_onboard": true/false} - Code Location:
/repos/inou-portal/portal/api_mobile.go:105-149
OAuth 2.0 Flow (for AI Integrations)
-
Authorization Request (
GET /oauth/authorize)- Supports Authorization Code flow with PKCE (S256)
- Parameters: client_id, redirect_uri, code_challenge, state
- Validates client_id and redirect_uri against whitelist
- Redirects to /start if user not logged in
- Generates 10-minute authorization code (64-char hex)
- Code Location:
/repos/inou-portal/portal/oauth.go:52-140
-
Token Exchange (
POST /oauth/token)- Grant types:
authorization_code,refresh_token - PKCE validation: SHA256(code_verifier) == code_challenge
- Returns encrypted access token (15 minutes) + refresh token (30 days)
- Access token format: AES-encrypted JSON
{d: dossierID, exp: timestamp} - Refresh token: 64-char hex stored in auth.db (PLAINTEXT)
- Code Location:
/repos/inou-portal/portal/oauth.go:144-296
- Grant types:
Code Pointers
Authentication Logic:
- Main login handler:
/repos/inou-portal/portal/main.go:658-662(handleLogin) - Send code:
/repos/inou-portal/portal/main.go:540-557(handleSendCode) - Verify code:
/repos/inou-portal/portal/main.go:560-590(handleVerify) - Code generation:
/repos/inou-portal/lib/dbcore.go:291-327(DossierLogin) - Code validation:
/repos/inou-portal/lib/dbcore.go:330-353(DossierVerify)
Session Management:
- Cookie creation:
/repos/inou-portal/portal/main.go:311-313(setLoginCookie) - Session validation:
/repos/inou-portal/portal/main.go:279-285(getLoggedInDossier) - Session token lookup:
/repos/inou-portal/lib/dbcore.go:356-364(DossierGetBySessionToken)
OAuth Implementation:
- Authorization endpoint:
/repos/inou-portal/portal/oauth.go:52-140 - Token endpoint:
/repos/inou-portal/portal/oauth.go:144-296 - Token creation:
/repos/inou-portal/lib/crypto.go:174-182(TokenCreate) - Token validation:
/repos/inou-portal/lib/crypto.go:186-207(TokenParse) - PKCE verification:
/repos/inou-portal/lib/db_queries.go:861-875
3.1 Role Assignment Process
Role Determination: This application does NOT use traditional roles. Instead, it uses a permission-based system with bitmask capabilities.
Permission Assignment on Authentication:
- New users get implicit owner permissions on their own dossier (self-access)
- Self-access check:
if accessorID == dossierID { return true }(automatic, no database record) - Code Location:
/repos/inou-portal/lib/rbac.go:23-25
Permission Grant Process:
- Users with PermManage (bit 8) can grant permissions to others
- Permissions granted via
/dossier/{id}/permissionspage - Permission templates available: parent/guardian (read+write+manage), caregiver/medical (read+write), custom
- Templates converted to bitmask at grant time
- Code Location:
/repos/inou-portal/portal/main.go:1376-1464
Default Permissions:
- Self (owner): All permissions (implicit, not stored)
- Newly granted access: Depends on role template selected by grantor
Permission Upgrade Path:
- Only users with PermManage can grant/modify permissions
- No self-service upgrade mechanism
- No automatic privilege escalation
3.2 Privilege Storage & Validation
Storage Location:
- Database Table:
accesstable in inou.db - Schema:
{AccessID, DossierID, GranteeID, EntryID, Relation, Ops, CreatedAt} - Ops Field: Integer bitmask (1=read, 2=write, 4=delete, 8=manage)
- Encryption: Access records encrypted at rest via AES-256-GCM
- Code Location:
/repos/inou-portal/lib/types.go:189-197
Alternative Storage (for authentication only):
- Session Cookies: Contain dossier ID only (no permissions stored)
- OAuth Access Tokens: Encrypted JSON with
{d: dossierID, exp: timestamp}(no permissions) - Mobile Session Tokens: Random 64-char hex (lookup dossier ID only)
Validation Points:
- API Middleware:
requireDossierAccess(),requireEntryAccess(),requireManageAccess()- Location:
/repos/inou-portal/api/auth.go:87-131
- Location:
- V1 API:
v1CanAccess(),v1CanWrite()- Location:
/repos/inou-portal/api/api_v1.go:61-86
- Location:
- Core RBAC:
CheckAccess(accessorID, dossierID, entryID, perm)- Location:
/repos/inou-portal/lib/rbac.go:19-54
- Location:
Validation Process:
- Extract dossier ID from cookie/token/header
- Query
accesstable for grants whereGranteeID = accessorID AND DossierID = targetDossierID - Check if
(grant.Ops & requestedPermission) != 0 - Special cases: self-access (owner), SystemContext, empty accessorID bypass checks
Cache/Session Persistence:
- No caching: Permissions checked on every request via database query
- Session Duration:
- Portal cookies: 30 days
- Mobile session tokens: Never expire
- OAuth access tokens: 15 minutes (then must refresh)
3.3 Role Switching & Impersonation
Impersonation Features: NONE - Not implemented
Role Switching: NONE - Not implemented
Privilege Escalation Mechanisms: NONE - No "sudo mode" or temporary elevation
Audit Trail:
- Audit log exists at
/dossier/{id}/auditshowing access grants/revokes - No logging of impersonation (feature doesn't exist)
- Code Location:
/repos/inou-portal/portal/main.go:1262-1278(handleAuditLog)
System Context Bypass:
- SystemAccessorID: Hardcoded value
7b3a3ee1c2776dcdbypasses all RBAC - Empty AccessorID: Empty string
""also bypasses all checks - Localhost Bypass: Requests from 127.0.0.1 or ::1 get SystemContext (full access)
- Code Locations:
- SystemAccessorID:
/repos/inou-portal/lib/config.go:27 - Localhost bypass:
/repos/inou-portal/api/auth.go:151-156 - Empty accessor bypass:
/repos/inou-portal/lib/rbac.go:20-22
- SystemAccessorID:
4. API Endpoint Inventory
Network Surface Focus: Only includes API endpoints accessible through the target web application. Excludes development/debug endpoints, local-only utilities, and components unreachable via network requests.
Portal Server Endpoints (Port 8443)
| Method | Endpoint Path | Required Role | Object ID Parameters | Authorization Mechanism | Description & Code Pointer |
|---|---|---|---|---|---|
| GET/POST | / |
anon | None | None | Public landing page. /repos/inou-portal/portal/main.go:516 |
| POST | /send-code |
anon | None | Bot detection (nonce) | Sends 6-digit verification code. /repos/inou-portal/portal/main.go:540 |
| POST | /verify |
anon | None | None | Validates verification code, creates session. /repos/inou-portal/portal/main.go:560 |
| GET/POST | /onboard |
user | None | Cookie login required |
Profile completion (name/DOB/sex). /repos/inou-portal/portal/main.go:593 |
| GET | /logout |
anon | None | None | Clears login cookie. /repos/inou-portal/portal/main.go:638 |
| POST | /set-lang |
anon | None | None | Sets language preference. /repos/inou-portal/portal/main.go:643 |
| GET | /start |
anon | None | None | Login page. /repos/inou-portal/portal/main.go:658 |
| GET | /dashboard |
user | None | Cookie login |
User dashboard showing all dossiers. /repos/inou-portal/portal/main.go:831 |
| GET | /connect |
anon | None | None | AI integration setup instructions. /repos/inou-portal/portal/main.go:664 |
| GET/POST | /invite |
user | None | Cookie login |
Invite management page. /repos/inou-portal/portal/main.go:795 |
| GET | /demo |
anon | None | None | Demo dossier (ID: 1111111111111111). /repos/inou-portal/portal/main.go:867 |
| GET | /pricing |
anon | None | None | Pricing tiers page. /repos/inou-portal/portal/main.go:694 |
| GET | /privacy-policy |
anon | None | None | Privacy policy. /repos/inou-portal/portal/main.go:689 |
| GET | /legal/terms |
anon | None | None | Terms of service. /repos/inou-portal/portal/main.go:709 |
| GET | /legal/dpa |
anon | None | None | Data Processing Agreement. /repos/inou-portal/portal/main.go:704 |
| GET/POST | /dossier/add |
user | None | Cookie login |
Create new dossier. /repos/inou-portal/portal/main.go:906 |
| GET | /dossier/{id} |
user | id (dossierID) |
PermRead on dossier | View dossier details. /repos/inou-portal/portal/dossier_sections.go:678 |
| GET/POST | /dossier/{id}/edit |
user | id |
PermWrite on dossier | Edit dossier profile. /repos/inou-portal/portal/main.go:977 |
| GET/POST | /dossier/{id}/share |
user | id |
PermManage on dossier | Share access via email invitation. /repos/inou-portal/portal/main.go:1193 |
| POST | /dossier/{id}/revoke |
user | id, granteeId (POST param) |
PermManage on dossier | Revoke access from user. /repos/inou-portal/portal/main.go:1326 |
| GET | /dossier/{id}/audit |
user | id |
PermRead on dossier | View access audit log. /repos/inou-portal/portal/main.go:1262 |
| GET | /dossier/{id}/export |
user | id |
PermRead on dossier | Download dossier data as JSON. /repos/inou-portal/portal/main.go:1100 |
| GET/POST | /dossier/{id}/permissions |
user | id |
PermManage on dossier | Manage permissions (grant/revoke access). /repos/inou-portal/portal/main.go:1376 |
| GET/POST | /dossier/{id}/rbac/{role} |
user | id, role (path param) |
PermManage on dossier | Edit role-based permissions. /repos/inou-portal/portal/main.go:1531 |
| GET/POST | /dossier/{id}/access/{granteeId} |
user | id, granteeId |
PermManage on dossier | Edit specific access grant. /repos/inou-portal/portal/main.go:1669 |
| GET | /dossier/{id}/trackers |
user | id |
PermRead on dossier | Daily check-in trackers (vitals, medications, etc.). /repos/inou-portal/portal/trackers.go:78 |
| GET | /dossier/{id}/trackers/card/{trackerId} |
user | id, trackerId |
PermRead | Render tracker card HTML. /repos/inou-portal/portal/trackers.go:473 |
| POST | /dossier/{id}/trackers/respond |
user | id, tracker_id (POST) |
PermWrite | Submit tracker response. /repos/inou-portal/portal/trackers.go:348 |
| GET | /dossier/{id}/upload |
user | id |
PermRead | File upload page. /repos/inou-portal/portal/upload.go:117 |
| POST | /dossier/{id}/upload |
user | id |
PermWrite | Upload medical files (imaging, labs, genetics). /repos/inou-portal/portal/upload.go:153 |
| DELETE | /dossier/{id}/files/{fileId}/delete |
user | id, fileId |
PermDelete | Delete uploaded file. /repos/inou-portal/portal/upload.go:261 |
| POST | /dossier/{id}/files/{fileId}/update |
user | id, fileId |
PermWrite | Update file metadata. /repos/inou-portal/portal/upload.go:306 |
| GET | /dossier/{id}/files/{fileId}/status |
user | id, fileId |
PermRead | Check file processing status. /repos/inou-portal/portal/main.go:1970 |
| POST | /dossier/{id}/process-imaging |
user | id |
PermWrite | Process uploaded DICOM imaging files. /repos/inou-portal/portal/upload.go:364 |
| GET | /image/{id} |
user | id (entryID) |
PermRead on entry | Retrieve medical image (proxied to API). /repos/inou-portal/portal/api_proxy.go:57 |
| GET | /contact-sheet.webp/{id} |
user | id (studyID) |
PermRead on entry | Contact sheet preview image. /repos/inou-portal/portal/api_proxy.go:57 |
| GET | /api |
anon | None | None | API documentation page. /repos/inou-portal/portal/main.go:717 |
| POST | /api/token/generate |
user | None | Cookie login |
Generate long-lived API token. /repos/inou-portal/portal/main.go:726 |
| POST | /api/token/regenerate |
user | None | Cookie login |
Regenerate API token (invalidates old). /repos/inou-portal/portal/main.go:747 |
| GET | /api/openapi.yaml |
anon | None | None | OpenAPI specification. /repos/inou-portal/portal/main.go:768 |
| GET | /api/docs |
anon | None | None | API documentation (Swagger). /repos/inou-portal/portal/main.go:773 |
Mobile API Endpoints (Portal Server)
| Method | Endpoint Path | Required Role | Object ID Parameters | Authorization Mechanism | Description & Code Pointer |
|---|---|---|---|---|---|
| POST | /api/v1/auth/send |
anon | None | None | Mobile: Send verification code. /repos/inou-portal/portal/api_mobile.go:55 |
| POST | /api/v1/auth/verify |
anon | None | None | Mobile: Verify code, get session token. /repos/inou-portal/portal/api_mobile.go:105 |
| GET | /api/v1/dashboard |
user | None | Bearer token | Mobile: Dashboard data (all dossiers). /repos/inou-portal/portal/api_mobile.go:165 |
| GET | /api/v1/trackers |
user | dossier (query) |
Bearer token | Mobile: Get trackers for dossier. /repos/inou-portal/portal/api_mobile.go:209 |
| POST | /api/v1/trackers/respond |
user | dossier (query) |
Bearer token | Mobile: Submit tracker response. /repos/inou-portal/portal/api_mobile.go:220 |
OAuth 2.0 Endpoints
| Method | Endpoint Path | Required Role | Object ID Parameters | Authorization Mechanism | Description & Code Pointer |
|---|---|---|---|---|---|
| GET | /oauth/authorize |
user | None | Cookie login (redirects if missing) |
OAuth authorization endpoint (PKCE supported). /repos/inou-portal/portal/oauth.go:52 |
| POST | /oauth/token |
anon | None | Client credentials | Token exchange (authorization_code, refresh_token grants). /repos/inou-portal/portal/oauth.go:144 |
| GET | /oauth/userinfo |
user | None | Bearer token | UserInfo endpoint (OpenID Connect compatible). /repos/inou-portal/portal/oauth.go:298 |
| POST | /oauth/revoke |
anon | None | Client credentials | Revoke refresh token. /repos/inou-portal/portal/oauth.go:337 |
MCP (Model Context Protocol) Endpoints
| Method | Endpoint Path | Required Role | Object ID Parameters | Authorization Mechanism | Description & Code Pointer |
|---|---|---|---|---|---|
| GET | /.well-known/oauth-protected-resource |
anon | None | None | OAuth resource metadata. /repos/inou-portal/portal/mcp_http.go:26 |
| GET | /.well-known/oauth-authorization-server |
anon | None | None | OAuth server metadata. /repos/inou-portal/portal/mcp_http.go:54 |
| POST | /register |
anon | None | None | Dynamic client registration (OAuth). /repos/inou-portal/portal/mcp_http.go:88 |
| POST | /mcp |
user | None | Bearer token | MCP JSON-RPC endpoint (AI tools/prompts). /repos/inou-portal/portal/mcp_http.go:178 |
API Server Endpoints (Port 8082 - Proxied via Portal)
| Method | Endpoint Path | Required Role | Object ID Parameters | Authorization Mechanism | Description & Code Pointer |
|---|---|---|---|---|---|
| GET | /api/version |
anon | None | None | API version info. /repos/inou-portal/api/api_version.go:8 |
| GET | /api/dossiers |
user | None | Bearer/session token | List accessible dossiers. /repos/inou-portal/api/api_dossiers.go:10 |
| GET/POST | /api/dossier |
user/system | dossier (query) |
Bearer/session/localhost | Get/create dossier. /repos/inou-portal/api/api_dossier.go:29 |
| GET | /api/studies |
user | dossier (query) |
Bearer/session + PermRead | List medical imaging studies. /repos/inou-portal/api/api_studies.go:10 |
| GET | /api/series |
user | dossier, study (query) |
Bearer/session + PermRead | List imaging series. /repos/inou-portal/api/api_series.go:12 |
| GET | /api/slices |
user | dossier, series (query) |
Bearer/session + PermRead | List image slices. /repos/inou-portal/api/api_slices.go:10 |
| GET | /image/{id} |
user | id (entryID) |
PermRead on entry | Retrieve medical image file. /repos/inou-portal/api/api_image.go:57 |
| GET | /contact-sheet.webp/{id} |
user | id (studyID) |
PermRead on entry | Generate contact sheet image. /repos/inou-portal/api/api_contact_sheet.go:61 |
| GET | /api/labs/tests |
user | dossier (query) |
PermRead | List lab test types. /repos/inou-portal/api/api_labs.go:11 |
| GET | /api/labs/results |
user | dossier (query) |
PermRead | Get lab results. /repos/inou-portal/api/api_labs.go:39 |
| GET | /api/access |
system | None | Localhost only | List access grants (internal). /repos/inou-portal/api/api_access.go:49 |
| POST | /api/access |
system | None | Localhost only | Grant/revoke access (internal). /repos/inou-portal/api/api_access.go:101 |
| GET/POST | /api/audit |
user | dossier (query) |
PermRead | Get audit log entries. /repos/inou-portal/api/api_audit.go:25 |
| GET | /api/entries |
user | dossier (query) |
PermRead | List entries (medical records). /repos/inou-portal/api/api_entries.go:45 |
| POST | /api/entries |
user/system | None | Bearer/session/localhost + PermWrite | Create/update entries. /repos/inou-portal/api/api_entries.go:45 |
| GET/POST | /api/trackers |
user | dossier (query) |
Bearer/session | Get/update trackers. /repos/inou-portal/api/api_trackers.go:177 |
| POST | /api/trackers/freeform |
user | dossier (query) |
Bearer/session + PermWrite | Submit freeform tracker entry. /repos/inou-portal/api/api_trackers.go:319 |
V1 RESTful API Endpoints (API Server)
| Method | Endpoint Path | Required Role | Object ID Parameters | Authorization Mechanism | Description & Code Pointer |
|---|---|---|---|---|---|
| GET | /api/v1/health |
anon | None | None | Health check endpoint. /repos/inou-portal/api/api_v1.go:702 |
| POST | /api/v1/token |
user | None | Bearer token | Generate new access token. /repos/inou-portal/api/api_v1.go:105 |
| GET | /api/v1/dossiers |
user | None | Bearer token | List accessible dossiers. /repos/inou-portal/api/api_v1.go:136 |
| GET | /api/v1/dossiers/{id} |
user | id |
Bearer token + PermRead | Get dossier details. /repos/inou-portal/api/api_v1.go:189 |
| GET | /api/v1/dossiers/{id}/entries |
user | id |
Bearer token + PermRead | List entries in dossier. /repos/inou-portal/api/api_v1.go:212 |
| GET | /api/v1/dossiers/{id}/entries/{entryId} |
user | id, entryId |
Bearer token + PermRead | Get specific entry. /repos/inou-portal/api/api_v1.go:277 |
| GET | /api/v1/dossiers/{id}/access |
user | id |
Bearer token + PermRead | List access grants for dossier. /repos/inou-portal/api/api_v1.go:335 |
| GET | /api/v1/dossiers/{id}/audit |
user | id |
Bearer token + PermRead | Get audit log. /repos/inou-portal/api/api_v1.go:365 |
| GET | /api/v1/dossiers/{id}/trackers |
user | id |
Bearer token + PermRead | Get trackers. /repos/inou-portal/api/api_v1.go:563 |
| GET | /api/v1/dossiers/{id}/journal |
user | id |
Bearer token + PermRead | List journal entries. /repos/inou-portal/api/api_v1.go:738 |
| GET | /api/v1/dossiers/{id}/journal/{entryId} |
user | id, entryId |
Bearer token + PermRead | Get journal entry. /repos/inou-portal/api/api_v1.go:777 |
| POST | /api/v1/dossiers/{id}/journal |
user | id |
Bearer token + PermWrite | Create journal entry. /repos/inou-portal/api/api_v1.go:804 |
| PATCH | /api/v1/dossiers/{id}/journal/{entryId} |
user | id, entryId |
Bearer token + PermWrite | Update journal entry. /repos/inou-portal/api/api_v1.go:864 |
| POST | /api/v1/dossiers/{id}/parse |
user | id |
Bearer token + PermRead | Parse medical data with LLM. /repos/inou-portal/api/api_v1.go:444 |
| GET | /api/v1/images/{id} |
user | id |
Bearer token + PermRead | Get image file. /repos/inou-portal/api/api_v1.go:608 |
| GET | /api/v1/categories |
user | None | Bearer token (optional) | List entry categories. /repos/inou-portal/api/api_v1.go:667 |
| GET | /api/v1/categories/{name}/types |
anon | name |
None | List types within category. /repos/inou-portal/api/api_v1.go:684 |
Total Network-Accessible Endpoints: 80+ endpoints across portal, API, OAuth, and MCP servers.
5. Potential Input Vectors for Vulnerability Analysis
Network Surface Focus: Only includes input vectors accessible through the target web application's network interface. Excludes inputs from local-only scripts, build tools, development utilities, or components unreachable via network requests.
URL Parameters
Dossier/Object Identifiers:
id(path parameter) - Dossier ID in/dossier/{id}/*routesdossierId(path parameter) - Dossier ID in various routesfileId(path parameter) - File ID in/dossier/{id}/files/{fileId}/*trackerId(path parameter) - Tracker ID in tracker routesentryId(path parameter) - Entry ID in V1 API routesgranteeId(path parameter) - User ID in access management routesdossier(query parameter) - Filter parameter in API endpoints
Filtering & Display:
token- Authentication token (query parameter fallback)lang- Language code (defaults to "en")wc- Window center for image rendering (float)ww- Window width for image rendering (float)maxdim- Max dimension for image scaling (integer)full- Full image flag (boolean "1")add- Tracker type to add (e.g.,?add=vital)
OAuth Parameters:
client_id- OAuth client identifierredirect_uri- OAuth redirect URL (whitelist validated)response_type- OAuth response type (must be "code")state- OAuth state parameter (passed through)code_challenge- PKCE code challengecode_challenge_method- PKCE method (must be "S256")code- Authorization codecode_verifier- PKCE code verifier
File Locations: /repos/inou-portal/api/api_v1.go:27, /repos/inou-portal/api/api_image.go:131-141, /repos/inou-portal/portal/oauth.go:59-64
POST Body Fields (Form-Encoded)
Authentication:
email- Email address (normalized: toLowerCase + trimSpace) -/repos/inou-portal/portal/main.go:543code- 6-digit verification code (converted to int) -/repos/inou-portal/portal/main.go:563,566nonce- Bot detection timing value (must be >= 2000) -/repos/inou-portal/portal/main.go:542
Profile Management:
name- User display name (trimSpace) -/repos/inou-portal/portal/main.go:601dob- Date of birth (YYYY-MM-DD format, validated >= 1900-01-01) -/repos/inou-portal/portal/main.go:602,608-611sex- Gender (converted: "" → 0, "M" → 1, "F" → 2) -/repos/inou-portal/portal/main.go:603,622-623email_lang- Email language preference -/repos/inou-portal/portal/main.go:920relation- Relationship type (converted to int) -/repos/inou-portal/portal/main.go:921,937is_care_receiver- Care receiver flag (boolean "1" check) -/repos/inou-portal/portal/main.go:922
Access Sharing:
recipient_name- Name of person being granted access (trimSpace) -/repos/inou-portal/portal/main.go:1216can_edit- Edit permission flag (boolean "1" check) -/repos/inou-portal/portal/main.go:1220action- Grant or revoke action -/repos/inou-portal/portal/main.go:1396role- Permission role template (parent/guardian/caregiver/medical/custom) -/repos/inou-portal/portal/main.go:1401op_w,op_d,op_m- Write/delete/manage permission flags (boolean "1") -/repos/inou-portal/portal/main.go:1420-1422
File Upload:
file- Multipart file upload (max 10GB, no type validation) -/repos/inou-portal/portal/upload.go:174path- Relative file path (defaults to filename, uses filepath.Base) -/repos/inou-portal/portal/upload.go:182-186category- File category (genetics triggers genome processing) -/repos/inou-portal/portal/upload.go:187
Tracker Responses:
tracker_id- Tracker ID being responded to -/repos/inou-portal/portal/trackers.go:368action- Tracker action -/repos/inou-portal/portal/trackers.go:369response_raw- JSON tracker response data -/repos/inou-portal/portal/trackers.go:384
OAuth:
grant_type- OAuth grant type (authorization_code/refresh_token) -/repos/inou-portal/portal/oauth.go:169client_secret- OAuth client secret (bcrypt validated) -/repos/inou-portal/portal/oauth.go:173refresh_token- Refresh token for token rotation -/repos/inou-portal/portal/oauth.go:174
POST Body Fields (JSON-Encoded)
Mobile Authentication:
POST /api/v1/auth/send
{
"email": "string" // No validation
}
File: /repos/inou-portal/portal/api_mobile.go:66
Entry/Journal Creation:
POST /api/v1/dossiers/{id}/journal
{
"dossier": "string", // Dossier ID
"category": "string", // Must be valid category (validated)
"type": "string", // Entry type (no validation)
"value": "string", // Entry value (no validation)
"summary": "string", // Entry summary (no validation)
"data": "string/json", // JSON data (no validation)
"timestamp": int64, // Unix timestamp (no validation)
"delete": bool, // Deletion flag
"delete_category": bool // Delete all in category flag
}
File: /repos/inou-portal/api/api_entries.go:71-86
Access Grant/Revoke:
POST /api/access (localhost only)
{
"accessor": "string", // Hex ID (required)
"target": "string", // Hex ID (required)
"relation": int, // Relation type
"can_edit": bool, // Permission flag
"delete": bool, // Revoke flag
"touch": bool // Update timestamp flag
}
File: /repos/inou-portal/api/api_access.go:108-142
Dossier Creation:
POST /api/dossier
{
"email": "string", // Required, no format validation
"invited_by": "string" // Optional inviter ID
}
File: /repos/inou-portal/api/api_dossier.go:82-85
MCP JSON-RPC:
POST /mcp
{
"method": "string", // tools/list, tools/call, prompts/get, etc.
"params": {...} // Varies by method
}
File: /repos/inou-portal/portal/mcp_http.go:249
HTTP Headers
Authentication:
Authorization: Bearer {token}- Primary authentication method for APIs- Accepts OAuth access tokens (encrypted JSON) OR session tokens (64-char hex)
- File:
/repos/inou-portal/api/auth.go:28-38
CORS (MCP/Mobile API):
Origin- Not validated (wildcard CORS:Access-Control-Allow-Origin: *)- File:
/repos/inou-portal/portal/mcp_http.go:180,/repos/inou-portal/portal/api_mobile.go:21
- File:
Custom Headers:
- None identified in source code
Cookie Values
Authentication:
login- Portal session cookie (30-day expiry)- Value: Plain dossier ID (16-char hex)
- Attributes: HttpOnly, Secure, SameSite=Lax
- File:
/repos/inou-portal/portal/main.go:311-313
Session State:
session- Alternative session cookie (used by some API endpoints)- Value: Session token (64-char hex)
- File:
/repos/inou-portal/api/auth.go:41-47
OAuth Flow State:
oauth_return- Stores return URL during OAuth login flow- File:
/repos/inou-portal/portal/oauth.go:109-115
- File:
Preferences:
- No preference cookies identified (language stored server-side)
File Upload Vectors
Multipart Form Data:
- Endpoint:
POST /dossier/{id}/upload - Field:
file(multipart file) - Max Size: 10GB (10 << 30 bytes)
- Type Validation: NONE - All file types accepted (.exe, .sh, .dll, etc.)
- Extension Filtering: NONE
- MIME Validation: NONE
- Processing: Encrypted and stored at
/tank/inou/uploads/{dossierID}/{fileID} - Special Handling:
category=genetics→ Triggers genome CSV parsing (async goroutine)- DICOM files → Processed by external binary
/tank/inou/bin/import-dicom
- File:
/repos/inou-portal/portal/upload.go:153-259
Genome File Format (CSV):
- Comma or tab delimited
- Columns: rsid, chromosome, position, genotype
- No schema validation before parsing
- File:
/repos/inou-portal/portal/genome.go:24-31
6. Network & Interaction Map
Network Surface Focus: Only maps components that are part of the deployed, network-accessible infrastructure. Excludes local development environments, build CI systems, local-only tools, or components unreachable through the target application's network interface.
6.1 Entities
| Title | Type | Zone | Tech | Data | Notes |
|---|---|---|---|---|---|
| Internet-Users | ExternAsset | Internet | Browser/Mobile | N/A | External users accessing the application |
| Portal-Server | Service | Edge | Go/net-http | PII, Tokens, Secrets | Main web application server (port 8443) |
| API-Server | Service | App | Go/net-http | PII, Medical, Tokens | Backend API service (port 8082, localhost) |
| DICOM-Viewer | Service | App | Orthanc-like | Medical-Imaging | Medical imaging viewer (port 8765, localhost) |
| SQLite-Main | DataStore | Data | SQLite | PII, Medical, Tokens | Main database (inou.db) with AES-256-GCM encryption |
| SQLite-Auth | DataStore | Data | SQLite | OAuth-Tokens | OAuth database (auth.db) - PLAINTEXT refresh tokens |
| Filesystem-Uploads | DataStore | Data | Linux-FS | Medical-Files | Encrypted file storage (/tank/inou/uploads/) |
| Master-Key-File | DataStore | Data | Linux-FS | Secrets | AES master key (/tank/inou/master.key) |
| DICOM-Processor | Service | App | External-Binary | Medical-Imaging | DICOM import tool (/tank/inou/bin/import-dicom) |
| SMTP-Server | ThirdParty | ThirdParty | Public | Email delivery for verification codes | |
| Gemini-API | ThirdParty | ThirdParty | Google-AI | Medical | LLM for parsing medical data |
| Claude-Desktop | ExternAsset | Internet | Anthropic-MCP | N/A | AI assistant connecting via OAuth/MCP |
| ChatGPT | ExternAsset | Internet | OpenAI-MCP | N/A | AI assistant (potential integration) |
6.2 Entity Metadata
| Title | Metadata |
|---|---|
| Portal-Server | Hosts: https://inou.com:8443; Endpoints: /, /dossier/*, /oauth/*, /mcp, /api/v1/auth/*; Auth: Cookie, Bearer; Dependencies: API-Server, SQLite-Main, SQLite-Auth, Filesystem-Uploads, DICOM-Viewer |
| API-Server | Hosts: http://127.0.0.1:8082; Endpoints: /api/*, /image/*; Auth: Bearer, Session, Localhost-Bypass; Dependencies: SQLite-Main, Filesystem-Uploads; Exposure: Localhost-Only |
| DICOM-Viewer | Hosts: http://127.0.0.1:8765; Endpoints: /viewer/*, /data/*; Auth: Proxied; Dependencies: Filesystem-Uploads; Exposure: Localhost-Only |
| SQLite-Main | Path: /tank/inou/inou.db; Tables: entries, access, dossiers; Encryption: AES-256-GCM; Consumers: Portal-Server, API-Server; Schema: Custom ORM with struct tags |
| SQLite-Auth | Path: /tank/inou/auth.db; Tables: oauth_clients, oauth_codes, oauth_refresh_tokens; Encryption: NONE (plaintext tokens); Consumers: Portal-Server; Risk: High |
| Filesystem-Uploads | Path: /tank/inou/uploads/{dossierID}/{fileID}; Encryption: AES-256-GCM; Types: DICOM, PDF, Images, Genetics-CSV; Max-Size: 10GB; Validation: None |
| Master-Key-File | Path: /tank/inou/master.key; Format: 32-byte AES key; Access: Portal-Server, API-Server on startup; Protection: Filesystem permissions |
| DICOM-Processor | Path: /tank/inou/bin/import-dicom; Args: dossierID, tempDir; Trigger: POST /dossier/{id}/process-imaging; Risk: Command injection potential |
| SMTP-Server | Protocol: SMTP; Usage: Sends 6-digit verification codes; Config: Environment variables; Rate-Limiting: None |
| Gemini-API | Endpoint: https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent; Auth: API key; Usage: Parse medical documents; Data-Sent: Medical-PII |
| Claude-Desktop | OAuth-Client: Anthropic; Redirect-URIs: claude.ai/api/mcp/auth_callback, localhost:6274/oauth/callback; Grant: authorization_code + PKCE; Scope: Read medical data |
6.3 Flows (Connections)
| FROM → TO | Channel | Path/Port | Guards | Touches |
|---|---|---|---|---|
| Internet-Users → Portal-Server | HTTPS | :8443 / | None | Public |
| Internet-Users → Portal-Server | HTTPS | :8443 /start | None | Public |
| Internet-Users → Portal-Server | HTTPS | :8443 /send-code | None | Public |
| Internet-Users → Portal-Server | HTTPS | :8443 /verify | None | Public |
| Internet-Users → Portal-Server | HTTPS | :8443 /dashboard | auth:user | PII |
| Internet-Users → Portal-Server | HTTPS | :8443 /dossier/{id} | auth:user, ownership:read | PII, Medical |
| Internet-Users → Portal-Server | HTTPS | :8443 /dossier/{id}/upload | auth:user, ownership:write | Medical-Files |
| Internet-Users → Portal-Server | HTTPS | :8443 /dossier/{id}/permissions | auth:user, ownership:manage | PII |
| Internet-Users → Portal-Server | HTTPS | :8443 /dossier/{id}/share | auth:user, ownership:manage | PII |
| Internet-Users → Portal-Server | HTTPS | :8443 /oauth/authorize | auth:user | Tokens |
| Internet-Users → Portal-Server | HTTPS | :8443 /oauth/token | client-auth | Tokens |
| Internet-Users → Portal-Server | HTTPS | :8443 /mcp | auth:bearer | PII, Medical |
| Claude-Desktop → Portal-Server | HTTPS | :8443 /oauth/authorize | auth:user | Tokens |
| Claude-Desktop → Portal-Server | HTTPS | :8443 /mcp | auth:bearer | PII, Medical |
| Portal-Server → API-Server | HTTP | :8082 /api/* | proxy-only | PII, Medical, Tokens |
| Portal-Server → DICOM-Viewer | HTTP | :8765 /viewer/* | proxy-only | Medical-Imaging |
| Portal-Server → SQLite-Main | File | inou.db | None | PII, Medical, Tokens, Secrets |
| Portal-Server → SQLite-Auth | File | auth.db | None | OAuth-Tokens |
| Portal-Server → Filesystem-Uploads | File | /uploads/* | None | Medical-Files |
| Portal-Server → Master-Key-File | File | master.key (startup) | None | Secrets |
| Portal-Server → SMTP-Server | SMTP | :25/:587 | None | Public (codes only) |
| API-Server → SQLite-Main | File | inou.db | None | PII, Medical, Tokens |
| API-Server → Filesystem-Uploads | File | /uploads/* | None | Medical-Files |
| API-Server → Master-Key-File | File | master.key (startup) | None | Secrets |
| API-Server → DICOM-Processor | Process | exec /bin/import-dicom | localhost-only | Medical-Imaging |
| API-Server → Gemini-API | HTTPS | generativelanguage.googleapis.com | api-key | Medical-PII |
| DICOM-Viewer → Filesystem-Uploads | File | /uploads/* | None | Medical-Imaging |
| Localhost → API-Server | HTTP | :8082 /api/access | localhost-bypass | PII, Tokens |
| Localhost → API-Server | HTTP | :8082 /api/entries | localhost-bypass | PII, Medical |
6.4 Guards Directory
| Guard Name | Category | Statement |
|---|---|---|
| auth:user | Auth | Requires valid user session via cookie login or Bearer token with dossier ID |
| auth:bearer | Auth | Requires Authorization: Bearer header with encrypted access token or session token |
| client-auth | Auth | Requires OAuth client_id and client_secret (bcrypt validated for confidential clients) |
| ownership:read | ObjectOwnership | Requires PermRead (bit 1) on target dossier - checks access table or self-access |
| ownership:write | ObjectOwnership | Requires PermWrite (bit 2) on target dossier - allows data modification |
| ownership:delete | ObjectOwnership | Requires PermDelete (bit 4) on target dossier - allows entry/file deletion |
| ownership:manage | ObjectOwnership | Requires PermManage (bit 8) on target dossier - allows granting access to others |
| proxy-only | Network | Only accessible via reverse proxy from Portal-Server, not directly from internet |
| localhost-only | Network | Restricted to requests from 127.0.0.1 or ::1 (localhost bypass grants SystemContext) |
| localhost-bypass | Authorization | CRITICAL: Localhost requests bypass all RBAC checks and get full database access |
| api-key | Protocol | Requires API key for Google Gemini LLM service |
| cors:wildcard | Network | WARNING: Access-Control-Allow-Origin: * on MCP and mobile API endpoints |
7. Role & Privilege Architecture
This section maps the application's authorization model. The application uses a capability-based RBAC system with bitmask permissions rather than traditional roles.
7.1 Discovered Roles
Important: This application does NOT use traditional roles. Instead, it uses a permission bitmask system with relationship labels.
| Role Name | Privilege Level | Scope/Domain | Code Implementation |
|---|---|---|---|
| anon | 0 | Global | No authentication - public endpoints only |
| user (authenticated) | 1 | Global | Any authenticated user with valid session/token |
| owner (self-access) | 10 | Per-Dossier | Implicit full permissions when accessorID == dossierID |
| viewer (PermRead) | 3 | Per-Dossier | Permission bit 1 - can read dossier entries |
| editor (PermWrite) | 5 | Per-Dossier | Permission bits 1+2 - can read and write |
| deleter (PermDelete) | 6 | Per-Dossier | Permission bits 1+2+4 - can read, write, delete |
| manager (PermManage) | 8 | Per-Dossier | Permission bits 1+2+8 - can read, write, grant access |
| system (SystemContext) | 99 | Global | Full unrestricted access (localhost bypass) |
Relationship Labels (stored as Relation field, not enforced):
- 0: Custom/unspecified
- 1: Parent
- 4: Guardian
- 5: Caregiver
- 7: Medical professional
- 99: Demo access
Code Locations:
- Permission bits:
/repos/inou-portal/lib/rbac.go:10-14 - Relationship labels:
/repos/inou-portal/portal/main.go:1432-1435 - Self-access check:
/repos/inou-portal/lib/rbac.go:23-25 - CheckAccess function:
/repos/inou-portal/lib/rbac.go:19-54
7.2 Privilege Lattice
Privilege Ordering (→ means "can access resources of"):
System Context (localhost) → [Full Bypass]
Owner (self-access) → [All Permissions on Own Dossier]
Manager (PermManage = 8) → [Can grant permissions to others]
Deleter (PermDelete = 4) → [Can delete entries]
Editor (PermWrite = 2) → [Can modify data]
Viewer (PermRead = 1) → [Can read data]
User (authenticated) → [Can access own dossier only]
Anon → [Public pages only]
Permission Combination Rules:
- PermManage typically includes PermRead + PermWrite (bitmask 11 = 1+2+8)
- PermDelete includes PermRead + PermWrite (bitmask 7 = 1+2+4)
- Permissions are checked via bitwise AND: (grant.Ops & requestedPerm) != 0
Isolation:
- Dossiers are isolated by default
- Access requires explicit grant in `access` table
- No cross-dossier permission inheritance
No hierarchical role system - each dossier maintains independent access control.
7.3 Role Entry Points
| Role | Default Landing Page | Accessible Route Patterns | Authentication Method |
|---|---|---|---|
| anon | / |
/, /start, /pricing, /demo, /privacy-policy, /legal/* |
None |
| user | /dashboard |
/dashboard, /onboard, /invite, /connect, /api, /dossier/add |
Cookie login or Bearer token |
| viewer | /dossier/{id} |
/dossier/{id} (read-only), /dossier/{id}/audit, /dossier/{id}/export |
PermRead check |
| editor | /dossier/{id} |
All viewer routes + /dossier/{id}/edit, /dossier/{id}/upload, /dossier/{id}/trackers/respond |
PermWrite check |
| manager | /dossier/{id}/permissions |
All editor routes + /dossier/{id}/permissions, /dossier/{id}/share, /dossier/{id}/revoke, /dossier/{id}/access/* |
PermManage check |
| owner | /dossier/{id} |
All routes for owned dossier (implicit full permissions) | Self-access (accessorID == dossierID) |
| system | Any | All endpoints (RBAC bypassed) | Localhost (127.0.0.1 or ::1) |
7.4 Role-to-Code Mapping
| Role/Permission | Middleware/Guards | Permission Checks | Storage Location |
|---|---|---|---|
| anon | None | No checks | N/A |
| user | getLoggedInDossier() (portal), getAccessContextOrFail() (API) |
Cookie or token exists | Cookie login or Dossier.SessionToken field |
| viewer (PermRead) | requireDossierAccess(), v1CanAccess() |
CheckAccess(accessor, dossier, dossier, PermRead) |
access table: Ops & 1 != 0 |
| editor (PermWrite) | requireEntryAccess(..., 'w'), v1CanWrite() |
CheckAccess(accessor, dossier, entry, PermWrite) |
access table: Ops & 2 != 0 |
| deleter (PermDelete) | requireEntryAccess(..., 'd') |
CheckAccess(accessor, dossier, entry, PermDelete) |
access table: Ops & 4 != 0 |
| manager (PermManage) | requireManageAccess(), CanManageDossier() |
CheckAccess(accessor, dossier, dossier, PermManage) |
access table: Ops & 8 != 0 |
| owner (self) | None (implicit) | accessorID == dossierID |
No storage (logic-based) |
| system | systemContextForLocalhost(), getAccessContextOrSystem() |
RemoteAddr starts with 127.0.0.1 or [::1] |
No storage (IP-based) |
Code Locations:
- Portal auth:
/repos/inou-portal/portal/main.go:279-285 - API auth middleware:
/repos/inou-portal/api/auth.go:76-131 - V1 API auth:
/repos/inou-portal/api/api_v1.go:16-86 - Core RBAC:
/repos/inou-portal/lib/rbac.go:19-54 - Localhost bypass:
/repos/inou-portal/api/auth.go:151-156
8. Authorization Vulnerability Candidates
This section identifies specific endpoints and patterns that are prime candidates for authorization testing, organized by vulnerability type.
8.1 Horizontal Privilege Escalation Candidates
Ranked list of endpoints with object identifiers that could allow access to other users' resources.
| Priority | Endpoint Pattern | Object ID Parameter | Data Type | Sensitivity | Notes |
|---|---|---|---|---|---|
| HIGH | GET /dossier/{id} |
id (dossierID) |
Medical + PII | Complete health records | View other users' complete health dossiers |
| HIGH | GET /dossier/{id}/export |
id |
Medical + PII | Data export | Download other users' complete data as JSON |
| HIGH | GET /api/v1/dossiers/{id} |
id |
Medical + PII | Dossier metadata | API access to dossier details |
| HIGH | GET /api/v1/dossiers/{id}/entries |
id |
Medical + PII | Medical records | List all medical entries in dossier |
| HIGH | GET /api/v1/dossiers/{id}/entries/{entryId} |
id, entryId |
Medical + PII | Specific record | Access individual medical records |
| HIGH | GET /image/{id} |
id (entryID) |
Medical-Imaging | DICOM images | Access medical imaging files (MRI, CT, X-ray) |
| HIGH | GET /contact-sheet.webp/{id} |
id (studyID) |
Medical-Imaging | Image preview | Contact sheet for imaging studies |
| HIGH | GET /api/v1/images/{id} |
id |
Medical-Imaging | Image file | API access to medical images |
| MEDIUM | GET /dossier/{id}/audit |
id |
Audit logs | Access history | See who has accessed a dossier |
| MEDIUM | GET /api/v1/dossiers/{id}/access |
id |
Access grants | Permission list | View access grants for dossier |
| MEDIUM | GET /dossier/{id}/trackers |
id |
Daily trackers | Vitals, symptoms | Access daily health check-ins |
| MEDIUM | GET /api/v1/dossiers/{id}/trackers |
id |
Tracker data | Health metrics | API access to tracker responses |
| MEDIUM | GET /api/v1/dossiers/{id}/journal |
id |
Journal entries | Health notes | Access journal/diary entries |
| MEDIUM | GET /api/v1/dossiers/{id}/journal/{entryId} |
id, entryId |
Journal entry | Personal notes | Specific journal entry |
| MEDIUM | GET /api/studies |
dossier (query) |
Imaging studies | Medical metadata | List imaging studies for dossier |
| MEDIUM | GET /api/series |
dossier (query) |
Imaging series | Medical metadata | List imaging series |
| MEDIUM | GET /api/slices |
dossier, series (query) |
Image slices | Medical metadata | List individual image slices |
| MEDIUM | GET /api/labs/tests |
dossier (query) |
Lab tests | Medical data | List lab test types |
| MEDIUM | GET /api/labs/results |
dossier (query) |
Lab results | Medical data | Get lab test results |
| LOW | GET /dossier/{id}/files/{fileId}/status |
id, fileId |
Processing status | Metadata | File processing status |
Testing Strategy:
- Create two user accounts (User A, User B)
- User A creates a dossier with medical data
- User B attempts to access User A's dossier using User A's dossier ID
- Check if authorization is properly validated
Key IDOR Vectors:
- Dossier IDs are 16-character hex strings (e.g.,
a1b2c3d4e5f67890) - Entry IDs follow same format
- IDs are predictable and enumerable
- No rate limiting on ID enumeration
8.2 Vertical Privilege Escalation Candidates
List endpoints requiring higher privileges, organized by target permission level.
Escalation to PermWrite (from PermRead)
| Endpoint Pattern | Functionality | Risk Level |
|---|---|---|
POST /dossier/{id}/edit |
Modify dossier profile (name, DOB, sex) | HIGH |
POST /dossier/{id}/upload |
Upload medical files | HIGH |
POST /dossier/{id}/trackers/respond |
Submit tracker responses | MEDIUM |
POST /dossier/{id}/files/{fileId}/update |
Update file metadata | MEDIUM |
POST /api/v1/dossiers/{id}/journal |
Create journal entries | MEDIUM |
PATCH /api/v1/dossiers/{id}/journal/{entryId} |
Update journal entries | MEDIUM |
POST /api/trackers/freeform |
Submit freeform tracker data | MEDIUM |
POST /api/entries |
Create/update medical entries | HIGH |
Escalation to PermDelete (from PermWrite)
| Endpoint Pattern | Functionality | Risk Level |
|---|---|---|
DELETE /dossier/{id}/files/{fileId}/delete |
Delete uploaded files | HIGH |
POST /api/entries with delete: true |
Delete medical entries | HIGH |
POST /api/entries with delete_category: true |
Delete entire category of entries | CRITICAL |
Escalation to PermManage (from PermDelete)
| Endpoint Pattern | Functionality | Risk Level |
|---|---|---|
POST /dossier/{id}/permissions |
Grant access to other users | CRITICAL |
POST /dossier/{id}/share |
Send access invitations | CRITICAL |
POST /dossier/{id}/revoke |
Revoke access from users | HIGH |
POST /dossier/{id}/rbac/{role} |
Edit role-based permissions | CRITICAL |
POST /dossier/{id}/access/{granteeId} |
Edit specific access grants | CRITICAL |
Escalation to Owner (from PermManage)
| Attack Vector | Functionality | Risk Level |
|---|---|---|
| Modify own access grant to include all permissions | Self-escalation via permission manipulation | CRITICAL |
| Grant PermManage to colluding user, have them grant back higher perms | Circular permission escalation | HIGH |
8.3 Context-Based Authorization Candidates
Multi-step workflow endpoints that assume prior steps were completed or check state incorrectly.
| Workflow | Endpoint | Expected Prior State | Bypass Potential | Risk |
|---|---|---|---|---|
| Onboarding | POST /onboard |
Email verification completed | Direct POST with cookie of unverified dossier | MEDIUM |
| File Processing | POST /dossier/{id}/process-imaging |
Files uploaded via /upload | Direct call without upload, manipulate file paths | HIGH |
| OAuth Authorization | POST /oauth/token |
Authorization code issued | Code replay, code injection, PKCE bypass | MEDIUM |
| Access Grant | POST /dossier/{id}/permissions |
User has PermManage | Check if permission check happens before or after grant | HIGH |
| Email Verification | POST /verify |
Code sent via /send-code | Backdoor code 250365 always works (CRITICAL) | CRITICAL |
| File Upload → Processing | 1) Upload, 2) Process | Files in database | Path traversal in relPath parameter between steps |
CRITICAL |
Workflow State Bypass Opportunities:
-
Email Verification Backdoor: Code
250365always validates (testing backdoor left in production)- Location:
/repos/inou-portal/lib/dbcore.go:347,/repos/inou-portal/portal/api_mobile.go:128 - Impact: Complete authentication bypass
- Location:
-
File Processing Path Traversal: Upload stores user-controlled
relPath, later used unsafely in file operations- Location:
/repos/inou-portal/portal/upload.go:222(storage), Line 455 (unsafe use) - Impact: Arbitrary file write on server filesystem
- Location:
-
OAuth State Validation: Check if
stateparameter is properly validated to prevent CSRF- Location:
/repos/inou-portal/portal/oauth.go:62 - Impact: OAuth CSRF attacks
- Location:
-
Permission Modification While Accessing: Check if concurrent permission changes affect ongoing requests
- Impact: Time-of-check-time-of-use (TOCTOU) race conditions
8.4 Additional Authorization Testing Targets
SystemContext Bypass Vulnerabilities:
| Attack Vector | Location | Impact |
|---|---|---|
| Localhost IP spoofing (X-Forwarded-For, X-Real-IP headers) | /repos/inou-portal/api/auth.go:151-156 |
Full system access if headers trusted |
SystemAccessorID reuse (7b3a3ee1c2776dcd) |
/repos/inou-portal/lib/config.go:27, /repos/inou-portal/lib/rbac.go:20-22 |
RBAC bypass if ID can be injected |
| Empty accessorID injection | /repos/inou-portal/lib/rbac.go:20-22 |
RBAC bypass if validation missing |
Cross-Dossier Access:
| Scenario | Endpoints | Test |
|---|---|---|
| Multi-entry operations | POST /api/entries (bulk create) |
Check if all entries validated for same dossier |
| Access grant modification | POST /dossier/{id}/access/{granteeId} |
Modify grants for dossiers not owned |
| Audit log access | GET /api/audit |
Access audit logs for other dossiers |
9. Injection Sources
TASK AGENT FINDING: Comprehensive injection source analysis completed. Only network-accessible endpoints included.
9.1 Path Traversal / File Inclusion
CRITICAL: File Upload Path Traversal
Severity: HIGH
Endpoint: POST /dossier/{id}/upload → POST /dossier/{id}/process-imaging
Network-Accessible: ✅ Yes
Data Flow:
-
Input Vector:
pathform parameter in file upload- File:
/repos/inou-portal/portal/upload.go:182-186 - Sanitization:
filepath.Base()applied, BUT...
- File:
-
Storage: User-controlled path stored in
UploadData.RelPath- File:
/repos/inou-portal/portal/upload.go:222
- File:
-
Dangerous Sink: Path used unsafely in file write operation
- File:
/repos/inou-portal/portal/upload.go:455 - Code:
outPath := filepath.Join(tempDir, relPath) - Vulnerability: No validation that result stays within
tempDir
- File:
Attack Payload:
POST /dossier/1234567890abcdef/upload
Content-Type: multipart/form-data
------WebKitFormBoundary
Content-Disposition: form-data; name="path"
../../../../etc/cron.d/malicious
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="evil.sh"
* * * * * root /tmp/backdoor.sh
------WebKitFormBoundary--
Impact: Arbitrary file write, potential remote code execution
Remediation Location: /repos/inou-portal/portal/upload.go:455 - Add path validation before os.WriteFile()
9.2 Command Injection
MEDIUM: Unvalidated Command Argument
Severity: MEDIUM
Endpoint: POST /dossier/{id}/process-imaging
Network-Accessible: ✅ Yes
Data Flow:
-
Input Vector:
{id}path parameter (extracted from URL)- File:
/repos/inou-portal/portal/upload.go:388 - Code:
targetID := parts[2]
- File:
-
Dangerous Sink: Passed to external command without validation
- File:
/repos/inou-portal/portal/upload.go:472 - Code:
cmd := exec.Command("/tank/inou/bin/import-dicom", targetID, tempDir)
- File:
Mitigation Present: Go's exec.Command() does NOT use shell interpretation, preventing classic command injection (;, |, &&, etc.)
Remaining Risk: If /tank/inou/bin/import-dicom interprets targetID unsafely, exploitation possible
Recommendation: Validate targetID is 16-character hexadecimal: /repos/inou-portal/portal/upload.go:388
9.3 SQL Injection
Status: ✅ NOT VULNERABLE Finding: All database queries use parameterized statements
Safe Pattern Example:
// /repos/inou-portal/lib/dbcore.go:166-202
q := "SELECT ... FROM entries WHERE DossierID = ?"
args := []any{dossierID}
if f.Type != "" {
q += " AND Type = ?"
args = append(args, Pack([]byte(f.Type)))
}
rows, err := db.Query(q, args...)
Safety Mechanisms:
- User input passed via
?placeholders - Dynamic SQL construction limited to table/column names (not user-controlled)
- ORM layer encrypts data before parameterization
Code Locations: All queries in /repos/inou-portal/lib/db_queries.go, /repos/inou-portal/lib/dbcore.go
9.4 Server-Side Template Injection (SSTI)
Status: ✅ NOT VULNERABLE Finding: Templates pre-loaded from disk, user data passed as structured objects
Safe Pattern:
// /repos/inou-portal/portal/main.go:230
templates = template.Must(template.New("").Funcs(funcs).ParseGlob(filepath.Join(tmplDir, "*.tmpl")))
// /repos/inou-portal/portal/upload.go:150
templates.ExecuteTemplate(w, "base.tmpl", data)
Safety Mechanisms:
- Templates loaded at startup (not runtime)
- User data in
PageDatastructs rendered with auto-escaping - No dynamic template parsing of user input
9.5 Unsafe Deserialization
Status: ✅ NOT VULNERABLE
Finding: Go's encoding/json library is safe (no code execution during unmarshal)
Analysis:
// /repos/inou-portal/api/api_entries.go:71-72
var requests []EntryRequest
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&requests); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
Why Safe: Unlike Python pickle or Java serialization, Go's JSON unmarshaling only populates struct fields without executing code
9.6 Other Injection Vectors
LDAP/XML/XPath Injection
Status: ✅ NOT APPLICABLE - No LDAP, XML, or XPath processing found
NoSQL Injection
Status: ✅ NOT APPLICABLE - Uses SQLite with parameterized queries only
Email Header Injection
Status: ⚠️ LOW RISK - Email library likely sanitizes, but not verified
- Email sending:
/repos/inou-portal/portal/main.go:429-436(sendCodeEmail) - User-controlled: Email address and verification code
- Recommendation: Verify SMTP library sanitizes headers
9.7 Summary of Injection Findings
| Type | Status | Severity | Location | Network-Accessible |
|---|---|---|---|---|
| Path Traversal | ❌ VULNERABLE | HIGH | /repos/inou-portal/portal/upload.go:455 |
✅ Yes |
| Command Injection | ⚠️ PARTIAL | MEDIUM | /repos/inou-portal/portal/upload.go:472 |
✅ Yes |
| SQL Injection | ✅ SAFE | N/A | All queries use parameterization | ✅ Yes |
| SSTI | ✅ SAFE | N/A | Templates pre-loaded | ✅ Yes |
| Deserialization | ✅ SAFE | N/A | Go JSON is safe | ✅ Yes |
| Email Header Injection | ⚠️ UNKNOWN | LOW | /repos/inou-portal/portal/main.go:429 |
✅ Yes |
Critical Remediation Needed:
- File upload path traversal validation (HIGH priority)
- Command argument validation (MEDIUM priority)
- Email header sanitization verification (LOW priority)