Skip to content

emulator: validate Host header to mitigate DNS-rebinding#10596

Open
adilburaksen wants to merge 1 commit into
firebase:mainfrom
adilburaksen:harden/emulator-host-validation
Open

emulator: validate Host header to mitigate DNS-rebinding#10596
adilburaksen wants to merge 1 commit into
firebase:mainfrom
adilburaksen:harden/emulator-host-validation

Conversation

@adilburaksen
Copy link
Copy Markdown

Description

The local emulator HTTP servers all use cors({ origin: true }) and perform no Host-header validation. A web page open in a developer's browser can therefore reach the (unauthenticated) emulator APIs via DNS-rebinding — rebind an attacker-controlled domain to 127.0.0.1, after which requests are same-origin so CORS no longer applies — and drive the emulator endpoints (read/modify/delete local emulator data, toggle Cloud Functions triggers, etc.).

Fix

Add a small hostValidationMiddleware (src/emulator/hostValidation.ts) that rejects requests whose Host header is not a loopback/configured host, wired as the first middleware on every emulator's express app — Hub/UI via ExpressBasedEmulator, plus Auth, Storage, Eventarc, Tasks, and Functions.

Enforcement happens only when the emulator is bound exclusively to loopback addresses (the rebinding-vulnerable default). If the operator bound to a non-loopback address (0.0.0.0, ::, or a specific host) they have opted into remote access — e.g. the tunnel support from #4227 — so the middleware becomes a no-op and those setups keep working.

The legitimate callers (the HubClient node client, the CLI, and the Emulator UI) send loopback Host headers and requests without a Host header are allowed, so they are unaffected.

Testing

  • Typecheck (tsc --noEmit) passes; files are prettier-clean.
  • Middleware behavior verified: under a loopback bind it allows Host: localhost / 127.0.0.1 / ::1 / [::1] / the configured host (and requests with no Host), and returns 403 for an untrusted host (e.g. a rebound attacker domain); under a non-loopback bind it is a pass-through.

@wiz-9635d3485b
Copy link
Copy Markdown

wiz-9635d3485b Bot commented Jun 3, 2026

Wiz Scan Summary

Scanner Findings
Vulnerability Finding Vulnerabilities -
Data Finding Sensitive Data -
Secret Finding Secrets -
IaC Misconfiguration IaC Misconfigurations -
SAST Finding SAST Findings 18 Low
Software Management Finding Software Management Findings -
Total 18 Low

View scan details in Wiz

To detect these findings earlier in the dev lifecycle, try using Wiz Code VS Code Extension.

Copy link
Copy Markdown
Contributor

@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 introduces host validation middleware across various Firebase emulators (Express-based, Auth, Eventarc, Functions, Storage, and Tasks) to mitigate DNS-rebinding attacks by rejecting requests with untrusted Host headers when bound to loopback interfaces. The feedback suggests improving the robustness of this middleware by retrieving all bound addresses for the storage emulator to prevent breaking remote access/tunneling configurations, and strictly handling potential null or undefined host/address values in isLoopbackAddress and hostValidationMiddleware to align with the repository's strict null safety guidelines.


// Reject requests with an untrusted Host header to mitigate DNS-rebinding.
// Only enforced when bound to loopback (see hostValidationMiddleware).
app.use(hostValidationMiddleware([emulator.getInfo().host]));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Passing only [emulator.getInfo().host] to the host validation middleware can break remote access/tunneling configurations.

If the storage emulator is configured to bind to both a loopback address (e.g., 127.0.0.1) and a wildcard/public address (e.g., 0.0.0.0), emulator.getInfo().host may return the loopback address. This causes the middleware to incorrectly assume the emulator is bound only to loopback, enabling host validation enforcement and blocking legitimate remote requests.

To fix this, retrieve all bound addresses from emulator.getInfo().listen if available.

Suggested change
app.use(hostValidationMiddleware([emulator.getInfo().host]));
const info = emulator.getInfo();
const listenAddresses = info.listen ? info.listen.map((s) => s.address) : [info.host];
app.use(hostValidationMiddleware(listenAddresses));

Comment on lines +10 to +11
export function isLoopbackAddress(addr: string): boolean {
const normalized = addr.trim().toLowerCase();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To adhere to strict null safety and defensive programming practices, update isLoopbackAddress to explicitly handle undefined or null values. This prevents potential runtime crashes if an invalid or unconfigured host is passed.

export function isLoopbackAddress(addr: string | undefined | null): boolean {
  if (!addr) {
    return false;
  }
  const normalized = addr.trim().toLowerCase();
References
  1. Use strict null checks and handle undefined/null explicitly. (link)

Comment on lines +55 to +69
export function hostValidationMiddleware(bindAddresses: string[]): express.RequestHandler {
// Cannot determine the bind address(es) — don't break anything.
if (bindAddresses.length === 0) {
return (req, res, next) => next();
}

// Operator opted into remote access by binding to a non-loopback address.
if (!bindAddresses.every((addr) => isLoopbackAddress(addr))) {
return (req, res, next) => next();
}

const allowedHosts = new Set(["localhost", "::1", "[::1]"]);
for (const addr of bindAddresses) {
allowedHosts.add(addr.trim().toLowerCase());
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Update hostValidationMiddleware to safely handle potentially undefined or null bind addresses. Filtering out falsy values ensures that the middleware does not crash when processing configuration lists that may contain empty or unresolved host entries.

Suggested change
export function hostValidationMiddleware(bindAddresses: string[]): express.RequestHandler {
// Cannot determine the bind address(es) — don't break anything.
if (bindAddresses.length === 0) {
return (req, res, next) => next();
}
// Operator opted into remote access by binding to a non-loopback address.
if (!bindAddresses.every((addr) => isLoopbackAddress(addr))) {
return (req, res, next) => next();
}
const allowedHosts = new Set(["localhost", "::1", "[::1]"]);
for (const addr of bindAddresses) {
allowedHosts.add(addr.trim().toLowerCase());
}
export function hostValidationMiddleware(bindAddresses: (string | undefined | null)[]): express.RequestHandler {
const cleanAddresses = (bindAddresses || []).filter((addr): addr is string => !!addr);
// Cannot determine the bind address(es) — don't break anything.
if (cleanAddresses.length === 0) {
return (req, res, next) => next();
}
// Operator opted into remote access by binding to a non-loopback address.
if (!cleanAddresses.every((addr) => isLoopbackAddress(addr))) {
return (req, res, next) => next();
}
const allowedHosts = new Set(["localhost", "::1", "[::1]"]);
for (const addr of cleanAddresses) {
allowedHosts.add(addr.trim().toLowerCase());
}
References
  1. Use strict null checks and handle undefined/null explicitly. (link)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants