// File helpers: content hashing, relative-path normalization, reading bytes. const crypto = require("crypto"); const fs = require("fs"); // SHA-256 of the file's contents, returned as lowercase hex. const sha256File = async (absPath) => { return new Promise((resolve, reject) => { const hash = crypto.createHash("sha256"); const stream = fs.createReadStream(absPath); stream.on("data", (chunk) => hash.update(chunk)); stream.on("end", () => resolve(hash.digest("hex"))); stream.on("error", reject); }); }; const sha256Buffer = (buf) => { return crypto.createHash("sha256").update(buf).digest("hex"); }; // Convert Saltcorn's absolute file location to a tenant-relative serve path, // using File.absPathToServePath. The relative path is what we transport // between instances (each has a different file_store root). const toRelativePath = (File, absPath) => { if (!absPath) return ""; return File.absPathToServePath(absPath); }; // Convert a relative serve path back to the absolute path on this instance. // SECURITY: relPath arrives from a (HMAC-authenticated) peer payload, so it is // attacker-influenceable. Resolve it and assert it stays strictly within this // tenant's file_store root -- otherwise a "../../.." (or absolute) path would let // a peer write/read arbitrary files (e.g. /etc/cron.d, app JS) -> RCE. const toAbsolutePath = (File, db, relPath) => { const path = require("path"); const tenant = db.getTenantSchema(); const root = path.resolve(db.connectObj.file_store, tenant); const abs = path.resolve(root, String(relPath || "")); if (abs !== root && !abs.startsWith(root + path.sep)) { throw new Error(`refusing file path outside tenant store: ${relPath}`); } return abs; }; const readFileBytes = async (absPath) => { return await fs.promises.readFile(absPath); }; const writeFileBytes = async (absPath, buf) => { const path = require("path"); await fs.promises.mkdir(path.dirname(absPath), { recursive: true }); await fs.promises.writeFile(absPath, buf); }; module.exports = { sha256File, sha256Buffer, toRelativePath, toAbsolutePath, readFileBytes, writeFileBytes };