feat(voyage): implement path-traversal + symlink/dotfile filter on loaded files
This commit is contained in:
parent
6293775f30
commit
cd6bca978f
3 changed files with 119 additions and 6 deletions
|
|
@ -1393,6 +1393,30 @@ playground first-run shows a complete round-trip-able artifact.
|
|||
// ProjectArtifacts. Mirrors lib/parsers/project-discovery.mjs:15-24
|
||||
// typedef on the browser side; storage-key derived deterministically
|
||||
// from basePath so per-project draft state cannot leak across projects.
|
||||
// v4.3 Step 26 — path-traversal + symlink/dotfile filter.
|
||||
// Pure predicate: returns true if `inside` (the path RELATIVE to the
|
||||
// project root, i.e. webkitRelativePath with the leading basePath
|
||||
// stripped) is safe to read. Rejects:
|
||||
// 1. Path-traversal (`..` anywhere) — covers symlinks pointing
|
||||
// outside the chosen directory: webkitRelativePath returns the
|
||||
// symlink's resolved relative path, so `../etc/passwd` shows up
|
||||
// with literal `..`.
|
||||
// 2. Dotfiles at root or any nested level (`.git/`, `.gitignore`,
|
||||
// `.DS_Store`, `.env`, etc.).
|
||||
// 3. Known unwanted directories at any depth (`node_modules/`,
|
||||
// `dist/`, `build/`).
|
||||
// Pure string-in-bool-out — no DOM, no I/O.
|
||||
function isProjectPathSafe(inside) {
|
||||
if (typeof inside !== 'string' || !inside) return false;
|
||||
if (inside.indexOf('..') !== -1) return false;
|
||||
if (inside.charAt(0) === '.') return false;
|
||||
if (inside.indexOf('/.') !== -1) return false;
|
||||
if (inside.indexOf('node_modules/') === 0 || inside.indexOf('/node_modules/') !== -1) return false;
|
||||
if (inside.indexOf('dist/') === 0 || inside.indexOf('/dist/') !== -1) return false;
|
||||
if (inside.indexOf('build/') === 0 || inside.indexOf('/build/') !== -1) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
async function loadProjectDirectory(files, basePath) {
|
||||
if (!files || !files.length) return null;
|
||||
var prefix = basePath ? basePath + '/' : '';
|
||||
|
|
@ -1410,14 +1434,15 @@ playground first-run shows a complete round-trip-able artifact.
|
|||
|
||||
// Phase 1 — validate + classify (sync).
|
||||
var classified = [];
|
||||
var filteredCount = 0; // v4.3 Step 26 — track suppressed entries
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var f = files[i];
|
||||
var rel = f.webkitRelativePath || '';
|
||||
// Validate: path must start with basePath prefix; reject parent traversal.
|
||||
if (basePath && rel.indexOf(prefix) !== 0) continue;
|
||||
if (rel.indexOf('..') !== -1) continue;
|
||||
// Validate: path must start with basePath prefix.
|
||||
if (basePath && rel.indexOf(prefix) !== 0) { filteredCount++; continue; }
|
||||
var inside = rel.slice(prefix.length);
|
||||
if (!inside) continue;
|
||||
// v4.3 Step 26 — reject path-traversal, dotfiles, node_modules, dist, build.
|
||||
if (!isProjectPathSafe(inside)) { filteredCount++; continue; }
|
||||
var parts = inside.split('/');
|
||||
var name = parts[parts.length - 1];
|
||||
var bucket = null;
|
||||
|
|
@ -1462,6 +1487,13 @@ playground first-run shows a complete round-trip-able artifact.
|
|||
if (!artifacts.brief) {
|
||||
announce('Advarsel: brief.md mangler i prosjektmappen.');
|
||||
}
|
||||
// v4.3 Step 26 — surface filter-rejection count via aria-live so
|
||||
// screen-readers (and curious operators) know what was suppressed.
|
||||
// Treat as informational, not error: dotfile + node_modules omission
|
||||
// is the common case for a clean dev directory.
|
||||
if (filteredCount > 0) {
|
||||
announce(filteredCount + ' fil(er) filtrert (dotfiles / node_modules / path-traversal).');
|
||||
}
|
||||
|
||||
// Phase 5 — render dashboard if available, else log.
|
||||
if (typeof renderDashboard === 'function') {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue