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
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:
- Magic-link URLs are derived from
request.nextUrl.originwhenAUTH_MAGIC_LINK_BASE_URLis unset, which makes login links vulnerable to host-header poisoning. - Debug magic-link previews can expose valid login URLs if enabled outside local development.
- The public address-autocomplete API is unauthenticated and unthrottled, making it an abuseable outbound proxy.
- Audit IP metadata trusts client-supplied forwarding headers, which makes audit logs spoofable.
Impact:
- If
AUTH_MAGIC_LINK_BASE_URLis not set correctly, the app builds authentication links fromrequest.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:32src/server/auth/auth.service.ts:127
Details:
- The request route passes
authConfig.magicLinkBaseUrl ?? request.nextUrl.originintorequestMagicLink. buildMagicLinkUrl()then embeds the bearer token into a URL created from that base URL.
Recommendation:
- Require
AUTH_MAGIC_LINK_BASE_URLin every non-test environment. - Do not fall back to request-derived origin for auth links.
- Optionally reject requests whose
Hostdoes not match an allowlist.
Impact:
- When debug delivery is enabled,
/api/auth/magic-link/requestreturns 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:33src/server/auth/auth.service.ts:66src/app/api/auth/magic-link/request/route.ts:43src/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.exampleships withAUTH_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.exampletofalse. - Consider compiling the preview response path out of production builds entirely.
Impact:
/api/address-autocompletecan 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:39src/app/api/address-autocomplete/route.ts:81src/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.
Impact:
- Audit events record
x-forwarded-forbeforex-real-ipwith 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.
Evidence:
.env.example:5src/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_SECRETin.env.exampleand 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.
- All CRUD API routes for patients, referrers, prescriptions, and service types resolve the current session and return
401when 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.
- 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.