skip to content

How should a team decide which Cypress config values go in `env` and which in `expose`?

level: principalimportance: nice to knowfreq 28%

answer

  1. Ask about reach, not about appearance
  2. The two mistakes cost different amounts
  3. One default, with promotions justified
  4. Synchronous need is the only real argument
  5. Keep the classification in one diff

basics

~20 s

Default everything to the env block and promote to expose only with a stated reason. The test is not whether the value looks like a password, but whether the page, its third-party scripts and its extensions may hold it for the length of a run.

solid answer

~50 s

Treat `expose` as a publication channel rather than a convenience, and the conversation changes: not "is this sensitive?" but "may the page, every script on it, every extension in the profile, and everyone who reads a captured run hold this?" The mistakes are asymmetric — a wrongly published credential has to be replaced, while a wrongly private value costs a refactor — so the default is `env`, and a key earns `expose` by having someone who must read it synchronously. Price the over-classification honestly, because it is real: `cy.env()` is a command, so a value in `env` cannot be read at module scope, in a support file, or by a plugin, and as of Cypress 16 per-test configuration overrides accept only `expose`. Keep both blocks in one configuration file so the classification shows up as a reviewable diff.

go deeper

for a junior

You are not expected to set the policy, but know which block you would reach for by default and be able to say why a credential never goes in the exposed one.

for a middle

Explain the mechanical consequence of each choice: browser-visible and synchronous on one side, Node-held and command-shaped on the other, and what that does to where the value can be read.

for a senior

Bring examples of values you have reclassified after they went wrong, and describe how you found them in a suite you inherited rather than one you designed.

for a principal

Be ready to defend a default, price the refactor that over-classification forces, and say plainly where this decision stops and secrets storage, rotation and pipeline injection begin.

## Frame it as a boundary question, not a sensitivity label The useful test is not "does this look like a password". It is a question about reach: > Would I be content for the application under test, every third-party script on the page, every > extension in the browser profile, and everyone who later reads a captured run to hold this value > for the duration of the run? If the answer is anything other than a confident yes, the value belongs in the `env` block, read with `cy.env()`. `expose` is the **publication** channel, and calling it that in a design review changes how people argue about it. "Should this be exposed?" invites a shrug; "should this be published to the page?" does not. ## The asymmetry that sets the default The two mistakes do not cost the same, so the default should not be neutral: - **Wrongly public** is not fully recoverable. Once a credential has been in browser state across a run, the honest response is to replace it, which touches the pipeline and every environment that used it. - **Wrongly private** costs a refactor. Someone discovers they cannot read the value at module scope, moves the read into a hook or a custom command, and the suite is a little more verbose. So: **default everything to `env`, and promote to `expose` on a stated reason.** The reason is almost always "this must be read synchronously outside a test", and it should be written down next to the key. ## What over-classifying costs, in Cypress specifically This is the part that is not generic advice, and it is what a lead is expected to have priced: 1. **A value in `env` cannot be read outside a running test or hook.** `cy.env()` is a command. No module-scope constant, no top-level code in a support file, no Mocha `describe` body, no plugin can read it. Code that used a synchronous read has to be restructured, not merely edited. 2. **Every read is another queued command**, with its own default four-second timeout, in a suite that may read the same value in hundreds of tests. A Mocha `before()` hook or a custom command usually pays for itself. 3. **Plugin ecosystems assume synchronous configuration.** A plugin that wants its settings before the first test can only take them from `expose`. If a plugin needs something genuinely sensitive, that is a signal about the plugin, not a reason to publish. 4. **Test-level overrides only accept `expose`.** As of Cypress 16, `env` is not valid in a per-test or per-suite configuration override at all, so per-test variation is available only on the public side of the line. ## The values teams misfile, in both directions | Value | Usually filed as | Why that is wrong | |---|---|---| | Internal hostname of a service reachable only inside the network | `expose`, as "just a URL" | it publishes internal topology to every script on the page | | Tenant identifier used as an authorization subject | `expose`, as "just an id" | if the backend trusts it, it is a credential in effect | | Shared test account's real mailbox address | `expose`, as "test data" | it is a live identity, and it will be scraped | | Environment label such as `staging` | `env`, out of caution | it is the textbook exposed value, and hiding it costs readability | | Public API version the console negotiates | `env`, out of caution | it is already visible in every request the app makes | The pattern in both columns is the same: people classify by how the value *looks* rather than by what an attacker or a third-party script could do with it. ## Keeping the decision reviewable - **Keep the classification in one place.** Two blocks in one configuration file mean the decision shows up as a diff a reviewer sees, instead of being rediscovered per test. - **Comment the promotions, not the demotions.** A key in `env` needs no defence; a key in `expose` should carry one line saying who needs it synchronously. - **Make the safe path the short path.** If reading a token means writing a nested `.then()` every time, someone will eventually move it to `expose` to save keystrokes. A custom command that wraps the read removes the incentive. - **Re-run the classification when the suite gains a new environment**, because a value that was harmless against a disposable sandbox may not be against a shared, data-carrying target. ## Where this decision stops Deciding which side of the browser boundary a value lands on is the whole of the Cypress-side question, and it is worth saying explicitly what it does not settle: how the secret is stored, how often it is rotated, how the pipeline injects it, and whether it should exist at all are secrets-management and delivery concerns that this configuration choice inherits rather than answers. A lead who blurs those together usually ends up arguing about the wrong file.

  • What kinds of values in a Cypress suite look public but should stay in `env`?
    Internal hostnames that reveal topology, tenant identifiers the backend treats as authorization subjects, and shared test accounts backed by real mailboxes. Each looks like harmless data and each is useful to someone reading the page. The filing rule is what a third-party script could do with the value, not how ordinary it looks in a config file.
  • What does a Cypress team give up by classifying a value as sensitive?
    Synchronous access. `cy.env()` only runs inside a test or hook, so plugin code, support-file top-level code and `describe` bodies cannot read it, and per-test configuration overrides cannot carry it. Every read is also another queued command, which is why a shared custom command usually pays for itself in a large suite.

saying these in an interview costs you the question

  • Classifies by whether the value looks like a password
  • Puts values in expose because the command is inconvenient
  • Treats the two misfiling mistakes as equally costly
  • Scatters the decision across specs instead of the config
  • Never revisits the classification when a new target appears