Skip to content

Commit 3508a11

Browse files
Potential fix for code scanning alert no. 13: Uncontrolled data used in path expression
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1 parent 9f879bd commit 3508a11

File tree

1 file changed

+49
-25
lines changed
  • remote-instance/src/servers/api-server/platform-api

1 file changed

+49
-25
lines changed

remote-instance/src/servers/api-server/platform-api/handler.ts

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,41 @@ import fs from "fs";
22
import ignore from "ignore";
33
import path from "path";
44

5-
const settingsPath = "~/.pulse-editor/settings.json";
6-
5+
// Define a safe root directory for projects. Can be overridden by env or configured as needed.
6+
const PROJECTS_ROOT = process.env.PROJECTS_ROOT ?? "/srv/projects";
7+
8+
// Utility to resolve and validate user-supplied uri inside PROJECTS_ROOT
9+
function getSafePath(uri: string): string {
10+
// Prevent empty/undefined input
11+
if (!uri || typeof uri !== 'string') {
12+
throw new Error("Invalid project path");
13+
}
14+
// Resolve against the root directory
15+
const resolved = path.resolve(PROJECTS_ROOT, uri);
16+
// Use fs.realpathSync to follow symlinks
17+
let normalized;
18+
try {
19+
normalized = fs.realpathSync(resolved);
20+
} catch {
21+
// If path does not exist yet (e.g., on creation), just use resolved.
22+
normalized = resolved;
23+
}
24+
// Ensure the normalized path is inside the root
25+
if (!normalized.startsWith(PROJECTS_ROOT)) {
26+
throw new Error("Access to paths outside projects root denied");
27+
}
28+
return normalized;
29+
}
730
// List all folders in a path
831
async function handleListProjects(uri: string) {
9-
const files = await fs.promises.readdir(uri, { withFileTypes: true });
32+
const rootPath = getSafePath(uri);
33+
const files = await fs.promises.readdir(rootPath, { withFileTypes: true });
1034
const folders = files
1135
.filter((file) => file.isDirectory())
1236
.map((file) => file.name)
1337
.map((projectName) => ({
1438
name: projectName,
15-
ctime: fs.statSync(path.join(uri, projectName)).ctime,
39+
ctime: fs.statSync(path.join(rootPath, projectName)).ctime,
1640
}));
1741

1842
return folders;
@@ -23,7 +47,8 @@ async function listPathContent(
2347
options: any,
2448
baseUri: string | undefined = undefined
2549
) {
26-
const files = await fs.promises.readdir(uri, { withFileTypes: true });
50+
const rootPath = getSafePath(uri);
51+
const files = await fs.promises.readdir(rootPath, { withFileTypes: true });
2752

2853
const promise: Promise<any>[] = files
2954
// Filter by file type
@@ -50,7 +75,7 @@ async function listPathContent(
5075
})
5176
.map(async (file) => {
5277
const name = file.name;
53-
const absoluteUri = path.join(uri, name);
78+
const absoluteUri = path.join(rootPath, name);
5479
if (file.isDirectory()) {
5580
return {
5681
name: name,
@@ -78,55 +103,54 @@ async function handleListPathContent(uri: string, options: any) {
78103
}
79104

80105
async function handleCreateProject(uri: string) {
81-
// Create a folder at the uri
82-
await fs.promises.mkdir(uri);
106+
// Create a folder at the validated path
107+
await fs.promises.mkdir(getSafePath(uri));
83108
}
84109

85110
async function handleCreateFolder(uri: string) {
86-
// Create a folder at the uri
87-
await fs.promises.mkdir(uri);
111+
// Create a folder at the validated path
112+
await fs.promises.mkdir(getSafePath(uri));
88113
}
89114

90115
async function handleCreateFile(uri: string) {
91-
// Create a file at the uri
92-
await fs.promises.writeFile(uri, "");
116+
// Create a file at the validated path
117+
await fs.promises.writeFile(getSafePath(uri), "");
93118
}
94119

95120
async function handleRename(oldUri: string, newUri: string) {
96-
await fs.promises.rename(oldUri, newUri);
121+
await fs.promises.rename(getSafePath(oldUri), getSafePath(newUri));
97122
}
98123

99124
async function handleDelete(uri: string) {
100-
await fs.promises.rm(uri, { recursive: true, force: true });
125+
await fs.promises.rm(getSafePath(uri), { recursive: true, force: true });
101126
}
102127

103128
async function handleHasPath(uri: string) {
104-
return fs.existsSync(uri);
129+
return fs.existsSync(getSafePath(uri));
105130
}
106131

107132
async function handleReadFile(uri: string) {
108-
// Read the file at path
109-
const data = await fs.promises.readFile(uri, "utf-8");
133+
// Read the file at validated path
134+
const data = await fs.promises.readFile(getSafePath(uri), "utf-8");
110135

111136
return data;
112137
}
113138

114139
async function handleWriteFile(data: any, uri: string) {
115-
// Write the data at path
116-
// await fs.promises.writeFile(path, data);
117-
// create path if it doesn't exist
118-
const dir = uri.split("/").slice(0, -1).join("/");
119-
140+
// Write the data at validated path
141+
const safePath = getSafePath(uri);
142+
// create parent directory if it doesn't exist
143+
const dir = path.dirname(safePath);
120144
if (!fs.existsSync(dir)) {
121145
fs.mkdirSync(dir, { recursive: true });
122146
}
123147

124-
fs.writeFileSync(uri, data);
148+
fs.writeFileSync(safePath, data);
125149
}
126150

127151
async function handleCopyFiles(from: string, to: string) {
128-
// Copy the files from the from path to the to path
129-
await fs.promises.cp(from, to, { recursive: true });
152+
// Copy the files from the validated from path to the validated to path
153+
await fs.promises.cp(getSafePath(from), getSafePath(to), { recursive: true });
130154
}
131155

132156
async function handleLoadSettings() {

0 commit comments

Comments
 (0)