diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml index 43dc14a..8613063 100644 --- a/.github/workflows/build-mobile.yml +++ b/.github/workflows/build-mobile.yml @@ -1,7 +1,7 @@ name: Build Mobile env: - ANDROID_SDK_VERSION: "35.0.0" + ANDROID_SDK_VERSION: "36.1.0" on: pull_request: diff --git a/.github/workflows/release-mobile.yml b/.github/workflows/release-mobile.yml index 7590004..20d2f66 100644 --- a/.github/workflows/release-mobile.yml +++ b/.github/workflows/release-mobile.yml @@ -1,7 +1,7 @@ name: Build Capacitor Release env: - ANDROID_SDK_VERSION: "35.0.0" + ANDROID_SDK_VERSION: "36.1.0" on: push: diff --git a/mobile/package.json b/mobile/package.json index 48b95d9..24fd576 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -9,16 +9,16 @@ }, "dependencies": { "@capacitor-community/safe-area": "^7.0.0-alpha.1", - "@capacitor/android": "^7.2.0", + "@capacitor/android": "^7.4.4", "@capacitor/app": "^7.1.0", "@capacitor/browser": "^7.0.2", - "@capacitor/cli": "^7.2.0", - "@capacitor/core": "^7.2.0", - "@capacitor/filesystem": "^7.0.1", - "@capacitor/keyboard": "^7.0.1", + "@capacitor/cli": "^7.4.4", + "@capacitor/core": "^7.4.4", + "@capacitor/filesystem": "^7.1.4", + "@capacitor/keyboard": "^7.0.3", "@capacitor/preferences": "^7.0.2", - "@capacitor/screen-orientation": "^7.0.1", - "@capacitor/status-bar": "^7.0.1", - "@capawesome/capacitor-file-picker": "^7.0.1" + "@capacitor/screen-orientation": "^7.0.2", + "@capacitor/status-bar": "^7.0.3", + "@capawesome/capacitor-file-picker": "^7.2.0" } } diff --git a/package-lock.json b/package-lock.json index 90bb5aa..2ade716 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,17 +51,17 @@ "version": "0.0.1", "dependencies": { "@capacitor-community/safe-area": "^7.0.0-alpha.1", - "@capacitor/android": "^7.2.0", + "@capacitor/android": "^7.4.4", "@capacitor/app": "^7.1.0", "@capacitor/browser": "^7.0.2", - "@capacitor/cli": "^7.2.0", - "@capacitor/core": "^7.2.0", - "@capacitor/filesystem": "^7.0.1", - "@capacitor/keyboard": "^7.0.1", + "@capacitor/cli": "^7.4.4", + "@capacitor/core": "^7.4.4", + "@capacitor/filesystem": "^7.1.4", + "@capacitor/keyboard": "^7.0.3", "@capacitor/preferences": "^7.0.2", - "@capacitor/screen-orientation": "^7.0.1", - "@capacitor/status-bar": "^7.0.1", - "@capawesome/capacitor-file-picker": "^7.0.1" + "@capacitor/screen-orientation": "^7.0.2", + "@capacitor/status-bar": "^7.0.3", + "@capawesome/capacitor-file-picker": "^7.2.0" } }, "mobile/node_modules/@capacitor-community/safe-area": { @@ -1754,9 +1754,9 @@ } }, "node_modules/@capacitor/android": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@capacitor/android/-/android-7.4.3.tgz", - "integrity": "sha512-VpjvnOcmYGPLgvXRhe3CGLs62Cg7sxOyp77NddCr+Y06qqgnoaj6OGeBVTc2DZlqZ6bSmh15JvFu82pkvmdgfQ==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@capacitor/android/-/android-7.4.4.tgz", + "integrity": "sha512-y8knfV1JXNrd6XZZLZireGT+EBCN0lvOo+HZ/s7L8LkrPBu4nY5UZn0Wxz4yOezItEII9rqYJSHsS5fMJG9gdw==", "license": "MIT", "peerDependencies": { "@capacitor/core": "^7.4.0" @@ -1781,9 +1781,9 @@ } }, "node_modules/@capacitor/cli": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-7.4.3.tgz", - "integrity": "sha512-SWozpdDgrbQ/ry1nIapugDFvE9z+l22BmU/+fpgL2Zv5487hGdXvCX5+1SluuFBP3IPpx6b4LjsKnBigyJoUWg==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-7.4.4.tgz", + "integrity": "sha512-J7ciBE7GlJ70sr2s8oz1+H4ZdNk4MGG41fsakUlDHWva5UWgFIZYMiEdDvGbYazAYTaxN3lVZpH9zil9FfZj+Q==", "license": "MIT", "dependencies": { "@ionic/cli-framework-output": "^2.2.8", @@ -1905,9 +1905,9 @@ } }, "node_modules/@capacitor/core": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.4.3.tgz", - "integrity": "sha512-wCWr8fQ9Wxn0466vPg7nMn0tivbNVjNy1yL4GvDSIZuZx7UpU2HeVGNe9QjN/quEd+YLRFeKEBLBw619VqUiNg==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.4.4.tgz", + "integrity": "sha512-xzjxpr+d2zwTpCaN0k+C6wKSZzWFAb9OVEUtmO72ihjr/NEDoLvsGl4WLfjWPcCO2zOy0b2X52tfRWjECFUjtw==", "license": "MIT", "dependencies": { "tslib": "^2.1.0" @@ -26290,12 +26290,12 @@ "version": "0.1.1-beta.0", "dependencies": { "@capacitor-community/safe-area": "^7.0.0-alpha.1", - "@capacitor/android": "^7.4.3", + "@capacitor/android": "^7.4.4", "@capacitor/app": "^7.1.0", "@capacitor/browser": "^7.0.2", - "@capacitor/cli": "^7.4.3", - "@capacitor/core": "^7.4.3", - "@capacitor/filesystem": "7.1.4", + "@capacitor/cli": "^7.4.4", + "@capacitor/core": "^7.4.4", + "@capacitor/filesystem": "^7.1.4", "@capacitor/keyboard": "^7.0.3", "@capacitor/preferences": "^7.0.2", "@capacitor/screen-orientation": "^7.0.2", diff --git a/remote-workspace/src/servers/api-server/platform-api/handler.ts b/remote-workspace/src/servers/api-server/platform-api/handler.ts index 4fad202..2d01175 100644 --- a/remote-workspace/src/servers/api-server/platform-api/handler.ts +++ b/remote-workspace/src/servers/api-server/platform-api/handler.ts @@ -3,8 +3,30 @@ import ignore from "ignore"; import path from "path"; // Define a safe root directory for projects. Can be overridden by env or configured as needed. +// All incoming URIs will be resolved and validated to ensure they don't escape this root. +const SAFE_ROOT = path.resolve( + process.env.PLATFORM_API_ROOT ?? "/pulse-editor", +); -const settingsPath = path.join("/pulse-editor", "settings.json"); +const settingsPath = path.join(SAFE_ROOT, "settings.json"); + +function safeResolve(uri: string): string { + if (!uri || typeof uri !== "string") { + throw new Error("Invalid path"); + } + + // Canonicalize the SAFE_ROOT once for this function + const rootPath = path.resolve(SAFE_ROOT); + // Combine and normalize the user input relative to the safe root + const candidate = path.resolve(SAFE_ROOT, uri); + + // Check that candidate is strictly under rootPath (or equal to rootPath) + if (candidate === rootPath || candidate.startsWith(rootPath + path.sep)) { + return candidate; + } + + throw new Error("Can only access paths within the project home directory."); +} export async function handlePlatformAPIRequest( data: { @@ -110,10 +132,9 @@ export async function handlePlatformAPIRequest( } } - // List all folders in a path async function handleListProjects(uri: string) { - const rootPath = uri; + const rootPath = safeResolve(uri); const files = await fs.promises.readdir(rootPath, { withFileTypes: true }); const folders = files .filter((file) => file.isDirectory()) @@ -131,7 +152,7 @@ async function listPathContent( options: any, baseUri: string | undefined = undefined, ) { - const rootPath = uri; + const rootPath = safeResolve(uri); const files = await fs.promises.readdir(rootPath, { withFileTypes: true }); const promise: Promise[] = files @@ -188,12 +209,14 @@ async function handleListPathContent(uri: string, options: any) { async function handleCreateProject(uri: string) { // Create a folder at the validated path - await fs.promises.mkdir(uri); + const safe = safeResolve(uri); + await fs.promises.mkdir(safe, { recursive: true }); } async function handleDeleteProject(uri: string) { // Delete the folder at the validated path - await fs.promises.rm(uri, { recursive: true, force: true }); + const safe = safeResolve(uri); + await fs.promises.rm(safe, { recursive: true, force: true }); } async function handleUpdateProject( @@ -203,51 +226,59 @@ async function handleUpdateProject( ctime?: Date; }, ) { - const newUri = path.join(path.dirname(uri), updatedInfo.name); - await fs.promises.rename(uri, newUri); + const safeOld = safeResolve(uri); + const newPathCandidate = path.join(path.dirname(safeOld), updatedInfo.name); + const safeNew = safeResolve(newPathCandidate); + await fs.promises.rename(safeOld, safeNew); } async function handleCreateFolder(uri: string) { // Create a folder at the validated path - await fs.promises.mkdir(uri); + const safe = safeResolve(uri); + await fs.promises.mkdir(safe, { recursive: true }); } async function handleCreateFile(uri: string) { // Create a file at the validated path - await fs.promises.writeFile(uri, ""); + const safe = safeResolve(uri); + // ensure parent exists + await fs.promises.mkdir(path.dirname(safe), { recursive: true }); + await fs.promises.writeFile(safe, ""); } async function handleRename(oldUri: string, newUri: string) { - await fs.promises.rename( - oldUri, - newUri, - ); + const safeOld = safeResolve(oldUri); + const safeNew = safeResolve(newUri); + await fs.promises.rename(safeOld, safeNew); } async function handleDelete(uri: string) { - await fs.promises.rm(uri, { + const safe = safeResolve(uri); + await fs.promises.rm(safe, { recursive: true, force: true, }); } async function handleHasPath(uri: string) { - return fs.existsSync(uri); + try { + const safe = safeResolve(uri); + return fs.existsSync(safe); + } catch (err) { + return false; + } } async function handleReadFile(uri: string) { // Read the file at validated path - const data = await fs.promises.readFile( - uri, - "utf-8", - ); - + const safe = safeResolve(uri); + const data = await fs.promises.readFile(safe, "utf-8"); return data; } async function handleWriteFile(data: any, uri: string) { // Write the data at validated path - const safePath = uri; + const safePath = safeResolve(uri); // create parent directory if it doesn't exist const dir = path.dirname(safePath); if (!fs.existsSync(dir)) { @@ -259,13 +290,9 @@ async function handleWriteFile(data: any, uri: string) { async function handleCopyFiles(from: string, to: string) { // Copy the files from the validated from path to the validated to path - await fs.promises.cp( - from, - to, - { - recursive: true, - }, - ); + const safeFrom = safeResolve(from); + const safeTo = safeResolve(to); + await fs.promises.cp(safeFrom, safeTo, { recursive: true }); } async function handleLoadSettings() { diff --git a/web/components/modals/login-modal.tsx b/web/components/modals/login-modal.tsx index a72d9ff..daea83d 100644 --- a/web/components/modals/login-modal.tsx +++ b/web/components/modals/login-modal.tsx @@ -28,7 +28,13 @@ export default function LoginModal({ signIn }: { signIn: () => void }) { return ( { + editorContext?.setEditorStates((prev) => ({ + ...prev, + isSigningIn: false, + })); + setIsModelOpen(open); + }} title={"Access Pulse Editor Workspace"} placement={"center"} > diff --git a/web/package.json b/web/package.json index c96b75c..5b532df 100644 --- a/web/package.json +++ b/web/package.json @@ -12,12 +12,12 @@ }, "dependencies": { "@capacitor-community/safe-area": "^7.0.0-alpha.1", - "@capacitor/android": "^7.4.3", + "@capacitor/android": "^7.4.4", "@capacitor/app": "^7.1.0", "@capacitor/browser": "^7.0.2", - "@capacitor/cli": "^7.4.3", - "@capacitor/core": "^7.4.3", - "@capacitor/filesystem": "7.1.4", + "@capacitor/cli": "^7.4.4", + "@capacitor/core": "^7.4.4", + "@capacitor/filesystem": "^7.1.4", "@capacitor/keyboard": "^7.0.3", "@capacitor/preferences": "^7.0.2", "@capacitor/screen-orientation": "^7.0.2", @@ -73,4 +73,4 @@ "typescript": "^5", "workbox-webpack-plugin": "^7.3.0" } -} +} \ No newline at end of file