How to Manage Environment Variables Without Exposing Secrets
A vibe-coded application often begins with configuration written directly into the source code:
const apiUrl = "http://localhost:3000";
const emailApiKey = "example-secret-key";
const databaseUrl = "postgresql://user:password@localhost:5432/app";This may allow the application to work locally, but it creates immediate problems.
The same code cannot move cleanly between development, staging, and production. Credentials can be committed to Git. A database password may appear in a screenshot, code review, build log, browser bundle, or AI conversation. Changing a service URL requires editing and redeploying code even when the application logic has not changed.
Environment variables help separate deployment-specific configuration from application code.
Instead of storing the database connection string in a JavaScript file, the server reads it from its runtime environment:
const databaseUrl = process.env.DATABASE_URL;The local development environment can provide one value, while staging and production provide different values without changing the source code.
That separation is useful, but it does not make every environment variable automatically secret. Frontend build tools may copy selected variables into public JavaScript. Deployment logs may print values. Processes running in the same environment may be able to access them. A poorly scoped preview deployment may receive production credentials.
The goal is therefore not merely to “put secrets in environment variables.” The goal is to control which values exist, where they are available, when they are loaded, who can change them, and how you verify that they were not exposed.
This article continues with the appointment-booking application deployed in the previous guide:
Frontend:
- React
- Vite
- Render Static Site
Backend:
- Node.js
- Express
- Render Web Service
Database:
- PostgreSQL
External service:
- Transactional email providerThe examples are illustrative. Adapt variable names and validation logic to your project.
What an Environment Variable Is
An environment variable is a named value supplied to a process by the environment in which that process runs.
A Node.js server can read an environment variable through process.env:
const appBaseUrl = process.env.APP_BASE_URL;A Python application might use:
import os
database_url = os.environ.get("DATABASE_URL")The value is not written directly into the application’s source file. It is supplied separately by your shell, local .env loader, continuous-integration system, container platform, or deployment provider.
The Twelve-Factor App methodology recommends storing configuration that changes between deployments in the environment rather than hardcoding it into the application. This makes it possible to change deployment-specific values without changing application code.
Examples of deployment-specific configuration include:
- Database connection URLs
- External API endpoints
- Session configuration
- Email-provider credentials
- Logging levels
- Allowed web origins
- Public application URLs
- Feature switches
- Storage-bucket names
- OAuth callback configuration
Not every constant belongs in an environment variable.
Application behavior that does not change between deployments can remain in code. For example, a route path, validation rule, or fixed list of supported appointment states may belong in a normal module.
This distinction prevents environment variables from becoming an undocumented collection of unrelated switches.
Environment Variables and Secrets Are Not the Same Thing
An environment variable describes how a value is provided. A secret describes how sensitive the value is.
Consider these variables:
APP_BASE_URL=https://appointments.example.com
LOG_LEVEL=info
DATABASE_URL=postgresql://...
EMAIL_API_KEY=...
SESSION_SECRET=...APP_BASE_URL and LOG_LEVEL are configuration, but they are not usually confidential.
DATABASE_URL, EMAIL_API_KEY, and SESSION_SECRET are sensitive. They require stricter controls because someone who obtains them may be able to access data, send messages, impersonate application sessions, or consume paid services.
A useful classification is:
| Classification | Examples | Can appear in browser code? |
|---|---|---|
| Public configuration | Public API URL, public site URL, display mode | Yes, when intentionally exposed |
| Internal configuration | Log level, internal service hostname, worker settings | No |
| Secret | Database password, private API key, session secret | No |
| Highly sensitive secret | Private signing key, privileged production credential | No; consider a dedicated secret system |
Environment variables are convenient for small applications and managed hosting platforms, but they are not the strongest possible secret-delivery mechanism in every architecture. OWASP warns that environment variables may be available to processes in the same environment and can sometimes appear in logs or system dumps. More mature systems may instead inject secrets through dedicated secret stores or mounted files with restricted access.
For a first Render deployment, platform-managed environment variables are a practical improvement over hardcoded credentials or committed .env files. As the application’s risk increases, evaluate whether a dedicated secrets manager, short-lived credentials, workload identity, or restricted secret files would provide better protection.
The Most Important Rule: Browser Variables Are Public
A frontend application runs on the user’s device. The browser must receive its HTML, CSS, and JavaScript to execute it.
Any value included in that delivered code can be inspected.
This remains true when:
- The repository is private.
- The JavaScript is minified.
- The variable is labeled “secret.”
- The value came from a deployment dashboard.
- The value is stored in a
.envfile. - The browser developer tools do not show it immediately.
- The application is available only through HTTPS.
For Vite, variables using the default VITE_ prefix are exposed to client-side code through import.meta.env. Vite replaces those references during the build, and the resulting values become part of the generated browser assets.
This is appropriate:
VITE_API_URL=https://api.example.comThe browser needs the public API address to send requests.
This is unsafe:
VITE_EMAIL_API_KEY=real-private-key
VITE_DATABASE_URL=postgresql://user:password@host/database
VITE_SESSION_SECRET=real-session-secretThe VITE_ prefix should be read as:
This value is intentionally permitted to enter the public browser bundle.
It should never be read as:
Vite will protect this value for me.
The same principle applies to other frontend frameworks. They use different naming conventions, but any variable deliberately exposed to client-side code must be treated as public.
Use the Browser-to-Server Pattern for Private Credentials
Suppose the appointment app sends confirmation emails.
The unsafe design is:
Browser
|
| Uses private email API key
v
Email providerThe email-provider key must be delivered to the browser for this design to work. Anyone can inspect or reuse it.
The safer design is:
Browser
|
| Sends appointment request
v
Express backend
|
| Uses server-only email API key
v
Email providerThe frontend sends only the appointment information required by your API:
const response = await fetch(
`${import.meta.env.VITE_API_URL}/api/appointments`,
{
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
name,
email,
slotId
})
}
);The server reads the private credential:
const emailApiKey = process.env.EMAIL_API_KEY;
if (!emailApiKey) {
throw new Error("EMAIL_API_KEY is required");
}The server must still validate the request, enforce authorization where required, and restrict how the credential can be used. Moving a key to the backend is necessary, but it does not automatically make the endpoint safe.
Create an Environment-Variable Inventory
Before adding values to a hosting dashboard, identify every variable the application uses.
For a Node.js backend, search for:
git grep -n "process.env"For the Vite frontend:
git grep -n "import.meta.env"Also review:
- Package scripts
- Deployment configuration
- Dockerfiles
- CI workflows
- Migration scripts
- Test configuration
- Authentication callbacks
- Storage configuration
Create an inventory:
| Variable | Component | Classification | Required when |
VITE_API_URL | Frontend | Public | Build |
DATABASE_URL | Backend | Secret | Migration and runtime |
SESSION_SECRET | Backend | Secret | Runtime |
EMAIL_API_KEY | Backend or worker | Secret | Runtime |
APP_BASE_URL | Backend | Public configuration | Runtime |
ALLOWED_ORIGINS | Backend | Internal configuration | Runtime |
LOG_LEVEL | Backend | Internal configuration | Runtime |
The Required when column matters because some values are needed while creating an application build, while others should exist only when the server is running.
This distinction becomes important when deciding whether changing a value requires rebuilding the application.
Understand Build-Time and Runtime Variables
A build-time variable is read while the deployment platform creates the application artifact.
A runtime variable is read while the deployed application is running.
For the Vite frontend:
const apiUrl = import.meta.env.VITE_API_URL;VITE_API_URL is effectively a build-time value. Vite substitutes it into the generated frontend assets. Updating the dashboard variable without rebuilding the site does not change the JavaScript already delivered by the CDN.
For the Express backend:
const databaseUrl = process.env.DATABASE_URL;DATABASE_URL is normally read when the backend process starts or when the database client is initialized. It is a runtime value.
This difference explains a common deployment failure:
- The frontend is built with
VITE_API_URL=https://old-api.example.com. - The developer changes the dashboard value to the new API URL.
- The existing frontend build is restarted without being rebuilt.
- The browser continues calling the old URL.
The fix is to rebuild the frontend so that the generated assets contain the new public configuration.
Render provides separate save options for environment-variable changes. A developer can save without deploying, redeploy an existing build, or rebuild and deploy. The correct choice depends on whether the variable is consumed during the build or only at runtime.
Document this for every important variable instead of guessing during an incident.
Remember That Environment Variables Are Strings
Environment-variable values are usually delivered to application code as strings.
These values:
ENABLE_REMINDERS=false
MAX_DAILY_APPOINTMENTS=50
REQUEST_TIMEOUT_MS=5000arrive as:
"false"
"50"
"5000"Render explicitly documents that its environment-variable values are strings and should be converted by the application when another type is required.
This code is incorrect:
const remindersEnabled = Boolean(process.env.ENABLE_REMINDERS);Boolean("false") evaluates to true because "false" is a non-empty string.
Parse the expected values explicitly:
function parseBoolean(value, fallback = false) {
if (value === undefined) {
return fallback;
}
if (value === "true") {
return true;
}
if (value === "false") {
return false;
}
throw new Error("Expected true or false");
}
const remindersEnabled = parseBoolean(
process.env.ENABLE_REMINDERS,
false
);Parse numeric values with validation:
function requirePositiveInteger(name, fallback) {
const rawValue = process.env[name] ?? String(fallback);
const parsedValue = Number(rawValue);
if (!Number.isInteger(parsedValue) || parsedValue < 1) {
throw new Error(`${name} must be a positive integer`);
}
return parsedValue;
}
const maxDailyAppointments = requirePositiveInteger(
"MAX_DAILY_APPOINTMENTS",
50
);Do not allow an invalid value to silently become NaN, 0, or an unsafe default.
Validate Configuration When the Application Starts
A missing variable should produce a clear startup failure before users begin sending requests.
Avoid spreading unchecked process.env access across the application:
const client = createEmailClient(process.env.EMAIL_API_KEY);Instead, create one configuration module:
function requireEnv(name) {
const value = process.env[name];
if (!value || value.trim() === "") {
throw new Error(`${name} is required`);
}
return value;
}
export const config = {
nodeEnv: process.env.NODE_ENV || "development",
databaseUrl: requireEnv("DATABASE_URL"),
sessionSecret: requireEnv("SESSION_SECRET"),
emailApiKey: requireEnv("EMAIL_API_KEY"),
appBaseUrl: requireEnv("APP_BASE_URL"),
allowedOrigins: requireEnv("ALLOWED_ORIGINS")
.split(",")
.map((value) => value.trim())
.filter(Boolean)
};Other modules import the validated configuration:
import { config } from "./config.js";
const database = createDatabaseClient(config.databaseUrl);This approach provides several benefits:
- Required values are documented in one place.
- Missing configuration stops the process early.
- Type conversion is consistent.
- Tests can target the configuration rules.
- Typographical errors are easier to find.
- Secret values do not need to be printed.
Do not include the value in an error:
throw new Error(
`DATABASE_URL is invalid: ${process.env.DATABASE_URL}`
);A startup error may be captured in a deployment log. Name the variable without printing its content.
Use .env Files Only for Appropriate Local Work
A local .env file is a convenient way to supply development configuration:
DATABASE_URL=postgresql://dev-user:dev-password@localhost:5432/appointments
SESSION_SECRET=local-development-placeholder
EMAIL_API_KEY=local-test-provider-key
APP_BASE_URL=http://localhost:5173
ALLOWED_ORIGINS=http://localhost:5173It should not be committed.
Add local environment files to .gitignore:
.env
.env.*
!.env.exampleThe exception allows a safe template to remain in the repository.
Create .env.example:
DATABASE_URL=postgresql://USER:PASSWORD@HOST:5432/DATABASE
SESSION_SECRET=replace-with-a-random-value
EMAIL_API_KEY=replace-with-provider-key
APP_BASE_URL=http://localhost:5173
ALLOWED_ORIGINS=http://localhost:5173
LOG_LEVEL=infoA useful .env.example contains:
- Every required variable name
- Safe placeholder values
- Comments where the purpose is unclear
- No production hostname that should remain private
- No actual account identifier
- No usable credential
- No copied database connection string
Check whether an environment file is already tracked:
git ls-files | grep -E '(^|/)\.env'On PowerShell:
git ls-files | Select-String '(^|/)\.env'Adding .env to .gitignore does not remove a file that Git already tracks.
A tracked file can be removed from the current index with:
git rm --cached .envThis changes Git tracking and should be reviewed before committing. It also does not erase the file from earlier commits.
Any real credential previously committed should be treated as exposed and rotated.
Do Not Store Production Values in .env.production
A filename does not provide security.
This file is still dangerous when committed:
.env.productionIt may contain the application’s most privileged values and can be copied into:
- Git history
- Repository archives
- developer laptops
- CI build contexts
- Container images
- Support bundles
- AI assistant context
- Deployment logs
Vite supports mode-specific files such as .env.production, and those files can have higher priority than generic .env files. That behavior is useful for non-sensitive build configuration, but it does not make the files private.
For a hosted production service, supply real production values through the platform rather than a committed environment file.
Separate Development, Preview, Staging, and Production Values
Each environment should have its own configuration.
A practical model is:
Development
- Local database
- Test email account
- Local frontend origin
- Development session secret
Preview
- Disposable or isolated data
- Restricted test credentials
- Temporary preview origin
- No production customer access
Staging
- Production-like infrastructure
- Separate database
- Separate credentials
- Controlled team access
Production
- Real production database
- Production credentials
- Public domain
- Strongest access controlsDo not reuse a production database credential in previews merely because it is convenient.
Preview deployments may run code from branches that have not received the same level of review as the production branch. Giving them full production secrets increases the consequences of a vulnerable dependency, malicious contribution, accidental log statement, or AI-generated debug route.
GitHub deployment environments can store separate environment-level secrets and variables, and can apply deployment rules before a workflow accesses a protected environment.
Render projects and environments can similarly scope environment variables and secret files to services within a particular environment.
Even when the platform supports separation, you must configure it deliberately.
Follow Least Privilege
A production application should not receive a more powerful credential than it needs.
For the email service:
- Use a key restricted to the required sending function.
- Restrict it to the correct domain or sender when supported.
- Do not reuse an account-owner key.
- Keep test and production credentials separate.
- Disable unused capabilities.
- Rotate the key when exposure is suspected.
For the database:
- Give the web application only the permissions required for normal requests.
- Consider a separate credential for schema migrations.
- Do not use an unrestricted database-administrator account for every request.
- Restrict external network access.
- Use the provider’s private connection path when available.
For automation:
- Use repository- or environment-scoped credentials.
- Avoid organization-wide secrets when only one project requires access.
- Restrict deployment tokens to the intended service.
- Remove credentials when automation is retired.
Least privilege limits the damage caused by an application bug, exposed log, compromised dependency, or leaked credential.
Avoid Printing Secrets While Debugging
A missing variable often tempts beginners to add:
console.log(process.env);Do not do this in a deployed application.
It can print every environment variable available to the process, including credentials supplied by the platform.
Also avoid:
console.log("Database URL:", process.env.DATABASE_URL);
console.log("Email key:", process.env.EMAIL_API_KEY);Use presence checks:
console.log("Configuration status", {
databaseUrlConfigured: Boolean(process.env.DATABASE_URL),
emailApiKeyConfigured: Boolean(process.env.EMAIL_API_KEY),
appBaseUrlConfigured: Boolean(process.env.APP_BASE_URL)
});Even this should normally be temporary. The preferred production behavior is configuration validation that reports the missing variable name without printing values.
Remember that secrets can also leak through:
- Error objects
- HTTP request headers
- Shell command output
- Database-client errors
- CI workflow debugging
- Screenshots
- Screen recordings
- Support tickets
- Chat conversations
- AI coding prompts
Redaction in a logging platform is helpful, but prevention is more reliable.
Configure the Appointment App on Render
For the sample application, open the backend web service’s Environment section and add:
NODE_ENV=production
DATABASE_URL=<internal PostgreSQL connection URL>
SESSION_SECRET=<new production-only value>
EMAIL_API_KEY=<restricted production or test credential>
APP_BASE_URL=https://appointment-web.onrender.com
ALLOWED_ORIGINS=https://appointment-web.onrender.com
LOG_LEVEL=infoRender stores environment variables outside the repository. It also supports secret files and reusable environment groups for multi-service projects.
The frontend static site should receive only:
VITE_API_URL=https://appointment-api.onrender.comDo not duplicate all backend variables in the frontend service. A static site does not need database credentials or private API keys.
For a future worker service, provide only the variables the worker needs:
DATABASE_URL
EMAIL_API_KEY
LOG_LEVELIt may not need SESSION_SECRET or ALLOWED_ORIGINS.
This per-service scoping reduces unnecessary credential distribution.
Be Careful With Shared Environment Groups
Render environment groups can distribute common variables and secret files to multiple services. This can reduce duplication in an architecture containing a web service, worker, and scheduled job.
For example:
Shared production group:
- DATABASE_URL
- LOG_LEVEL
- EMAIL_API_KEYLink it only to services that require those values.
Do not create one universal group containing every production secret and attach it to every component. The frontend, backend, worker, and cron process have different needs.
A shared group improves consistency but increases the number of services affected by a mistaken change. Document which services consume each value before centralizing it.
Handle Secret Files Separately
Some credentials are multiline files rather than single-line values.
Examples include:
- Private signing keys
- Provider credential documents
- TLS client certificates
- Service-account files
For these, a platform’s secret-file feature may be easier and safer than forcing the content into one environment variable.
Render supports plaintext secret files that are mounted into the service at runtime, including access under /etc/secrets/<filename>. The platform also limits the combined secret-file size for a service or environment group.
The application can read the file:
import fs from "node:fs";
const privateKey = fs.readFileSync(
"/etc/secrets/signing-key.pem",
"utf8"
);Do not copy the file into a public directory, frontend build, image asset folder, or log output.
For Docker-based deployments, review how the platform exposes variables during image builds. Render warns that sensitive values used as Docker build arguments can remain in generated image layers and recommends secret files for sensitive build-time access.
Verify That Frontend Secrets Were Not Exposed
After building the frontend, inspect the generated files.
Search for known variable names:
grep -R "EMAIL_API_KEY" client/dist
grep -R "DATABASE_URL" client/dist
grep -R "SESSION_SECRET" client/distSearching names does not prove that the values are absent. Also search for recognizable fragments of any credential using only a small non-sensitive prefix or identifier.
Do not paste the entire secret into shell history for a search command.
Inspect the deployed app:
- Open the browser developer tools.
- Review the Network panel.
- Inspect loaded JavaScript files.
- Check request URLs and headers.
- Search frontend sources for unexpected configuration.
- Confirm that server credentials are never sent in API responses.
A minified bundle is still public.
You can also test whether the browser code has access to a variable:
console.log(import.meta.env.VITE_API_URL);
console.log(import.meta.env.DATABASE_URL);In a correctly configured Vite app, the public prefixed value may exist, while an unexposed server variable should not.
Remove temporary diagnostic logging before deployment.
Verify Environment Changes Safely
When adding or changing a variable:
- Record the variable name and purpose.
- Confirm which service requires it.
- Confirm whether it is public, internal, or secret.
- Determine whether it is used at build time or runtime.
- Add the value through the correct platform environment.
- Trigger the required rebuild or restart.
- Review deployment logs without printing the value.
- Test the affected feature.
- Test a failure case.
- Record the change in deployment notes.
For VITE_API_URL, rebuild the frontend and confirm the browser calls the new address.
For DATABASE_URL, restart the backend and confirm that:
- The health endpoint succeeds.
- The application can perform a safe database query.
- No migration ran unexpectedly.
- The old database is not receiving new records.
For EMAIL_API_KEY, use a designated test recipient and verify:
- The backend, not the browser, sends the request.
- The expected provider account is used.
- The key does not appear in application logs.
- A failed provider request produces a controlled error.
Rotate Secrets Without Breaking the App
Secret rotation means replacing an existing credential with a new one.
You should rotate a secret when:
- It was committed to Git.
- It appeared in a log or screenshot.
- It was shared in an inappropriate channel.
- A developer or service no longer requires access.
- A third-party provider reports a security issue.
- The application’s access scope changes.
- The credential has reached its planned replacement date.
A safe rotation process depends on whether the external service allows two active credentials at once.
When overlap is supported:
- Create a new credential.
- Give it the minimum required permissions.
- Add it to the staging or preview environment.
- Test the relevant workflow.
- Update production.
- Redeploy or restart the affected service.
- Verify production behavior.
- Disable the old credential.
- Monitor for attempts using the old credential.
- Record the rotation date and owner.
When overlap is not supported, plan a controlled change window and a quick rollback path. Avoid changing multiple unrelated credentials in one deployment.
A secret is not rotated merely because its environment-variable name changed. The value must be replaced at the issuing provider.
Respond to an Exposed Secret
When you discover a real secret in source code, logs, a public bundle, or chat history, assume it may have been copied.
Take these actions:
- Disable or revoke the exposed credential.
- Generate a replacement.
- Update only the environments and services that need it.
- Redeploy affected services.
- Verify the application with the replacement.
- Review provider activity logs where available.
- Search Git history and deployment artifacts for the exposed value.
- Remove the value from current source and configuration.
- Clean history when appropriate.
- document the incident and prevention changes.
Do not delay revocation while trying to determine whether someone definitely used the credential.
Do not publish the exposed value in an issue describing the problem.
Git history rewriting may be required, but it does not replace credential revocation. Existing clones, caches, build artifacts, and copied messages may still contain the original value.
Common Environment-Variable Mistakes
Prefixing a private key with VITE_
Vite exposes prefixed variables to client-side code. The key becomes public.
Move the integration to the server.
Assuming .env means encrypted
A .env file is normally plaintext.
Protect it with file permissions, Git exclusions, device security, and careful handling.
Using one database URL everywhere
Development, staging, previews, and production should not casually share the same database.
Separate them to reduce accidental data modification.
Changing a frontend value without rebuilding
Frontend variables are often substituted during the build.
Create a new frontend build after changing them.
Comparing a string as a boolean
The string "false" is truthy in JavaScript.
Parse accepted values explicitly.
Logging all variables to diagnose one missing setting
This can expose every credential available to the process.
Report only the missing variable name.
Giving every service every secret
A static frontend does not need a database password. A cron service may not need a session secret.
Scope credentials by service.
Copying a local .env directly into production
Local files often contain development URLs, weak placeholders, and credentials for the wrong account.
Review and enter production values individually.
Trusting secret masking completely
Masking can miss transformed, encoded, partial, or reformatted values.
Prevent the secret from entering output in the first place.
AI Prompt for Reviewing Environment Variables
Goal:
Review this application’s environment-variable usage without changing files.
Project:
- React and Vite frontend
- Node.js and Express backend
- PostgreSQL database
- Transactional email provider
- Render deployment
Constraints:
- Do not print or request real secret values.
- Do not expose server credentials to frontend code.
- Treat every VITE_-prefixed value as public.
- Do not add secrets to Git-tracked files.
- Do not change database targets.
- Do not log process.env.
- Do not claim a value is safe based only on its name.
- Identify build-time and runtime variables separately.
Review:
1. Every environment-variable reference
2. Component that consumes each variable
3. Public, internal, or secret classification
4. Build-time or runtime requirement
5. Missing startup validation
6. Unsafe type conversion
7. Frontend exposure risks
8. Environment separation risks
9. Overly broad secret distribution
10. Logging and error-message exposure
11. Required rebuild or restart behavior
12. Rotation and recovery requirements
Expected output:
- Environment-variable inventory
- Findings ranked by severity
- Exact affected files and lines
- Proposed changes without applying them
- Verification steps that do not reveal values
- Questions requiring manual security reviewDo not give the AI assistant real credentials as context. Use placeholders and inspect every proposed change.
Environment-Variable Management Checklist
- Every variable used by the project is documented.
- Each variable has a clear owning component.
- Each variable is classified as public, internal, or secret.
- Build-time and runtime variables are distinguished.
- Frontend-exposed variables contain only public values.
- No database URL is included in frontend code.
- No private API key is included in frontend code.
- No session secret is included in frontend code.
- Local
.envfiles are excluded from Git. .env.examplecontains placeholders rather than real values.- Previously committed credentials have been rotated.
- Development and production use separate credentials.
- Preview deployments do not receive unnecessary production secrets.
- Production values are stored through the deployment platform.
- Each service receives only the variables it requires.
- Required variables are validated at startup.
- Missing-variable errors do not print values.
- Boolean and numeric values are parsed explicitly.
- Frontend variable changes trigger a new build.
- Runtime variable changes trigger the appropriate restart or redeployment.
- Logs do not print complete environment objects.
- Error responses do not reveal configuration.
- Secret files are not copied into public build directories.
- Docker builds do not preserve sensitive build arguments in image layers.
- Credential permissions follow least privilege.
- A secret-rotation procedure is documented.
- Exposed credentials can be revoked quickly.
- Deployment notes record variable names but not values.
- The production frontend bundle has been inspected for accidental secrets.
- Environment changes are followed by controlled verification.
