Check input file

This commit is contained in:
Reinier Criel 2025-12-05 15:31:19 -08:00 committed by Sander Declerck
parent 314001eb0c
commit ec22421bd9
No known key found for this signature in database

View file

@ -96,14 +96,18 @@ export function getCombinedCaBundlePath() {
} }
/** /**
* Read user certificate file. * Read and validate user certificate file with comprehensive security checks.
* @param {string} certPath - Path to certificate file * @param {string} certPath - Path to certificate file
* @returns {string | null} Certificate PEM content or null if invalid/unreadable * @returns {string | null} Certificate PEM content or null if invalid/unreadable
*/ */
function readUserCertificateFile(certPath) { function readUserCertificateFile(certPath) {
try { try {
// Validate path is a string and not attempting path traversal if (typeof certPath !== "string" || certPath.trim().length === 0) {
if (typeof certPath !== "string" || certPath.includes("..")) { return null;
}
// Path traversal protection - check for .. and multiple slashes
if (certPath.includes("..") || certPath.includes("//") || certPath.includes("\\\\")) {
return null; return null;
} }
@ -111,15 +115,24 @@ function readUserCertificateFile(certPath) {
return null; return null;
} }
const certPathAbsolute = path.resolve(certPath); const stats = fs.lstatSync(certPath);
// Verify it's an absolute path (cross-platform) if (!stats.isFile() || stats.isSymbolicLink()) {
if (!path.isAbsolute(certPathAbsolute)) {
return null; return null;
} }
const content = fs.readFileSync(certPathAbsolute, "utf8"); const content = fs.readFileSync(certPath, "utf8");
return content && isParsable(content) ? content : null; if (!content || typeof content !== "string") {
return null;
}
// 6) Validate PEM format
if (!isParsable(content)) {
return null;
}
return content;
} catch { } catch {
// Silently fail on any errors (permissions, parsing, etc.)
return null; return null;
} }
} }
@ -134,7 +147,7 @@ function readUserCertificateFile(certPath) {
export function getCombinedCaBundlePathWithUserCerts(userCertPath) { export function getCombinedCaBundlePathWithUserCerts(userCertPath) {
const parts = []; const parts = [];
// 1) Add Safe Chain CA (for MITM'd registries) // 1) Safe Chain CA
const safeChainPath = getCaCertPath(); const safeChainPath = getCaCertPath();
try { try {
const safeChainPem = fs.readFileSync(safeChainPath, "utf8"); const safeChainPem = fs.readFileSync(safeChainPath, "utf8");
@ -143,12 +156,12 @@ export function getCombinedCaBundlePathWithUserCerts(userCertPath) {
// Ignore if Safe Chain CA is not available // Ignore if Safe Chain CA is not available
} }
// 2) Add user's certificates if provided // 2) User's certificates
if (userCertPath) { if (userCertPath) {
const userPem = readUserCertificateFile(userCertPath); const userPem = readUserCertificateFile(userCertPath);
if (userPem) { if (userPem) {
parts.push(userPem.trim()); parts.push(userPem.trim());
ui.writeWarning(`Safe-chain: Merging user's NODE_EXTRA_CA_CERTS from ${userCertPath}`); ui.writeVerbose(`Safe-chain: Merging user's NODE_EXTRA_CA_CERTS from ${userCertPath}`);
} else { } else {
ui.writeWarning(`Safe-chain: Could not read or parse user's NODE_EXTRA_CA_CERTS from ${userCertPath}`); ui.writeWarning(`Safe-chain: Could not read or parse user's NODE_EXTRA_CA_CERTS from ${userCertPath}`);
} }