Skip to content

Latest commit

 

History

History
220 lines (153 loc) · 13.1 KB

File metadata and controls

220 lines (153 loc) · 13.1 KB
title Access and redaction
description Who can reach the devtools, and which values are redacted before they leave the page.
The devtools send what they read from your app to a server on your machine. Here is who can reach that server, and what is redacted on the way.

Access and redaction

The devtools read your running app and send what they find to a server on your machine. This page covers who can reach that server, and what is redacted on the way.

Don't expose the dev server beyond localhost. Some values, such as HTTP response previews, are sent as they are.

At a glance

Loopback requests only. A request that sends an Origin must come from a loopback host, a Chrome extension, allowedOrigins or Vite's server.allowedHosts. Asks for a one-time code when a non-loopback host or origin is allowed. A one-time code and an origin check that accepts loopback origins and the Chrome extension. Both on by default. Binds to localhost and asks for a one-time code by default. Reaches loopback hosts out of the box. Any other host needs a click on Allow access, for that host only.

Local-only access

Vite plugin

The devtools only answer requests from this machine. When a request carries an Origin header, that origin must be a loopback host, the Chrome extension or an origin you allowed. Requests without an Origin header pass the origin check. Browsers leave the header out of some cross-site requests, such as image loads and link clicks, so the origin check alone does not stop every request from another website.

In detail, a request to the devtools must:

  • come from a loopback address (any 127.x.x.x address or ::1), and
  • have no Origin header, or an origin that is a loopback host, a Chrome extension, an entry in allowedOrigins, or a host that Vite's server.allowedHosts accepts.

Other requests get 403 with the message "ng-devtools only answers requests from this machine." WebSocket upgrades follow the same rules.

If you open the dev server through another hostname that points to your machine (for example myapp.test), list it in Vite's server.allowedHosts and the devtools trust it too. Add other origins with allowedOrigins:

// vite.config.ts
import analog from '@analogjs/platform';
import ngDevtools from '@santoshyadavdev/ng-devtools/vite';
import {defineConfig} from 'vite';

export default defineConfig({
  server: {allowedHosts: ['myapp.test']},
  plugins: [analog(), ngDevtools({allowedOrigins: ['https://tunnel.example']})],
});

One-time code

The plugin's auth option decides whether the devtools also ask for the one-time code. The server prints the code in the terminal, and a browser reads data only after it exchanges that code.

auth One-time code
not set On if server.allowedHosts or allowedOrigins allows a host other than localhost or a loopback address, otherwise off. allowedHosts: true turns it on.
true Always on.
false Always off. The loopback and origin checks still apply.

With only loopback hosts allowed, the loopback and origin checks take the place of the code.

A tunnel client runs on your machine, so the requests it forwards come from a loopback address. That is why an allowed tunnel host or origin turns the one-time code on. If your tunnel rewrites the Host header to localhost, nothing in your config names the tunnel, so pass auth: true. Don't pass auth: false while a tunnel is allowed.

Express hub

initNgDevtoolsHub() has two checks, both on by default:

Check Option What it does
One-time code auth The server prints a code. A browser can read data only after it exchanges that code.
Origin check allowedOrigins Only loopback origins, the Chrome extension, or clients that send no Origin, can open the WebSocket. Pass a list to allow more origins.
// src/server.ts
import {initNgDevtoolsHub} from '@santoshyadavdev/ng-devtools/hub';
import express from 'express';

const app = express();

const devtools = initNgDevtoolsHub({
  allowedOrigins: ['https://tunnel.example'],
});
app.use(devtools.nodeMiddleware);

A list keeps loopback origins but replaces the Chrome extension default. If you use the extension with your own list, add its origin, chrome-extension://<id>, with the ID from chrome://extensions.

Pass auth: false only on a machine only you use. Keep it on when you allow a tunnel origin: the origin check does not tell who is on the other end of the tunnel. allowedOrigins: false turns the origin check off. Keep the check on for your own apps.

Standalone CLI

The CLI server binds to localhost and asks for a one-time code. --host changes the bind address and --no-auth turns the code off. See Standalone CLI.

MCP endpoint

The HTTP MCP endpoint answers only requests that carry a loopback Origin header. In the Vite plugin, the request must also come from a loopback address, like every devtools request.

While the one-time code is on, the endpoint also asks for a bearer token. That is the Express hub by default, and the Vite plugin when its code is on. The hub prints a generated token when it starts. Set NG_DEVTOOLS_MCP_TOKEN to choose the token yourself. Requests without the right Authorization: Bearer <token> header get 401. The stdio server needs no token. See Send a token.

Without a token, the Express hub answers only requests from a loopback address. With a token, it also answers other addresses that send the right token and a loopback Origin. Any client can set that header, so treat the token like a password.

Chrome extension

The extension has host permissions for loopback hosts only: localhost and its subdomains, 127.0.0.1 and [::1], over HTTP and HTTPS. On those hosts, the panel looks for the devtools server as soon as it opens.

On any other host, the panel doesn't send a request until you click Allow access. Chrome then asks you to grant the extension that one host, on the scheme of the page and any port. The extension never asks for all hosts at once.

Granting the extension a host doesn't change what the devtools server accepts. The server still applies the checks on this page. Both the Vite plugin and the Express hub accept the extension's chrome-extension:// origin by default. An Express hub with its own allowedOrigins list needs the extension origin in that list. See Chrome extension.

What is redacted

Live values leave the page. They are sent to the devtools server, shown in the panel and returned to agents. Redacted values are replaced with [redacted].

Forms

A field's value is replaced with [redacted] when the field:

  • is a password field,
  • has a password, one-time-code or credit-card autocomplete,
  • sits inside .sentry-mask, .rr-mask, [data-private] or [data-ng-devtools="mask"], or
  • has a name that contains a secret word (password, token, card, cvv, apiKey and similar).

Those values are also removed from error messages. The devtools don't write secret fields unless you unmask them (see Opt fields in or out). Other values are sent as they are, so keep real credentials out of forms you inspect.

Opt fields in or out

Mark a field in the template, or list keys on window:

<input name="nickname" data-ng-devtools="mask" />
<input name="cardHolder" data-ng-devtools="unmask" />
window.__NG_DEVTOOLS_FORMS__ = {mask: ['iban'], unmask: ['passport']};

[data-ng-devtools="unmask"] opts a field back in. The window setting does the same by key.

You can also name secret and unmasked fields on the server, with the redaction option. redaction.secretNames adds secret names for forms, the router, components, signals, NgRx and Analog, and redaction.unmask joins the window list. See Redaction options.

Unmasking also changes what the devtools can write. A key listed in unmask on window can be written. The element marker only lifts the checks that come from the element (password type, autocomplete and mask markers), so a field with a secret-looking name is still not written.

password, passwd, passphrase, passcode, pass, pwd, secret, token, otp, totp, pin, cvv, cvc, csc, ssn, iban, card, cc, credential and credentials. Names are split on camelCase and punctuation, so userPassword and card_number both match. The pairs apiKey, privateKey, secretKey, accessKey, ccNum, ccNumber and securityCode match as well.

Router

These are replaced with [redacted] in URLs, params, data and messages:

  • query, matrix and fragment keys that look secret (token, password, api key, code, sig, session, jwt and similar), including inside encoded return URLs,
  • JWTs, bearer tokens and long opaque tokens,
  • route params with secret-looking names.

A secret route param is only known once the route is recognized or found in the config. A navigation that fails before that (for example inside a lazy route that failed to load) can still show it in its URL.

A navigation whose URL was redacted cannot be replayed.

Components, signals and NgRx

Component inputs, signal values and NgRx state use the same secret names as forms. A value whose name looks secret is replaced with [redacted]. JWTs and bearer tokens inside strings and error messages are replaced too.

Analog

Server call previews and URLs are redacted: secret-looking keys in JSON bodies, secret query parameters, JWTs and bearer tokens. Only JSON and plain text responses get a preview, and it is cut at 1000 characters. The load() data preview on the open page redacts secret-looking keys too.

Not redacted

Response previews and TransferState values in the SSR & HTTP tab are not redacted. They reach the devtools server unchanged, so don't expose the dev server beyond localhost.

Checklist

Open the app on localhost. Add other hostnames or origins one by one, only when you need them. Keep auth and the origin check on in the Express hub unless the machine is yours alone. In the Vite plugin, don't pass auth: false while a tunnel host or origin is allowed. Keep real credentials out of forms and API responses you inspect. Use data-ng-devtools="mask", window.__NG_DEVTOOLS_FORMS__ or redaction.secretNames for fields the secret words miss. Set agent.readOnly or turn off actions to stop the panel and agents from writing to your app. See Configuration.

Related pages