Fix five P0/P1 security & correctness findings from the full-eval
- Arbitrary file write (P0): validate import keys in /api/library/import via a now-exported safeFilename(); a ../../ key is skipped, not written out of the scope dir. - SSRF (P0): guard downloadPodcastAudio — reject non-HTTP(S) schemes, block IP-literal and DNS-resolved private/link-local/loopback/reserved/multicast and embedded-IPv4 IPv6 targets (closes DNS rebinding), cap + resolve redirects. - ESM require (P1): top-level import of randomBytes in license-purchase.js (the inner require threw on the anon purchase-settle path). - Concurrency lock (P1): skip the process-global free-tier slot in multi-mode so it no longer serializes every cloud tenant onto one job. - X-Forwarded-For bypass (P1): set Express trust proxy from RECAP_TRUSTED_PROXY_HOPS (default 1); getClientIp now reads req.ip instead of a client-spoofable XFF entry. Tests added for safeFilename, the SSRF guard, and getClientIp (119 pass). Registry blockers deferred (ROADMAP); leaked-key history purge queued.
This commit is contained in:
+12
-1
@@ -25,6 +25,7 @@ import {
|
||||
loadMeta,
|
||||
saveMeta,
|
||||
scopeForRequest,
|
||||
safeFilename,
|
||||
ROOT_SIDECARS,
|
||||
} from "./history.js";
|
||||
|
||||
@@ -129,7 +130,17 @@ export function setupLibraryRoutes(app) {
|
||||
|
||||
// Sessions — skip if already present.
|
||||
for (const [id, session] of Object.entries(data.sessions)) {
|
||||
const filePath = path.join(scopeDir, `${id}.json`);
|
||||
// The import file is fully attacker-controlled; validate the key
|
||||
// before using it as a filename. A "../../" id would otherwise
|
||||
// escape the scope dir and write anywhere the process can reach.
|
||||
let safeId;
|
||||
try {
|
||||
safeId = safeFilename(id);
|
||||
} catch {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
const filePath = path.join(scopeDir, `${safeId}.json`);
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
skipped++;
|
||||
|
||||
Reference in New Issue
Block a user