Join our Newsletter — 33% off our NHI Course
Home› Guides› Ephemeral and Preview Environment Security Guide: Per-Pull-Request Environments…
Guide Software & AI Supply Chain

Ephemeral and Preview Environment Security Guide: Per-Pull-Request Environments Without Exposed Data or Credentials

PDF for newsletter subscribers · free to read online

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 11 November 2026 15 min read
On this page

Per-pull-request preview environments run unreviewed code with real credentials on public URLs, and most teams secure them less carefully than production even though they often hold the same secrets. Review apps, deploy previews and ephemeral namespaces are now a default feature of hosting platforms and CI systems: every pull request gets its own URL, its own build and, too often, a copy of the staging secrets and a database seeded from production. This guide covers the four exposures that matter (secrets reachable by untrusted code, open preview URLs, real data in short-lived copies, and environments that never get torn down), with platform settings, a rollout plan, detection and framework mapping. It is written for platform engineers, DevSecOps teams and security architects who own CI/CD and hosting configuration.

Key takeaways

  • Treat every preview build as untrusted code execution. A pull request, especially from a fork, can change build scripts, tests and dependencies, so anything the build can reach the author can reach.
  • Give previews their own credentials, not staging's. Separate, low-privilege, short-lived identities per preview context remove most of the blast radius; OIDC federation beats stored keys.
  • Preview URLs are public by default on many platforms. Turn on platform authentication for non-production deployments and do not rely on obscure URLs or noindex headers.
  • Never seed previews from production. Use synthetic or masked fixtures; a preview database is a copy of whatever you put in it, on infrastructure with weaker controls.
  • Teardown is a security control. Auto-stop timers, stop jobs and DNS clean-up prevent orphaned environments, forgotten secrets and subdomain takeover.

What makes preview environments different

A preview (or review) environment is a short-lived deployment created automatically for a branch or pull request so reviewers can see the change running. GitLab describes review apps as "a unique environment for each branch or merge request"; Netlify builds a Deploy Preview at deploy-preview-<PR number>--<site>.netlify.app whenever a pull or merge request is opened; Heroku Review Apps, Vercel preview deployments and Kubernetes namespace-per-PR patterns work the same way.

Three properties separate them from ordinary staging:

  • The code is not yet reviewed: the environment exists precisely so that someone can look at unmerged changes. Any secret injected at build or run time is available to that unmerged code.
  • The trigger is external: opening a pull request, sometimes from a fork by anyone on the internet, starts a build. OWASP calls this Public Poisoned Pipeline Execution (3PE) when public repositories run unreviewed pull request code in CI.
  • The volume is high and the lifetime is short: dozens of environments a day, each with a URL, DNS entry, database and credentials, which makes manual governance impossible and orphaned resources likely.

The four exposures

ExposureHow it happensPrimary control
Secrets reachable by untrusted codeBuild or deploy step for a fork PR receives environment variables, cloud keys or a write-scoped token; the PR modifies a build script to print or send themWithhold secrets from untrusted builds; separate privileged workflows; OIDC with narrow trust conditions
Open preview URLsPreview is reachable by anyone with the link; links are posted in PR comments on public repositories, shared in chat and indexedPlatform authentication on all non-production deployments
Real data in a short-lived copyPreview database restored from a production snapshot or pointed at a shared staging database with live recordsSynthetic or masked seed data; no production connection strings
Environments that never dieStop jobs fail or are never defined; DNS records outlive the app; credentials issued per preview never expireAuto-stop, TTL-bound credentials, DNS lifecycle tied to the resource

Keeping secrets away from untrusted code

The most damaging failure is a pipeline that builds an outside contributor's pull request with privileged credentials. The Travis CI secrets exposure (CVE-2021-41077) let forked pull requests read public projects' secure environment variables, and the Ultralytics PyPI compromise started with a GitHub Actions injection in a workflow that handled pull request content with elevated privileges.

GitHub Actions

  • Prefer pull_request over pull_request_target: GitHub's secure use reference says to avoid pull_request_target if it is not necessary, because those workflows may have repository write access and secrets, and they must not check out untrusted code. GitHub Security Lab calls the result of breaking this rule "pwn requests". See the How to Find GitHub Workflows Exposed to pull_request_target Abuse.
  • Split build from deploy: an unprivileged pull_request workflow builds the preview artefact; a separate workflow_run workflow holding deploy credentials consumes it. Security Lab warns that artefacts built from untrusted code are themselves untrusted, so validate them and never execute their contents in the privileged job.
  • Do not treat a label as a gate: "safe to test" labels are a temporary fix, because the author can push a new commit after approval and before the run starts.
  • Set the default GITHUB_TOKEN to read-only and raise permissions per job. Note that actions/checkout persists credentials to disk by default (persist-credentials: true), so later steps can read the token.
  • Use environments with required reviewers for the deploy job: a job cannot access environment secrets until a reviewer approves it.
  • Use OIDC federation for cloud access so the preview deploy job holds no stored cloud keys, and restrict the cloud-side trust policy to the preview environment and repository, not to any branch. See the GitHub Actions OIDC Federation Hardening Guide.
  • Never run fork PR previews on self-hosted runners: GitHub says self-hosted runners should almost never be used for public repositories, because they are not guaranteed to be ephemeral and can be persistently compromised. See the Self-hosted CI/CD Runners and Build Agents.

Hosting platform settings

PlatformSettingSecure choice
NetlifyUntrusted deploys policy (public repositories): Require approval (default), Build without sensitive variables, Build without restrictionsRequire approval, or Build without sensitive variables with "public" values declared for the deploy-preview context. Never Build without restrictions.
HerokuReview app config vars in pipeline settings; fork PR handlingHeroku does not auto-create review apps for fork PRs on public repositories; creating one manually injects every pipeline review app config var, so treat that as running untrusted code with your secrets.
VercelEnvironment variables per environment (Production, Preview, Development)Separate Preview values with no production credentials; protection bypass secrets for automation held only by the test system.
GitLabDynamic environments with environment:name such as review/$CI_COMMIT_REF_SLUGKeep production variables protected and scoped to protected branches and environments; review jobs get their own, lower-privilege variables.

Heroku review apps also illustrate a less obvious identity: they are created with the GitHub token of the user who linked the pipeline. That personal token is a non-human dependency that should be owned and reviewed like any other integration credential.

Controlling who can reach a preview

A preview URL is a production-like copy of unreleased features, sometimes with debug settings, source maps and test accounts. Netlify's documentation is plain that anyone with a preview URL can open it unless access is restricted, and that the X-Robots-Tag: noindex header it adds does not by itself keep previews private.

  • Vercel: Deployment Protection is configured per project with a method (Vercel Authentication, Password Protection, Trusted IPs, or Passport through your identity provider on Enterprise) and a scope. Standard Protection, available on all plans, protects every deployment except production domains. Team defaults can apply protection to new projects automatically. Protected Source Maps gates .map files behind authentication.
  • Netlify: password protection can be required for all Deploy Previews, and project visibility can be set to private on some plans.
  • Self-built previews: put an identity-aware proxy or SSO-backed ingress in front of every preview host, not basic auth with a shared password in the README.

Decision guidance: SSO-backed platform authentication is the default choice because access ends when the person leaves. A shared password suits external reviewers such as agencies, but rotate it per release and never reuse it. IP allow lists help only when reviewers have stable egress addresses. Automation that must reach protected previews (end-to-end tests, visual diff tools) should use the platform's bypass mechanism with a dedicated secret, not a disabled protection setting.

Data: what goes into a preview database

Heroku's review app documentation states that copying production data to test apps is not supported because it risks data leaks or code operating on recent customer data, and recommends seeding with non-production data in a postdeploy script. That is the right default for every platform.

  • Seed from fixtures or synthetic generators committed to the repository, so every preview starts from known, harmless data.
  • If realistic data is essential, use a masked dataset produced by a controlled refresh process, never a raw snapshot. See the Production Data in Non-Production.
  • No shared staging database: pointing previews at a shared staging database lets unreviewed migrations corrupt it and gives every preview the staging database credential.
  • Third-party sandboxes only: payment, email and messaging integrations in previews should use provider test modes and test keys, so a preview cannot charge cards or email customers.

Isolation and lifecycle on Kubernetes and cloud

Teams that build their own previews usually create one Kubernetes namespace or one cloud stack per pull request. Isolation and expiry must be designed in.

  • Namespace guardrails: a ResourceQuota per namespace caps CPU, memory and object counts so a pull request cannot exhaust the cluster. Pods are non-isolated by default, so apply a default-deny NetworkPolicy for ingress and egress and then allow only what the preview needs; this needs a network plugin that enforces NetworkPolicy. See the Kubernetes Namespace Onboarding Checklist.
  • Separate cluster or account: run previews in a non-production cluster and cloud account, so a compromised preview workload cannot reach production service accounts, metadata endpoints or databases.
  • Per-preview identities with a TTL: issue short-lived dynamic credentials (database users, cloud roles) that expire on their own even if teardown fails. See the Dynamic Secrets and Ephemeral Credentials Guide.
  • Automatic stop: GitLab's environment:on_stop runs a stop job when the merge request is merged or the branch deleted, and auto_stop_in (for example 1 week) stops idle review apps. Heroku can destroy review apps after 1, 2, 5, 14 or 30 days of inactivity. Netlify previews stay live until deleted, so clean them up deliberately.
  • DNS lifecycle: a CNAME left pointing at a deleted preview resource is a dangling record that someone else can claim. Microsoft's guidance is to remove DNS entries as part of decommissioning and to prefer records whose lifecycle is tied to the resource (Azure DNS alias records). Heroku notes that predictable review app names can expose apps to subdomain takeover, which is why its default names are random. See the Subdomain Takeover and Dangling DNS Guide.
  • Cookie scope: do not host previews under the production parent domain if production cookies are scoped to it; a hijacked or malicious preview subdomain can then read them. Use a separate registrable domain for previews.

Where identity fits

Every preview pipeline is a chain of non-human identities: the CI token, the deploy credential, the cloud role, the database user created for the preview, the platform integration token and any bypass secret used by test automation. Inventory them as one system, give each an owner, and make sure each is either federated (no stored secret) or short-lived. The privileged identity in this chain is the deploy credential, because it is the one untrusted code most wants. See the CI/CD Pipeline Identity Security Guide.

Rolling out secure previews

  1. Inventory: list every repository with previews enabled, the platform, the trigger (all PRs, forks, labels), the variables and credentials injected, the data source and the teardown method.
  2. Close the fork path first: set fork builds to require approval or to build without sensitive variables; remove pull_request_target workflows that check out PR code.
  3. Turn on access protection: apply SSO-backed protection to all non-production deployments using team defaults, so new projects inherit it. Pilot on one team for a week to find broken test automation, then fix it with bypass secrets.
  4. Split credentials: create preview-only identities with read or sandbox access; move cloud access to OIDC; rotate any staging secret that previews could read.
  5. Replace data sources: move seeding to fixtures or masked datasets; remove production and shared staging connection strings from preview contexts.
  6. Enforce expiry: add stop jobs, auto-stop timers, credential TTLs and DNS clean-up; run a weekly job that lists previews older than the agreed limit.
  7. Test and roll back: each change is a configuration toggle (policy setting, protection scope, variable scope), so record the previous value and revert per project if delivery breaks, while keeping the fork restriction in place.

Small and large organisations: a small team on a hosting platform gets most of the value from three settings: fork approval, Standard Protection or equivalent, and separate preview variables. Large organisations running their own preview infrastructure need a platform team to own the template (namespace guardrails, identity issuance, teardown) so that individual product teams cannot opt out.

Detection and measurement

  • What to log: CI workflow runs triggered by fork pull requests, approvals of untrusted deploys, changes to preview protection settings and variable scopes, cloud role assumptions from preview identities, and access logs on preview hosts.
  • What to alert on: a preview identity calling APIs outside its expected set, egress from preview workloads to unexpected hosts, preview credentials used after the preview was destroyed, and workflow file changes in pull requests from first-time contributors.
MetricTarget direction
Percentage of projects with access protection on all non-production deployments100%
Repositories where fork builds receive sensitive variablesZero
Preview environments older than the agreed limit (for example 14 days)Near zero
Preview contexts holding stored (non-federated, non-expiring) cloud credentialsFalling to zero
Dangling DNS records found in scheduled zone reviewsZero open beyond one day
Preview databases seeded from unmasked production dataZero

Framework mapping

FrameworkReferenceHow it applies
NIST CSF 2.0PR.AA-05, PR.PS-01, PR.IR-01, PR.DS-01Least privilege for preview identities; configuration baselines for preview templates; separation of preview from production networks; protection of seeded data
NIST SP 800-53 Rev. 5AC-6, CM-3, CM-5, IA-5, SC-7, SA-11Least privilege, change control and access restrictions for pipeline configuration, short-lived authenticators, boundary protection, developer testing in controlled environments
ISO/IEC 27001:2022 Annex A8.31, 8.33, 8.25Separation of development, test and production environments; protection of test information; secure development life cycle
CIS Controls v8.1Control 16, Control 3, Control 6Application software security, data protection, access control management
MITRE ATT&CKT1677, T1552.001, T1078.004, T1584.001Poisoned Pipeline Execution; credentials in files; use of cloud accounts obtained from a pipeline; attacker acquiring domains such as abandoned subdomains
OWASP Top 10 CI/CD Security RisksCICD-SEC-4Poisoned Pipeline Execution, including the Public PPE variant

Common failure patterns

  • Staging secrets in the preview context: one environment variable set shared by staging and previews means every pull request can read staging credentials.
  • Fork builds with full variables: "Build without restrictions" or equivalent settings chosen to make contributor previews work, exposing secrets to anyone who opens a PR. The Travis CI exposure shows how quickly this class of flaw is found.
  • Privileged workflows consuming untrusted artefacts: a workflow_run deploy job that extracts and runs a PR's build output in a privileged context.
  • Tokens in uploaded artefacts: preview build artefacts that include .git/config or environment files with live tokens; the ArtiPACKED research found exactly this in public projects.
  • Open preview URLs with test accounts: previews linked from public PR comments with seeded admin logins and debug pages enabled.
  • Teardown that removes the app but not the DNS record or credentials, leaving takeover-ready subdomains and valid database users behind.

Practitioner checklist

  • Set fork and outside-contributor previews to require approval or to build without sensitive variables.
  • Remove pull_request_target workflows that check out or run PR code; split build and deploy.
  • Set default GITHUB_TOKEN permissions to read-only and gate deploy jobs with protected environments.
  • Give previews their own federated or short-lived identities with no production or shared staging access.
  • Enable SSO-backed access protection for all non-production deployments through team defaults.
  • Seed preview databases only from fixtures, synthetic data or a masked dataset.
  • Apply ResourceQuota and default-deny NetworkPolicy to every preview namespace, in a non-production cluster and account.
  • Configure stop jobs, auto-stop timers, credential TTLs and DNS clean-up, and report previews past their limit.
  • Host previews on a separate registrable domain from production cookies.

Standards and references

Related NHI Mgmt Group resources: CI/CD Security Guide · GitHub and GitHub Actions Security Guide · Heroku, Netlify and Render PaaS Security Guide · OWASP CI/CD Top 10 CICD-SEC-4 Poisoned Pipeline Execution Guide

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 11 November 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.