// path-confinement.mjs // Delt lesegrense/skrivegrense mot en bundle-rot. Trukket ut av // lib/innboks-write.mjs (D7 steg 17) fordi arkivklar-traverseringen trenger // NOEYAKTIG samme regel paa lesesiden som ingestion har paa skrivesiden. // // EN delt regel, ett sted -- samme disiplin som unquote() i lib/frontmatter.mjs. // To kopier av en sikkerhetsregel maa endres i takt for alltid, og gjoer det ikke. // // Tolags, fordi de to lagene fanger ULIKE angrepsformer: // (1) LEKSIKALSK (resolveUnderBundle) -- '..'-escape og absolutt-override. // path.resolve() lar en absolutt `rel` vinne over rota; containment-sjekken // fanger det. path.sep i prefikssjekken er load-bearing: uten den tillater // basen /uploads ogsaa soesken-katalogen /uploads-other. // (2) SYMLINK (assertRealUnderBundle) -- path.resolve() loeser IKKE symlenker, // saa en symlinket node INNE i bundlen kan peke UT av den. realpathSync paa // den faktiske noden maa ogsaa lande under rotas realpath. // Lag (1) alene er utilstrekkelig, og lag (2) alene kaster paa stier som ikke // finnes ennaa. Begge trengs. import path from 'node:path'; import { realpathSync } from 'node:fs'; // Resolver en bundle-relativ sti og asserter at den blir UNDER bundle-rota. // `hvem` gaar inn i feilmeldingen slik at kalleren er identifiserbar -- den // eneste grunnen funksjonen tar den i det hele tatt. export function resolveUnderBundle(resolvedBundle, rel, hvem = 'path-confinement') { const resolved = path.resolve(resolvedBundle, rel); if (resolved !== resolvedBundle && !resolved.startsWith(resolvedBundle + path.sep)) { throw new Error(`${hvem}: maal-sti utenfor bundle-rot avvist: ${rel}`); } return resolved; } // realpathSync kaster ENOENT paa en sti som ikke finnes. Det er kallerens ansvar // aa kalle denne paa en node som ER opprettet (destinasjons-parent etter mkdir, // eller en fil som faktisk ligger der) -- vi pakker den derfor IKKE inn i en // try/catch her: en ENOENT skal boble som ENOENT, ikke maskeres som et // confinement-brudd. De to feilene krever ulik handling hos kalleren. export function assertRealUnderBundle(realBundle, p, what, hvem = 'path-confinement') { const real = realpathSync(p); if (real !== realBundle && !real.startsWith(realBundle + path.sep)) { throw new Error(`${hvem}: ${what} resolverer utenfor bundle-rot (symlink-escape avvist): ${p}`); } return real; }