Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions security/pentest-2026-06-20.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Pentest mini-report — versila22/lima-app — 2026-06-20

**Probed URL:** https://limaimpro.duckdns.org/
**Stack:** React 18 + Vite 7 SPA / npm / PWA=yes (vite-plugin-pwa, Workbox NetworkFirst for API)
**Counts:** Critical=1 High=4 Medium=1 Low=3 Info=0

> Note: deployed URL returned 403 from audit host (network policy). DAST headers verified from nginx.conf source. TLS checked via openssl (TLS 1.0/1.1 disabled, TLS 1.2+1.3 enabled). Exposed-path checks skipped.

## Findings

| Sev | Cat | Title | Location |
|---|---|---|---|
| Critical | Dep | vitest: arbitrary file read+exec via UI server | package.json → vitest (dev) |
| High | Dep | serialize-javascript: RCE via RegExp.flags | package.json → workbox-build → serialize-javascript (dev) |
| High | Dep | fast-uri: path traversal via %2E segments | package.json → vite → fast-uri (dev) |
| High | Dep | undici: TLS cert bypass via SOCKS5 requestTls drop | package.json → vite → undici (dev) |
| High | Dep | vite: NTLMv2 hash disclosure via UNC path (Windows) | package.json → vite (dev) |
| Medium | Dep | react-router-dom: open redirect via `//`-prefixed path | package.json → react-router-dom ^6.30.1 (runtime) |
| Low | DAST | CSP style-src unsafe-inline | nginx.conf:10 |
| Low | SAST | api.ts.orig committed — historical localStorage token pattern | src/lib/api.ts.orig |
| Low | SAST | api_upload.patch committed — stale patch artifact | src/lib/api_upload.patch |

## Top 3 fixes
1. **react-router-dom open redirect** — `npm update react-router-dom` to a patched release (>=6.x fixing GHSA-jkqr-q8mg-9736)
2. **Dev dependency chain (Critical/High)** — `npm update vitest vite workbox-build` to resolve CVE chain; add `"vitest": ">=X.Y.Z"` overrides in package.json
3. **Stale artifacts in repo** — `git rm src/lib/api.ts.orig src/lib/api_upload.patch` to prevent confusion and accidental regression to insecure localStorage approach

## Evidence (Critical/High only)

**Critical — vitest arbitrary file read/exec (dev)**
- Location: `package.json` devDependencies → `vitest ^3.2.4` (installed <3.x patched ver)
- Snippet: `"vitest": "^3.2.4"` — GHSA-9crc-q9x8-hgqq
- Impact: Attacker with access to the Vitest UI dev server can read arbitrary files and execute code on the dev machine
- Fix: Update vitest; never expose port 51204 or `--ui` flag outside localhost

**High — serialize-javascript RCE (dev/build)**
- Location: `package.json` → `workbox-build` → `@rollup/plugin-terser` → `serialize-javascript`
- Snippet: `"workbox-build"` in devDeps — GHSA-h4j5-g7hw-2h49
- Impact: Malicious regex in build input can achieve RCE during `vite build`
- Fix: `npm update workbox-build` or pin `"serialize-javascript": ">=6.0.2"` in overrides (already present — verify lockfile resolves it)

**High — fast-uri path traversal (dev)**
- Location: `package.json` → `vite` → `fast-uri` — GHSA-vc7q-jjfg-4q4f
- Impact: Vite dev server file-serve bypass via %2E-encoded dotdot segments
- Fix: Update vite to version bundling patched fast-uri; pin `"fast-uri": ">=3.1.0"` in overrides (already present)

**High — undici TLS bypass (dev)**
- Location: `package.json` → `vite/undici` — GHSA-3xgq-xp9f-89fc
- Impact: Vite dev server's fetch/proxy drops TLS options for SOCKS5 proxy connections, bypassing certificate validation
- Fix: Update undici / vite

**High — vite NTLMv2 hash disclosure (dev, Windows)**
- Location: `package.json` → `vite` via `launch-editor` — GHSA-8wh2-6qhj-h7j9
- Impact: Visiting a crafted URL during vite `--open` triggers a UNC path, disclosing NTLMv2 hashes to a remote SMB host
- Fix: Update vite >=6.x with patched launch-editor

## Verified safe
- No hardcoded secrets or API keys in source (secret grep clean)
- JWT token: migrated from localStorage to sessionStorage (httpOnly-cookie primary, Safari fallback) — XSS impact limited to tab session
- PWA: no VAPID_PRIVATE in client; runtime API caching uses NetworkFirst (not stale-while-revalidate on auth routes); SW scope /
- Backend CORS: explicit allow_origins list; `allow_credentials=True` only with named origins, never wildcard
- Backend security headers middleware: HSTS, nosniff, X-Frame-Options: DENY, CSP (API-only), COOP, CORP all set
- Nginx CSP: script-src 'self' (no unsafe-eval, no unsafe-inline for scripts)
- Nginx: HSTS max-age=31536000 + includeSubDomains, X-Frame-Options: DENY, nosniff set
- No postMessage usage
- No prototype pollution surface (no lodash.merge/spread on user input)
- dangerouslySetInnerHTML in chart.tsx: CSS variables built from developer-controlled ChartConfig, not user input
- TLS: 1.0/1.1 disabled; 1.2+1.3 enabled
- JWT default secret blocked in non-dev mode via pydantic model_validator

## Needs server-side verification
- Deployed response headers (curl returned 403 from audit host) — confirm CSP, HSTS, nosniff present on live limaimpro.duckdns.org
- Cookie flags (Secure, HttpOnly, SameSite) on auth cookies from Railway backend
- CORS reflection test on live API (api-production-e15b.up.railway.app)
- Exposed paths check: .env, .git/config, backup.zip on deployed URL

## Tools
ran=npm-audit, grep-secret-scan, openssl-tls; skipped=curl-dast (network policy 403), nmap
Loading