Skip to content

Commit 70748ca

Browse files
committed
fix(@angular/ssr): ensure public directory containment in CommonEngine
Ensure that paths resolved in `retrieveSSGPage` strictly remain within the configured `publicPath` by checking `relative()` containment before evaluating the static file. Previously, a string `startsWith()` check was used, which could match sibling directories that share the same name prefix as `publicPath`. (cherry picked from commit bb72145)
1 parent 1c9b1af commit 70748ca

1 file changed

Lines changed: 8 additions & 6 deletions

File tree

packages/angular/ssr/node/src/common-engine/common-engine.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { ApplicationRef, StaticProvider, Type } from '@angular/core';
1010
import { BootstrapContext } from '@angular/platform-browser';
1111
import { renderApplication, renderModule, ɵSERVER_CONTEXT } from '@angular/platform-server';
1212
import * as fs from 'node:fs';
13-
import { dirname, join, normalize, resolve } from 'node:path';
13+
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
1414
import { URL } from 'node:url';
1515
import { validateUrl } from '../../../src/utils/validation';
1616
import { getAllowedHostsFromEnv } from '../environment-options';
@@ -155,16 +155,18 @@ export class CommonEngine {
155155
// See: https://portswigger.net/web-security/file-path-traversal
156156
const pagePath = join(publicPath, pathname, 'index.html');
157157

158+
const relativePath = relative(publicPath, pagePath);
159+
const isOutside =
160+
relativePath === '..' || relativePath.startsWith('../') || relativePath.startsWith('..\\');
161+
if (isOutside || isAbsolute(relativePath)) {
162+
return undefined;
163+
}
164+
158165
if (this.pageIsSSG.get(pagePath)) {
159166
// Serve pre-rendered page.
160167
return fs.promises.readFile(pagePath, 'utf-8');
161168
}
162169

163-
if (!pagePath.startsWith(normalize(publicPath))) {
164-
// Potential path traversal detected.
165-
return undefined;
166-
}
167-
168170
if (pagePath === resolve(documentFilePath) || !(await exists(pagePath))) {
169171
// View matches with prerender path or file does not exist.
170172
return undefined;

0 commit comments

Comments
 (0)