Skip to content

Harden some security issues based on Pentest scan - Report attached.#228

Closed
rafaelfoster wants to merge 2 commits into
ulsklyc:mainfrom
rafaelfoster:security-review-001
Closed

Harden some security issues based on Pentest scan - Report attached.#228
rafaelfoster wants to merge 2 commits into
ulsklyc:mainfrom
rafaelfoster:security-review-001

Conversation

@rafaelfoster

Copy link
Copy Markdown
Contributor

Summary

  • Restrict OpenAPI JSON endpoints to authenticated admin users.
  • Disable /docs in production unless explicitly enabled, and require admin auth otherwise.
  • Remove anonymous package version disclosure from /api/v1/version.
  • Hide completed setup endpoint in production.
  • Remove deployment-host and database implementation details from the generated OpenAPI spec.
  • Add Docker-based production client build that minifies shipped JavaScript assets.

Testing

  • docker compose build oikos
  • docker run --rm --entrypoint node ghcr.io/ulsklyc/oikos:latest test/test-setup.js

Working tree is clean on security-review-001.


Security validation report:

Penetration Test Report: OIKOS. pentest (2026-06-03) - Jun 3, 2026, 3:28 PM-

Asset: OIKOS.
Date: 6/3/2026
Total Findings: 5

Executive Summary

A penetration test was conducted against the OIKOS. web application, resulting in an overall medium security posture with 5 total findings: 0 critical, 0 high, 1 medium, 1 low, and 3 informational. The assessment revealed that the application exposes significant internal technical details — including its complete operational blueprint and internal structure — to any unauthenticated visitor on the internet. An attacker could leverage this freely available information to map the entire application's functionality, identify potential weaknesses in authentication and data-handling workflows, and craft highly targeted attacks against user accounts and family data without requiring any prior access or credentials.

Security Posture: MEDIUM

Key Concerns:

  • Data Exposure — The application publicly discloses its complete internal architecture, operational schema, and source logic to any unauthenticated user, giving potential attackers a detailed roadmap to identify and exploit weaknesses across the entire platform.
  • Configuration & Deployment — Several administrative and development-oriented interfaces remain accessible in the production environment, expanding the attack surface unnecessarily and signaling that hardening best practices have not been fully applied before deployment.
  • Authentication & Access Control — Sensitive technical resources that should be restricted to authorized personnel are available without any authentication, meaning an attacker can perform extensive reconnaissance with zero barriers to entry.

Immediate Actions: While no critical or high-severity vulnerabilities were identified in this free-tier assessment, the medium-severity finding warrants prompt attention as it provides attackers with a comprehensive understanding of the application's inner workings that could facilitate more damaging attacks. We recommend engaging for the full penetration test report to uncover deeper vulnerabilities that typically accompany this level of information exposure. Detailed findings, reproduction steps, and remediation guidance are available in the full pentest report.

Findings Summary

Severity Count
Critical 0
High 0
Medium 1
Low 1
Info 3

Detailed Findings

1. Info Disclosure via /openapi.json - Full API Schema Exposed

Severity: MEDIUM
Status: Open
Occurrences: 2

Description

The full OpenAPI/Swagger specification is exposed at /openapi.json without authentication. This 99KB JSON file reveals the complete API surface of the Oikos family planner application, including:

  1. All 100+ API endpoints with their HTTP methods, request/response schemas, and descriptions
  2. Security scheme details (bearer tokens, API keys via X-API-Key header, cookie-based sessions via oikos.sid)
  3. Internal schema definitions revealing the database structure (User, Task, Calendar, Budget, Document schemas with all field names and types)
  4. Admin-only endpoints including /api/v1/backup/database (SQLite backup download), /api/v1/auth/setup (initial admin creation), /api/v1/auth/users (user management)
  5. The database technology (SQLite) is disclosed via the backup endpoint description
  6. Hidden endpoints not referenced in the frontend JS: /api/v1/search, /api/v1/split-expenses/*, /api/v1/contacts/{id}/vcard, /api/v1/budget/export
  7. Server URL confirming the deployment domain

This information provides an attacker with a complete roadmap for targeted attacks against the API.

Proof of Concept

Step 1: Access the OpenAPI specification without any authentication

curl -s https://OIKOS./openapi.json --max-time 10 | jq '{info: .info, servers: .servers, paths_count: (.paths | keys | length), security_schemes: .components.securitySchemes}'

Expected output: {"info":{"title":"Oikos API","version":"0.55.13","description":"OpenAPI documentation for the Oikos family organizer backend."},"servers":[{"url":"https://OIKOS.","description":"Current server"}],"paths_count":107,"security_schemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API token sent in the Authorization header as Bearer <token>."},"apiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API token sent in the X-API-Key header."},"cookieAuth":{"type":"apiKey","in":"cookie","name":"oikos.sid","description":"Browser session cookie. State-changing requests also require X-CSRF-Token."}}}

Step 2: Extract the backup database endpoint details showing SQLite database download capability and admin endpoints

curl -s https://OIKOS./openapi.json --max-time 10 | jq '{backup_download: .paths["/api/v1/backup/database"].get.summary, setup_endpoint: .paths["/api/v1/auth/setup"].post.summary, user_schema: .components.schemas.User.properties | keys}'

Expected output: {"backup_download":"Download database backup","setup_endpoint":"Initial setup: create first admin","user_schema":["avatar_color","avatar_data","birth_date","display_name","email","family_role","id","phone","role","username"]}

Solution

  • Restrict access to /openapi.json to authenticated admin users only by adding authentication middleware to the route
  • If API documentation is needed for development, serve it only in non-production environments or behind a VPN/IP allowlist
  • Remove internal implementation details from the OpenAPI spec (e.g., database type from backup endpoint descriptions)
  • Consider stripping sensitive schema definitions from the public spec

2. Info Disclosure via Exposed Swagger UI and API Documentation at /docs

Severity: LOW
Status: Open
Occurrences: 2

Description

The application serves an interactive Swagger UI documentation page at /docs (which returns a 301 redirect to /docs/) and exposes the full OpenAPI specification at both /openapi.json and /api/v1/openapi.json without any authentication. While /docs/ is intercepted by the SPA catch-all router and doesn't render the Swagger UI properly, the underlying Express route exists and the redirect response confirms the Swagger documentation framework is installed. Combined with the unauthenticated /openapi.json endpoint (reported separately), an attacker gains complete knowledge of the API surface area, including admin-only endpoints, database backup download paths, authentication schemes (Bearer, API key, session cookie), and all request/response schemas. The /docs 301 redirect itself confirms Express with swagger-ui-express middleware is running, adding to technology fingerprinting.

Proof of Concept

Step 1: Access /docs to confirm the Swagger UI route exists - it returns a 301 redirect to /docs/

curl -sI https://OIKOS./docs --max-time 10 | head -10

Expected output: HTTP/2 301
date: Wed, 03 Jun 2026 15:20:06 GMT
content-type: text/html; charset=UTF-8
content-security-policy: default-src 'none'
cross-origin-opener-policy: same-origin
cross-origin-resource-policy: same-origin
location: /docs/

Step 2: Confirm the OpenAPI specification is also available at /api/v1/openapi.json

curl -s https://OIKOS./api/v1/openapi.json --max-time 10 | jq '.info'

Expected output: {"title":"Oikos API","version":"0.55.13","description":"OpenAPI documentation for the Oikos family organizer backend."}

Solution

  • Remove or disable the Swagger UI middleware (swagger-ui-express) in production environments
  • If API documentation must remain accessible, protect it with authentication middleware that requires admin privileges
  • Disable the /docs route and /openapi.json endpoint in production via environment-based configuration (e.g., only enable when NODE_ENV=development)

3. Info Disclosure via Complete Source Code Exposure in Unminified JavaScript

Severity: INFO
Status: Open
Occurrences: 2

Description

The Oikos application serves all client-side JavaScript as unminified, fully commented ES modules without bundling or obfuscation. Over 500KB of source code is directly accessible at well-known paths (/api.js, /router.js, /pages/settings.js, /pages/dashboard.js, etc.) with full German/English code comments explaining the architecture, security mechanisms, and implementation details. Key exposures include:

  1. Full CSRF token handling mechanism in api.js - reveals that CSRF tokens are stored in-memory and refreshed via X-CSRF-Token headers, with a specific retry mechanism for 403 responses
  2. Complete authentication flow - session-based auth via oikos.sid cookie, API token auth via Bearer/X-API-Key headers
  3. All 160+ API endpoint paths with their HTTP methods, request body schemas, and ID parameter names
  4. Client-side route guard logic in router.js showing which routes require auth and which don't
  5. User role system (admin vs member) and all admin-gated features (user management, backup, API tokens, CalDAV/CardDAV/Google/Apple integrations)
  6. Database field names and relationships visible through API calls (user IDs are integers, sequential)
  7. Third-party module system that can load external modules (potential for supply chain attacks if compromised)

While frontend code is inherently client-accessible, the unminified commented source code significantly reduces the effort for an attacker to map the entire application architecture and identify targeted attack vectors.

Proof of Concept

Step 1: Download the api.js file showing the complete API client implementation with CSRF handling

curl -s https://OIKOS./api.js --max-time 10 | head -30

Expected output: /**

  • Modul: API-Client
  • Zweck: Fetch-Wrapper mit Session-Auth, einheitlicher Fehlerbehandlung und JSON-Parsing
  • Abhängigkeiten: keine
    */

const API_BASE = '/api/v1';

/** In-Memory CSRF-Token (zuverlaessiger als document.cookie auf iOS Safari/PWA). */
let _csrfToken = '';

/** Liest den CSRF-Token: bevorzugt In-Memory, Fallback auf Cookie. */
function getCsrfToken() {
if (_csrfToken) return _csrfToken;
return document.cookie.split(';')
.map((c) => c.trim())
.find((c) => c.startsWith('csrf-token='))
?.slice('csrf-token='.length) ?? '';
}

Step 2: Download the settings page JS showing admin-only functionality like user management, backup, and API token creation

curl -s https://OIKOS./pages/settings.js --max-time 10 | grep -c 'role.*admin'

Expected output: 15

Solution

  • Minify and bundle JavaScript files for production using tools like esbuild, Vite, or webpack to strip comments, variable names, and reduce attack surface visibility
  • Remove development comments that explain security mechanisms and internal architecture
  • Consider implementing source map files served only in development environments rather than exposing raw source
  • Use code-splitting to reduce the amount of code loaded upfront and make automated analysis harder

4. Info Disclosure via /api/v1/auth/setup - Setup Endpoint Active in Production

Severity: INFO
Status: Open
Occurrences: 2

Description

The /api/v1/auth/setup endpoint, designed for initial admin user creation during application setup, remains active and accessible in production. While it correctly returns HTTP 403 with "Setup has already been completed" (indicating the initial setup was done), the endpoint's existence in production reveals several things:

  1. The endpoint confirms the application uses a single-admin setup pattern - an attacker now knows the admin creation flow and can target fresh deployments or database resets.
  2. The OpenAPI spec reveals the SetupRequest schema requires only username, display_name, and password fields - no email verification, CAPTCHA, or additional security.
  3. If the database is ever reset or corrupted, this endpoint would allow anyone to create the first admin account without any authentication, potentially leading to complete application takeover.

The endpoint should be disabled or removed in production after initial setup is complete.

Proof of Concept

Step 1: Attempt to use the setup endpoint to create a new admin user

curl -s -X POST https://OIKOS./api/v1/auth/setup -H 'Content-Type: application/json' -d '{"username":"pentester","display_name":"Pentester","password":"PenTest123!"}' --max-time 10

Expected output: {"error":"Setup has already been completed.","code":403}

Step 2: Confirm the setup endpoint schema is exposed in the OpenAPI specification, revealing the exact fields needed for admin creation

curl -s https://OIKOS./openapi.json --max-time 10 | jq '.paths["/api/v1/auth/setup"].post | {summary, requestBody: .requestBody.content["application/json"].schema}'

Expected output: {"summary":"Initial setup: create first admin","requestBody":{"$ref":"#/components/schemas/SetupRequest"}}

Solution

  • Disable or remove the /api/v1/auth/setup endpoint after the initial setup is complete, either by environment variable or by checking a persistent flag
  • If the endpoint must remain for disaster recovery, protect it with an additional secret/token or restrict to localhost access only
  • Add rate limiting and CAPTCHA to the setup endpoint to prevent automated abuse on fresh deployments

5. Info Disclosure via /api/v1/version - Application Version Exposed

Severity: INFO
Status: Open
Occurrences: 2

Description

The /api/v1/version endpoint is accessible without authentication and reveals the application name and exact version number (Oikos FamilyPlanner v0.55.13). This information can be used by attackers to identify known vulnerabilities specific to this version, or to fingerprint the application for targeted attacks.

Proof of Concept

Step 1: Access the version endpoint without authentication to retrieve the application name and version

curl -s https://OIKOS./api/v1/version --max-time 10

Expected output: {"version":"0.55.13","app_name":"FamilyPlanner"}

Step 2: Confirm the endpoint does not require any authentication cookies or tokens

curl -sI https://OIKOS./api/v1/version --max-time 10 | grep -E 'HTTP|content-type'

Expected output: HTTP/2 200
content-type: application/json; charset=utf-8

Solution

  • Require authentication for the /api/v1/version endpoint, or remove the version information from the response
  • If version display is needed for the login page branding, use a separate, minimal endpoint that only returns the app name without the version number

ulsklyc pushed a commit that referenced this pull request Jun 3, 2026
Harden information-disclosure surface flagged by a pentest scan, without
adopting a frontend build step (the esbuild/minification part of #228 was
dropped to keep the no-bundler / zero-build constraint intact):

- /openapi.json + /api/v1/openapi.json now require an admin session/token.
- /docs is admin-only and returns 404 in production unless ENABLE_API_DOCS=true.
- GET /api/v1/version returns the exact version only to authenticated callers;
  unauthenticated login/setup pages still get app_name + setup_required.
- POST /api/v1/auth/setup responds 404 (not 403) in production once set up.
- Strip deployment host and SQLite implementation details from the OpenAPI spec.

Docs: document ENABLE_API_DOCS (.env.example, installation.md) and the new
auth model for version/openapi in SPEC.md.

Co-Authored-By: Rafael Foster <rafaelfoster@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ulsklyc

ulsklyc commented Jun 3, 2026

Copy link
Copy Markdown
Owner

Thanks for the thorough pentest report and the hardening work, @rafaelfoster! 🙏

The security hardening has been merged and shipped in v0.60.3:

  • /openapi.json, /api/v1/openapi.json and /docs are now admin-only (and /docs returns 404 in production unless ENABLE_API_DOCS=true)
  • GET /api/v1/version returns the version only to authenticated callers
  • POST /api/v1/auth/setup returns 404 in production once setup is complete
  • deployment host + SQLite details stripped from the OpenAPI spec

I did not take the esbuild/JS-minification part (Dockerfile build step, scripts/build-client.js, dist/ serving, README/landing-page rewrite). Oikos has an explicit hard constraint of no bundlers / zero build step, and the underlying finding (#3) is only INFO severity (client code is inherently public). Keeping that constraint intact was the deliberate call.

Because the merged subset landed via a separate commit (fd7a6d2) rather than this branch, I'm closing this PR — but it's fully credited via Co-Authored-By. Thanks again!

@ulsklyc ulsklyc closed this Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants