[]previous and next page

What the build checks before you ship

What the build checks before you ship in the Security guide.

For agentsUpdated

Everything under dist/client is public: the bundles, the prerendered pages, the files copied from public/ and, if you ask for them, the source maps. A secret that gets into any of them is public from the moment you deploy, and a normal review will not see it, because it is not in your source; it arrived through a define, a constant, a file in public/ or a page that rendered it. So the build reads the output.

After the client build and the prerender, and before the manifest and the adapter copy the folder anywhere, the build reads every text file in dist/client and fails on two things.

The value of a server variable. Any server variable (a name without a public prefix) whose value is written as a quoted string in a file fails the build with [ENV_SECRET_EXPOSED], naming the variable and the file. A name that looks like a secret (SECRET, TOKEN, KEY, PASSWORD, DATABASE, COOKIE ...) is checked from 8 characters. Every other variable is checked from 16 characters, because a nondescript name (BILLING_ENDPOINT, MAILER) can hold a credential too, and because a shorter value (production, eu-west-1, a port) is too common to mean anything. The scan reads the env files and the environment of the build, so run the build with the same variables as production. The value is never printed.

A credential by its shape. An AWS access key id, a Stripe or Clerk secret key (sk_live_, sk_test_, rk_live_), a GitHub token, a Slack token, a Google API key, a webhook signing secret (whsec_) and a private key with its body fail the build with [BUILD_SECRET_PATTERN], naming the file and the rule. This finds what no variable explains: a key pasted into a file in public/, into a constant, into content that a page prerendered. Publishable keys (pk_), placeholders such as sk_test_xxxxxxxx, the ...EXAMPLE keys of vendor documentation and a library that merely names a PEM header are not flagged.

text
[BUILD_SECRET_PATTERN] notes.txt in the client output holds what looks like a credential (AWS access key id). Everything in the client output is public: remove it from the source or from public/.

Four kinds of value are left alone because they are public by nature: the deployment id (the client carries it on purpose, even when it comes from a commit id in the environment), a value that a public variable carries too, an http(s) URL without credentials or query string, and the variables the machine or the CI provider sets (npm_*, GITHUB_*, VERCEL_* ...) unless their name looks like a secret.

Source maps are off in production. A map carries the original sources, and with them every constant a developer wrote. A production client build writes none unless you ask: set build: { sourcemap: true } in vite.config.ts (or the client environment's own build.sourcemap) and they are written, and the scan then reads them too. Do not serve maps from the public host; upload them to your error tracker and delete them from the deploy.

Turning the scan off. build: { secretScan: false } in dusk.config.ts turns the output scan off for the app. From then on a secret in the browser output is your responsibility: nothing else in the build looks for it. Turn it off only for a value the scan cannot tell from a secret, and prefer fixing the cause (read the variable on the server only, give a browser variable a public prefix, move the file out of public/).

ts
// dusk.config.ts
export default defineConfig({
  build: { secretScan: false }, // default true
});

The scan covers build output. Keep secrets out of runtime responses, cookies and logs as well.