SPB Git

Repository Public Research preview

io.mjs 1.8 KB 41 lines javascript

1import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";2import { relative, resolve, sep } from "node:path";34/** File access for the audit engine. Every read is confined to one root directory. */5export class AuditError extends Error {6  constructor(code, message) {7    super(message);8    this.code = code;9  }10}1112export const MAX_FILE_BYTES = 5 * 1024 * 1024;1314/** Resolve `p` under `root` and refuse anything that lands outside it, symlinks included. */15export function resolveInside(root, p) {16  if (typeof p !== "string" || p.length === 0) throw new AuditError("path_invalid", "A file path is required.");17  let rootReal;18  try {19    rootReal = realpathSync(resolve(root));20  } catch {21    throw new AuditError("root_invalid", `The allowed root does not exist: ${root}`);22  }23  const target = resolve(rootReal, p);24  if (!existsSync(target)) throw new AuditError("not_found", `File not found: ${p}`);25  const real = realpathSync(target);26  if (real !== rootReal && !real.startsWith(rootReal + sep)) {27    throw new AuditError("path_forbidden", `Path is outside the allowed root (${rootReal}). To read it, change the root: MANUSCRIPT_AUDIT_ROOT for the MCP server, --root for the command line.`);28  }29  const st = statSync(real);30  if (!st.isFile()) throw new AuditError("not_a_file", `Not a regular file: ${p}`);31  if (st.size > MAX_FILE_BYTES) throw new AuditError("file_too_large", `${p} is larger than ${MAX_FILE_BYTES} bytes.`);32  return { real, rel: relative(rootReal, real) || p, rootReal };33}3435export function readInside(root, p) {36  const r = resolveInside(root, p);37  const buf = readFileSync(r.real);38  if (buf.subarray(0, 8192).includes(0)) throw new AuditError("binary_file", `${p} looks like a binary file, not a text manuscript.`);39  return { ...r, text: buf.toString("utf8").replace(/^\uFEFF/, "") };40}41
Files main
Menu

Theme