mirror of
https://github.com/HeyPuter/puter.git
synced 2026-05-27 03:42:34 +00:00
assert normalized
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
import Busboy from 'busboy';
|
||||
import type { Request, Response } from 'express';
|
||||
import { posix as pathPosix } from 'node:path';
|
||||
import { assertNormalized } from '../../services/fs/resolveNode.js';
|
||||
import { pipeline } from 'node:stream/promises';
|
||||
import type { Actor } from '../../core/actor.js';
|
||||
import { Context } from '../../core/context.js';
|
||||
@@ -1787,7 +1788,8 @@ export class FSController extends PuterController {
|
||||
pathToNormalize = `/${username}${pathToNormalize.slice(1)}`;
|
||||
}
|
||||
|
||||
let normalizedPath = pathPosix.normalize(pathToNormalize);
|
||||
assertNormalized(pathToNormalize);
|
||||
let normalizedPath = pathToNormalize;
|
||||
if (!normalizedPath.startsWith('/')) {
|
||||
normalizedPath = `/${normalizedPath}`;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import type { RequestHandler } from 'express';
|
||||
import { stat } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import type { IConfig } from '../../../types';
|
||||
import { assertNormalized } from '../../../services/fs/resolveNode.js';
|
||||
|
||||
/** Native-app subdomains served via `nativeAppStatic`. */
|
||||
const NATIVE_APP_SUBDOMAINS = [
|
||||
@@ -130,10 +131,8 @@ export const createNativeAppStatic = (config: IConfig): RequestHandler => {
|
||||
? path.join(root, active, 'dist')
|
||||
: path.join(root, active);
|
||||
|
||||
// req.path is already url-decoded by express; normalize strips any
|
||||
// `..` segments before sendFile's `root` option enforces its own
|
||||
// traversal guard.
|
||||
const requested = path.normalize(req.path);
|
||||
const requested = req.path;
|
||||
assertNormalized(requested);
|
||||
const absolute = path.join(appRoot, requested);
|
||||
|
||||
try {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import crypto from 'node:crypto';
|
||||
import { posix as pathPosix } from 'node:path';
|
||||
import { assertNormalized } from '../../services/fs/resolveNode.js';
|
||||
import { Readable } from 'node:stream';
|
||||
import { Context } from '../../core/context.js';
|
||||
import { HttpError } from '../../core/http/HttpError.js';
|
||||
@@ -419,7 +420,7 @@ export class ImageGenerationDriver extends PuterDriver {
|
||||
if (resolved === '~' || resolved.startsWith('~/')) {
|
||||
resolved = `/${username}${resolved.slice(1)}`;
|
||||
}
|
||||
resolved = pathPosix.normalize(resolved);
|
||||
assertNormalized(resolved);
|
||||
if (!resolved.startsWith('/')) {
|
||||
resolved = `/${resolved}`;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
import { posix as pathPosix } from 'node:path';
|
||||
import { assertNormalized } from '../../services/fs/resolveNode.js';
|
||||
import { Readable } from 'node:stream';
|
||||
import { Context } from '../../core/context.js';
|
||||
import { HttpError } from '../../core/http/HttpError.js';
|
||||
@@ -458,7 +459,7 @@ export class VideoGenerationDriver extends PuterDriver {
|
||||
if (resolved === '~' || resolved.startsWith('~/')) {
|
||||
resolved = `/${username}${resolved.slice(1)}`;
|
||||
}
|
||||
resolved = pathPosix.normalize(resolved);
|
||||
assertNormalized(resolved);
|
||||
if (!resolved.startsWith('/')) {
|
||||
resolved = `/${resolved}`;
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ export class ACLService extends PuterService {
|
||||
if (resource.path === '/') {
|
||||
return (PUBLIC_READ_MODES as AclMode[]).includes(mode);
|
||||
}
|
||||
const ancestors = await resource.resolveAncestors();
|
||||
|
||||
const components = resource.path.slice(1).split('/');
|
||||
|
||||
@@ -171,7 +172,6 @@ export class ACLService extends PuterService {
|
||||
const authorizer = actor.accessToken.issuer;
|
||||
if (!(await this.check(authorizer, resource, mode))) return false;
|
||||
|
||||
const ancestors = await resource.resolveAncestors();
|
||||
for (const ancestor of ancestors) {
|
||||
const permissions =
|
||||
mode === MANAGE_PERM_PREFIX
|
||||
@@ -219,7 +219,6 @@ export class ACLService extends PuterService {
|
||||
// Fall back to the permission scan: walk ancestors, any hit wins.
|
||||
// Widen the scan to all "higher" modes (`write` covers `read`/`list`/
|
||||
// `see`, etc.) so granting a stronger mode implies the weaker ones.
|
||||
const ancestors = await resource.resolveAncestors();
|
||||
for (const ancestor of ancestors) {
|
||||
const permissions =
|
||||
mode === MANAGE_PERM_PREFIX
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
import { posix as pathPosix } from 'node:path';
|
||||
import { assertNormalized } from './resolveNode.js';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { Readable, Transform } from 'node:stream';
|
||||
import type { TransformCallback } from 'node:stream';
|
||||
@@ -268,7 +269,8 @@ export class FSService extends PuterService {
|
||||
);
|
||||
}
|
||||
|
||||
let normalizedPath = pathPosix.normalize(trimmedPath);
|
||||
assertNormalized(trimmedPath);
|
||||
let normalizedPath = trimmedPath;
|
||||
if (!normalizedPath.startsWith('/')) {
|
||||
normalizedPath = `/${normalizedPath}`;
|
||||
}
|
||||
|
||||
@@ -130,6 +130,15 @@ export function splitParentAndName(absolutePath: string): {
|
||||
return { parentPath: parentPath === '.' ? '/' : parentPath, name };
|
||||
}
|
||||
|
||||
export function assertNormalized(input: string): string {
|
||||
if (pathPosix.normalize(input) !== input) {
|
||||
throw new HttpError(400, 'Invalid path', {
|
||||
legacyCode: 'bad_request',
|
||||
});
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
export function normalizeAbsolutePath(path: string): string {
|
||||
const trimmed = typeof path === 'string' ? path.trim() : '';
|
||||
if (trimmed.length === 0) {
|
||||
@@ -137,7 +146,8 @@ export function normalizeAbsolutePath(path: string): string {
|
||||
legacyCode: 'bad_request',
|
||||
});
|
||||
}
|
||||
let normalized = pathPosix.normalize(trimmed);
|
||||
assertNormalized(trimmed);
|
||||
let normalized = trimmed;
|
||||
if (!normalized.startsWith('/')) {
|
||||
normalized = `/${normalized}`;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import { statfs } from 'node:fs/promises';
|
||||
import { posix as pathPosix } from 'node:path';
|
||||
import { assertNormalized } from '../../services/fs/resolveNode.js';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { HttpError } from '../../core/http/HttpError.js';
|
||||
import type { LayerInstances } from '../../types.js';
|
||||
@@ -175,7 +176,8 @@ export class FSEntryStore extends PuterStore {
|
||||
});
|
||||
}
|
||||
|
||||
let normalized = pathPosix.normalize(trimmed);
|
||||
assertNormalized(trimmed);
|
||||
let normalized = trimmed;
|
||||
if (!normalized.startsWith('/')) {
|
||||
normalized = `/${normalized}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user