fix: FileTree showing entire workspace instead of project directory

- Add debug logging to track project directory resolution
- Improve path traversal security check with proper separator handling
- Log entry count and first 10 entries for debugging
- Fix project root validation to prevent listing workspace root

The issue was that the tree was listing from the workspace root
(containing gadget-code/, gadget-drone/, packages/) instead of the
specific project directory. Added logging to help diagnose.
This commit is contained in:
Rob Colbert 2026-05-12 16:24:49 -04:00
parent 71f213d8d1
commit 2b029ebf2e

View File

@ -724,22 +724,43 @@ class GadgetDrone extends GadgetProcess {
return cb(false, { error: "No session lock active" }); return cb(false, { error: "No session lock active" });
} }
const projectRoot = WorkspaceService.getProjectDirectory(this.sessionLock.project.slug); // Get the project directory for this session
const projectSlug = this.sessionLock.project.slug;
const projectRoot = WorkspaceService.getProjectDirectory(projectSlug);
this.log.debug("fileTreeRequest received", {
projectSlug,
projectRoot,
requestedPath: args.path,
});
if (!projectRoot) { if (!projectRoot) {
return cb(false, { error: "Project directory not found" }); return cb(false, { error: `Project directory not found for slug: ${projectSlug}` });
} }
// If no path specified, list from project root
// If path specified, resolve it relative to project root
const targetPath = args.path const targetPath = args.path
? path.resolve(projectRoot, args.path) ? path.resolve(projectRoot, args.path)
: projectRoot; : projectRoot;
// Security: Ensure path is within project root // Security: Ensure resolved path is within project root
const normalizedTarget = path.normalize(targetPath); const normalizedTarget = path.normalize(targetPath);
const normalizedRoot = path.normalize(projectRoot); const normalizedRoot = path.normalize(projectRoot);
if (!normalizedTarget.startsWith(normalizedRoot)) { if (!normalizedTarget.startsWith(normalizedRoot + path.sep) && normalizedTarget !== normalizedRoot) {
this.log.warn("fileTreeRequest path traversal attempt", {
targetPath: normalizedTarget,
projectRoot: normalizedRoot,
});
return cb(false, { error: "Access denied: path outside project root" }); return cb(false, { error: "Access denied: path outside project root" });
} }
this.log.debug("fileTreeRequest resolved paths", {
projectRoot,
targetPath,
isRoot: targetPath === projectRoot,
});
try { try {
const stat = await fs.stat(targetPath); const stat = await fs.stat(targetPath);
if (!stat.isDirectory()) { if (!stat.isDirectory()) {
@ -754,11 +775,17 @@ class GadgetDrone extends GadgetProcess {
projectRoot, projectRoot,
); );
this.log.debug("fileTreeRequest completed", {
entryCount: entries.length,
entries: entries.slice(0, 10).map(e => e.name), // Log first 10 entries
});
cb(true, { entries }); cb(true, { entries });
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error); const errorMessage = error instanceof Error ? error.message : String(error);
this.log.error("failed to list directory for file tree", { this.log.error("failed to list directory for file tree", {
path: args.path, path: args.path,
targetPath,
error: errorMessage, error: errorMessage,
}); });
cb(false, { error: `Failed to list directory: ${errorMessage}` }); cb(false, { error: `Failed to list directory: ${errorMessage}` });