Skip to content

Security: ulf007/physio.io

Security

security.md

Security Audit

Date: 2026-03-24

Scope:

  • src/app/api/**
  • src/server/**
  • auth/session/env handling
  • input validation and rendering sinks

Method:

  • reviewed all App Router API routes
  • traced session and permission enforcement through service-layer calls
  • searched for hardcoded secrets and public env exposure patterns
  • searched for raw SQL, dangerouslySetInnerHTML, and similar injection sinks

Executive summary

I did not find evidence of live third-party API keys committed to the repo, and I did not find obvious SQL injection or React XSS sinks in the current codebase.

I did find four meaningful security issues:

  1. Magic-link URLs are derived from request.nextUrl.origin when AUTH_MAGIC_LINK_BASE_URL is unset, which makes login links vulnerable to host-header poisoning.
  2. Debug magic-link previews can expose valid login URLs if enabled outside local development.
  3. The public address-autocomplete API is unauthenticated and unthrottled, making it an abuseable outbound proxy.
  4. Audit IP metadata trusts client-supplied forwarding headers, which makes audit logs spoofable.

Findings

High: Magic-link generation trusts request origin

Impact:

  • If AUTH_MAGIC_LINK_BASE_URL is not set correctly, the app builds authentication links from request.nextUrl.origin.
  • In a proxy or host-header-misconfigured deployment, an attacker can cause emailed magic links to point at an attacker-controlled origin.
  • That can leak the one-time token and turn the login flow into account takeover.

Evidence:

  • src/app/api/auth/magic-link/request/route.ts:32
  • src/server/auth/auth.service.ts:127

Details:

  • The request route passes authConfig.magicLinkBaseUrl ?? request.nextUrl.origin into requestMagicLink.
  • buildMagicLinkUrl() then embeds the bearer token into a URL created from that base URL.

Recommendation:

  • Require AUTH_MAGIC_LINK_BASE_URL in every non-test environment.
  • Do not fall back to request-derived origin for auth links.
  • Optionally reject requests whose Host does not match an allowlist.

Medium: Debug magic-link preview is a latent auth bypass if misconfigured

Impact:

  • When debug delivery is enabled, /api/auth/magic-link/request returns the full login URL in its JSON response.
  • The login form then renders that URL directly as a clickable link.
  • If this flag is ever enabled in a deployed environment, anyone who knows a user email can self-issue a usable login link.

Evidence:

  • src/server/auth/auth.config.ts:33
  • src/server/auth/auth.service.ts:66
  • src/app/api/auth/magic-link/request/route.ts:43
  • src/components/magic-link-request-form.tsx:94
  • .env.example:14

Details:

  • Production defaults this flag to false, which is good.
  • The risk is operational: .env.example ships with AUTH_ALLOW_DEBUG_MAGIC_LINKS=true, and the code path is fully wired end-to-end.

Recommendation:

  • Fail startup in production if AUTH_ALLOW_DEBUG_MAGIC_LINKS=true.
  • Change .env.example to false.
  • Consider compiling the preview response path out of production builds entirely.

Medium: Public address-autocomplete route is unauthenticated and unthrottled

Impact:

  • /api/address-autocomplete can be called by anyone.
  • Each request fans out to one or two third-party geocoding providers, sometimes across multiple query variants.
  • This creates an easy abuse path for traffic amplification, provider quota exhaustion, or provider blocking.

Evidence:

  • src/app/api/address-autocomplete/route.ts:39
  • src/app/api/address-autocomplete/route.ts:81
  • src/app/api/address-autocomplete/route.ts:145

Details:

  • There is no session check, no API key, no CSRF gate, and no rate limiting.
  • This is not an SSRF issue because the upstream hosts are fixed, but it is still an exposed proxy-style endpoint.

Recommendation:

  • Require an authenticated session if this feature is only intended for staff workflows.
  • Add per-IP and per-session rate limits.
  • Add caching and circuit breaking for upstream failures.

Medium: Audit IP metadata is spoofable

Impact:

  • Audit events record x-forwarded-for before x-real-ip with no trusted-proxy validation.
  • A direct client can forge these headers and poison audit IP data.
  • In a regulated system, that weakens the evidentiary value of the audit trail.

Evidence:

  • src/server/audit/request-metadata.ts:18

Recommendation:

  • Only trust forwarding headers when injected by a known proxy or platform.
  • Prefer platform-provided request IP metadata where available.
  • If trust cannot be established, omit client IP rather than storing spoofable values.

Additional observations

Low: Example and development secrets are concrete values

Evidence:

  • .env.example:5
  • src/server/auth/auth.config.ts:3

Details:

  • I did not find live provider keys committed to the repo.
  • I did find a concrete sample AUTH_SECRET in .env.example and a hardcoded development fallback secret in code.
  • Those are not production secrets by themselves, but they increase the chance of accidental secret reuse.

Recommendation:

  • Replace the example value with a placeholder like CHANGE_ME.
  • Keep the development fallback limited to local/test only, or remove it and require explicit local config.

Positive controls observed

  • All CRUD API routes for patients, referrers, prescriptions, and service types resolve the current session and return 401 when unauthenticated.
  • Those routes delegate authorization to service-layer permission checks rather than relying on frontend visibility.
  • The main write/read inputs are validated with zod.
  • I did not find dangerouslySetInnerHTML, raw SQL execution, or obvious unsanitized HTML rendering paths in the audited code.

Notable gaps I did not verify dynamically

  • I did not run live penetration tests against a deployed instance.
  • I did not audit infrastructure, reverse-proxy config, secret storage, or CI/CD settings.
  • I did not verify whether external middleware or hosting already adds rate limiting or host allowlisting.

There aren't any published security advisories