Skip to content

fix(@angular/build): skip HTML fallback for dotfile requests#32774

Open
instantraaamen wants to merge 2 commits intoangular:mainfrom
instantraaamen:fix/dotfile-html-fallback
Open

fix(@angular/build): skip HTML fallback for dotfile requests#32774
instantraaamen wants to merge 2 commits intoangular:mainfrom
instantraaamen:fix/dotfile-html-fallback

Conversation

@instantraaamen
Copy link

Description

Currently, the Angular dev server's HTML fallback middleware rewrites requests for dotfiles (such as /.env, /.npmrc, /.htpasswd) to /index.html and returns a 200 OK response. This happens because path.extname() treats leading-dot filenames as extensionless (e.g., extname('.env') returns ''), so lookupMimeTypeFromRequest returns undefined and these requests fall through the existing MIME type check to the SPA fallback logic.

While the actual file contents are not leaked (the response body is the Angular app's index.html), returning 200 for sensitive dotfiles is not ideal. Explicitly skipping the SPA fallback for dotfile requests would be a small defense-in-depth improvement, especially when the dev server is exposed to the network via --host.

This PR adds a check in angularHtmlFallbackMiddleware to skip the HTML fallback for requests where the last path segment starts with . (e.g., /.env, /.npmrc). The check uses url.split('?')[0], which is the same pattern already used internally by lookupMimeTypeFromRequest. Instead of rewriting to /index.html, these requests are passed to next(), which will result in a 404 from downstream middleware.

Motivation

I noticed this behavior while researching how various frameworks handle sensitive file access on their dev servers. The Angular dev server already has good protection through its strict server.fs.allow configuration, but adding this small check makes the behavior more explicit and prevents dotfile URLs from being silently rewritten to index.html.

Changes

  • html-fallback-middleware.ts: Added a dotfile check at the top of the if (req.url) block, before the MIME type check. Requests for paths whose last segment starts with . (e.g., /.env) are no longer rewritten to /index.html.

Before

GET /.env       → 200 (index.html content)
GET /.npmrc     → 200 (index.html content)
GET /.htpasswd  → 200 (index.html content)

After

GET /.env       → 404
GET /.npmrc     → 404
GET /.htpasswd  → 404

Normal Angular routes continue to work as expected (e.g., GET /dashboard200 with index.html).

Made with Cursor

The HTML fallback middleware currently rewrites dotfile requests
(e.g., /.env, /.npmrc) to /index.html, returning 200 instead of 404.
This change skips the SPA fallback when the last URL path segment
starts with a dot, providing a small defense-in-depth improvement
for the dev server.

Made-with: Cursor
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enhances security by preventing the dev server from serving index.html for requests to dotfiles (e.g., /.env). The change correctly identifies such requests and skips the HTML fallback, resulting in a 404 response instead of a 200. The implementation is sound, but I've suggested a minor improvement to make the logic for extracting the filename from the URL path more robust by using the standard path module, which also handles edge cases like trailing slashes correctly.

if (req.url) {
// No fallback for dotfile requests (e.g., .env, .npmrc)
const pathname = req.url.split('?')[0];
const lastSegment = pathname.substring(pathname.lastIndexOf('/') + 1);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For improved readability and robustness, consider using path.posix.basename to extract the last segment of the URL path. This is more declarative than manual string manipulation.

It also correctly handles paths with trailing slashes. For example, with the current implementation, a request for /.env/ would result in an empty lastSegment and would not be blocked. path.posix.basename('/.env/') correctly returns .env, ensuring such requests are also handled as intended.

You would need to add the following import at the top of the file:

import { basename } from 'node:path/posix';
Suggested change
const lastSegment = pathname.substring(pathname.lastIndexOf('/') + 1);
const lastSegment = basename(pathname);

Apply Gemini Code Assist suggestion: use basename from node:path/posix
instead of manual string manipulation. This is more readable and
correctly handles trailing slashes (e.g., /.env/ -> .env).

Made-with: Cursor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant