# Can You Run a Security Assessment With Only a Production Environment? — Splitting Zero-Touch Review, Read-Only Checks, and Tests That Need Staging

> No staging, only production? You can still run a security assessment. Split methods into review that never touches production, read-only checks against it, and tests that need staging or written agreement — with a decision table, a rules-of-engagement checklist, safe test accounts, and why backups are no undo button.

- Published: 2026-10-12
- Author: 友田 陽大
- Tags: セキュリティ, 脆弱性診断, OWASP, 発注, Supabase
- URL: https://tomodahinata.com/en/blog/security-audit-production-only-no-staging-guide
- Category: Application-layer security
- Pillar guide: https://tomodahinata.com/en/blog/nextjs-supabase-application-security-guide

## Key points

- Having only a production environment does not rule out a security assessment. Split methods into 'never touches production', 'touches it read-only', and 'needs staging or a written agreement', and the safe scope becomes clear
- Reviewing code, configuration, dependencies, and migrations (the data-free schema and RLS policies) never touches production — and that is where many serious problems, such as broken authorization, are found
- The OWASP ZAP baseline scan is designed not to attack, but its spider still follows links and sends requests. If a GET request changes state anywhere in your app, the spider will trigger it — so limit the scope and the time window
- Active scanning, fuzzing, load testing, and anything that writes data need either a staging environment or a written agreement on targets, time window, and stop conditions. Restoring a backup also erases your customers' writes since that point, so 'we can just roll back' does not hold
- If you do need write tests, temporarily spinning up a data-free test environment can be cheaper than carrying them out carefully in production

---

**The short answer: yes. Even with only a production environment and no staging (a separate pre-production environment for testing), you can run a security assessment.** The key is not to decide "may we test production?" once for everything, but to **split the work method by method — does it touch production, and if so, does it write?**

- **Review that never touches production**: read the source code, configuration, dependencies, and the database schema and access-control policies.
- **Read-only checks against production**: passive scanning that does not attack, header and TLS checks, and read-only authorization checks with test accounts.
- **Tests that need staging or a written agreement**: active scanning, fuzzing, load testing, and anything that writes data.

For a small company or a startup, "we only have production" is perfectly normal. It is not a good reason to put off an assessment. This post turns the three-way split into a decision table, then covers what to put in writing before you send anything at production, how to create test accounts, why backups are not something to lean on, and when it is cheaper to just stand up a test environment.

---

## Decision table: split methods by whether they touch production

**The impact on production varies widely by method, so split them in a table first and pick only what you need.** Rows near the top have the least impact; rows near the bottom need the most preparation.

| Method | Touches production? | Main risk | What you need |
|---|---|---|---|
| Source code review | No | Practically none (only managing who sees the code) | Read access to the repository |
| Configuration review (hosting, auth, env var names) | No (only reading settings screens) | Handing out overly broad admin permissions | A view-only role, or exported screenshots |
| Dependency audit (`npm audit` etc.) | No | Practically none | The lockfile |
| DB schema and policy review | No (reading migrations) | Practically none; misses drift from production | Migrations, or a data-free schema dump |
| Passive scanning (ZAP baseline) | Read-only | The spider hits a GET that changes state / extra traffic | Agreed target URLs and time window |
| Header and TLS checks | Read-only | Practically none (a handful of requests) | Target URLs |
| Authorization checks with test accounts (read-only) | Read-only | Actually seeing another customer's data | Test accounts and a test tenant, written agreement |
| Active scanning (ZAP full scan etc.) | Yes, including writes | Junk data, degraded performance, notifications sent by mistake | Staging, or written agreement plus stop conditions |
| Fuzzing | Yes, including writes | Errors or data corruption from unexpected input | Staging as the rule |
| Load testing | Yes | Outage, spikes in usage-based billing | Staging as the rule |
| Tests that write data (privilege escalation, tampering) | Writes | Changed customer data, side effects you cannot undo | Staging, or backups plus written agreement |

This split follows the reasoning of the US National Institute of Standards and Technology's testing guide, [SP 800-115](https://csrc.nist.gov/pubs/sp/800/115/final). When selecting techniques, it says organizations **should consider whether testing should be performed on production systems or on similarly configured non-production systems**, and lists the factors to weigh: the possible impact on production (a technique likely to cause a denial of service should probably run against a non-production system), the presence of sensitive personal data (use test data instead of real data), and how similarly production and non-production can be configured. That last factor is easy to overlook. The guide itself notes that test and production environments usually differ in practice, and that testing outside production can miss vulnerabilities. So "we only have production" is a limitation, but it is also an advantage: **the environment you examine is the one you actually need to protect.**

---

## ① How far does review that never touches production go?

**Many serious flaws can be found without sending a single request to production.** Broken authorization in particular — who is allowed to see which data — is the kind of problem you find by reading code and policies.

Here is what such a review reads:

- **Source code**: whether APIs and server-side handlers check that an ID in the request (URL parameters, submitted data) belongs to the signed-in user before passing it to the database, and whether admin-only operations actually check for admin.
- **Database schema and access-control policies**: on Supabase, whether the SQL in `supabase/migrations` defines RLS (row-level access control) for every table, whether the conditions narrow rows to the owner or tenant, and whether writes are constrained. If you suspect production has drifted from the migrations, a schema-only dump with no data is enough.
- **Configuration**: the list of hosting environment variable *names* (no values needed), auth settings (email confirmation, password rules, redirect targets for external sign-in), and storage visibility settings.
- **Dependencies**: known vulnerabilities, checked against the lockfile.

The thing to stress is that **because this stage never touches production, it does not directly confirm production's actual state.** If someone changed a policy by hand in the dashboard and it never made it into a migration, the code alone will not tell you. So treat the results of a review as a hypothesis — "with this design, production should behave like this" — and back up only the parts that need it with the read-only checks in ②.

For a Next.js × Supabase stack, where tools are enough and where a human has to read is covered in detail in [What Does a Security Audit Actually Look At — Where Automation Is Enough, and Where an Audit Is Required](/blog/nextjs-supabase-security-audit-scope-when-needed-guide).

---

## ② Checks that touch production read-only

**As long as the requests do not change data, you can check production too.** But "read-only" does not automatically mean "harmless", so here are the caveats for each method.

### Passive scanning: no attacks, but the spider still runs

According to its official documentation, the OWASP ZAP [baseline scan](https://www.zaproxy.org/docs/docker/baseline-scan/) **runs the ZAP spider against the target for 1 minute by default, then waits for passive scanning to complete before reporting.** It performs no actual attacks, so it finishes in a few minutes at most, and the docs say it is **intended to be ideal for CI/CD, even against production sites.** What it finds are problems visible just by observing responses: missing security headers, cookie attributes, information leaks.

There is one caveat. The spider follows the links on each page and sends requests. In its own [description of modes](https://www.zaproxy.org/docs/desktop/start/features/modes/), ZAP lists spidering among "potentially dangerous" operations, alongside active scanning and fuzzing. **If your app performs a logout, a deletion, or an email unsubscribe on a GET request, the spider will trigger it.** If you run it against production, narrow it down:

- Agree on the target URLs, and state explicitly which paths are out of scope (admin screens, redirects to external services, and so on).
- Keep authenticated crawling separate from the authorization checks below, and only do it after deciding it is actually needed.
- Keep the spider duration at its short default, and run it during low-traffic hours.

The counterpart is ZAP's [full scan](https://www.zaproxy.org/docs/docker/full-scan/). Its official docs state plainly that it "does perform actual 'attacks' and can potentially run for a long period of time" — not something to fire at production casually (covered in ③). For how to use ZAP itself, see [How to Do Web-App Vulnerability Assessment — a practical guide with OWASP's methods, ZAP, and SAST](/blog/web-application-vulnerability-assessment-owasp-zap-sast-dast-guide).

### Header and TLS checks

Checking response headers (`Strict-Transport-Security`, `Content-Security-Policy`, `X-Content-Type-Options`, and so on) and the certificate and TLS configuration takes a handful of requests and has almost no impact on production. These are the items most reliably checked in production itself, because CDN or hosting settings can make the actual response differ from the headers you wrote in code.

### Authorization checks with test accounts (read-only)

**When you check for broken authorization (being able to see someone else's data) in production, target only test data you created yourself.** For IDOR (flaws where changing an ID reaches someone else's data), the OWASP Web Security Testing Guide says the best approach is to [have at least two (often more) users](https://owasp.org/www-project-web-security-testing-guide/v42/4-Web_Application_Security_Testing/05-Authorization_Testing/04-Testing_for_Insecure_Direct_Object_References), each owning different objects, and try to reach one user's objects from the other.

In production, three rules apply:

1. **Do not guess IDs or walk through numbers.** If you reach another customer's data, that is itself a data leak. Use only the IDs of data you created under test account B.
2. **Stick to read requests (list and detail views).** Do not try update or delete APIs unless they are covered by the written agreement.
3. **Keep personal data out of your records.** If anything other than test data ever comes back, stop on the spot, do not save the content, and report it (write this into the stop conditions).

How to create the accounts is covered in a later section.

---

## ③ Tests that need staging or a written agreement

**Any test that writes data, sends a large volume of requests, or throws unexpected input is not something to run casually in production.** If you run it at all, either prepare a staging environment or agree in writing on targets, time window, and stop conditions.

- **Active scanning**: sends attack strings into input fields and parameters. When forms get submitted, you may receive a flood of inquiry emails, or end up with dummy orders and accounts.
- **Fuzzing**: sends large amounts of unexpected input. Where exception handling is weak, errors can cascade and data can be corrupted.
- **Load testing**: besides the risk of an outage, bills for usage-based services (serverless functions, databases, email sending, external APIs) can spike.
- **Authorization checks that write**: "Can I update someone else's data?" or "Can a regular user escalate to admin?" sometimes cannot be confirmed without actually writing. By the time the test succeeds, production data has changed.

NIST SP 800-115 likewise says techniques such as penetration testing could lead to loss of availability or exposure of sensitive data, and that a technique likely to cause a denial of service should probably be used against a non-production system. If you must run it in production, restrict it, as the guide recommends, to hours when it least affects operations.

That said, write tests are usually only needed to confirm spots that ① and ② have already flagged as suspicious. There is no need to try everything in production from the start.

---

## What to put in writing before you touch production

**For any assessment that sends requests to production, write down what, when, from where, and how far before anything is sent, and have the accountable people on both sides confirm it.** NIST SP 800-115 provides a template for this document in an appendix, calling it the Rules of Engagement (ROE). Adapted for a small web app, its items look like this:

- [ ] **Target URLs and APIs**: the domains and paths that may be tested. **Also list what is out of scope (the exclude list).** Admin screens on the same domain and external payment or email services are usually out of scope.
- [ ] **Date and time window**: start and end. Pick low-traffic hours.
- [ ] **Source IP addresses**: where the tester will send requests from. You need this so monitoring and logs can tell the assessment apart from a real attack.
- [ ] **Test accounts**: the list of accounts and tenants, and each one's role.
- [ ] **Permitted and prohibited actions**: "read-only", "no form submissions", "no file uploads", and so on. List the tools that may be used too.
- [ ] **Stop conditions**: a rising error rate, slow responses, any non-test data becoming visible. Include how testing resumes after a stop.
- [ ] **Contacts if something goes wrong**: the people on both sides, and a channel that works at night and on weekends.
- [ ] **Data handling**: where information from the assessment (vulnerabilities found, recorded responses) is stored, who may see it, and when it is destroyed. NIST SP 800-115 asks that test results be treated as sensitive, since they identify vulnerabilities an adversary could exploit.
- [ ] **Reporting format and timing**: is a critical finding reported immediately, or bundled into the final report?
- [ ] **Signatures**: the guide asks that, at a minimum, the test team leader and the organization's senior management sign to show they understand the scope and boundaries.

Even when your own developers are checking your own app, it is worth writing a short version of this list. If there is a record of who sent what and when, then when an incident happens you can quickly tell whether it was the assessment or a real attack. When you engage an outside party, testing without clear permission from whoever manages the system can be treated as unauthorized access. Your cloud providers may also have their own policies on penetration testing, so check those as well.

---

## Creating test accounts and a test tenant safely

**Create test accounts through the same flow as real customers, make them distinguishable from real customers, and disable them when you are done.** Accounts created by editing the admin console or the database directly end up in a different state from real users, which undermines the check.

The essentials:

- **Use the same sign-up flow as real users.** If sign-up is invite-only, start from an invite. That way you test the registration flow at the same time.
- **Use email addresses the company controls.** Not the tester's personal address, but addresses you can shut off afterwards.
- **Prepare the minimum combination needed to check authorization.** Two regular users in one tenant and one in another. If you have roles (admin, editor, viewer, and so on), one user per role you want to check. This applies WSTG's "at least two users" to both the tenant boundary and the role boundary.
- **Put in test data only.** Do not copy real customer names or contact details; use values that are obviously fictitious.
- **Keep a list.** Record the IDs of the accounts and tenants you created in the written list, and exclude them from analytics, billing, and email campaigns, so test data does not leak into revenue or user counts.
- **Do not hand admin accounts to the tester.** If you need to check what an admin can do, create a dedicated account for that and use it for a limited period.
- **Disable them when you are done.** On the day the assessment ends, disable the accounts, delete the test data, and record that you did.

Note that creating test accounts is itself a write to production. But it is the same operation your real users perform every day, carried out under your control — entirely different in nature from a testing tool performing writes nobody can predict.

---

## Before any write test: backups do not guarantee "we can just roll back"

**A backup is a last-resort safety net, not a way to undo only the data a test wrote.** If you run write tests in production, confirm that backups exist and, before anything else, understand what you would lose if you had to restore.

Taking Supabase as the example, its [official documentation](https://supabase.com/docs/guides/platform/backups) says:

- Projects on paid plans are backed up automatically every day. For finer-grained recovery, Point-in-Time Recovery (PITR: restore to any point with up to seconds of granularity) can be enabled as a paid add-on. Free-plan projects are advised to export regularly with the CLI's `db dump` command and keep off-site backups.
- **The project is inaccessible while a restore runs, and the downtime grows with the size of the database.**
- **Database backups do not include the files you store in Storage.** The database only holds metadata about those files.

What this tells you is that a restore rolls the *whole project* back to an earlier point. If real customers signed up or placed orders after the test started, those disappear too when you roll back. So before running write tests in production, confirm at least the following:

1. How far back the latest backup (or PITR) lets you restore.
2. How long a restore is expected to take, and whether everyone involved accepts the service being down during that time.
3. Whether there is a procedure for **deleting the test data individually**, rather than restoring.

The third one is what really matters. Treat restoring from backup as the last resort for when individual deletion fails.

---

## Rate limits and WAFs: block the assessment's traffic, or let it through?

**If you have rate limiting or a WAF (a layer that blocks malicious-looking requests), decide whether to let the assessment's traffic through based on what you want to learn, and write that decision down.** Which choice is right depends on the question you are asking.

- **To see flaws in the application itself**: add only the tester's source IP addresses to the allowlist, and only for the duration of the assessment. If the WAF keeps blocking, testing stops in front of the WAF and you never see the flaws left in the app. A WAF is just one layer of defense and can be bypassed, so you should know about the app's own flaws.
- **To see your actual defensive posture**: keep the WAF and rate limits on. This shows you what an attacker sees.

If you allowlist, **do not switch off the WAF or the rate limiting itself**, **limit the exception to the tester's IPs and the agreed period**, and **record that you removed it afterwards**. A temporary exception that someone forgets to remove is a common accident.

Passive scans and read-only checks also add requests. Agree in advance on spacing out the tool's requests so that rate limiting does not end up blocking legitimate users too.

---

## Sometimes "just spin up staging" is the cheaper answer

**If you need write tests, standing up a test environment just for the assessment and deleting it afterwards can be cheaper than running those tests carefully in production.** Think of "keeping an environment forever" and "standing one up temporarily" as two different things.

On a Supabase and Vercel stack, the options are:

- **Supabase branches**: per the [official docs](https://supabase.com/docs/guides/deployment/branching), each branch is a separate environment with its own Supabase instance and API credentials, built by applying your production migrations. **By default, a new branch starts with none of your production data or storage objects**; you populate it with a seed file. Preview branches are deleted automatically when the pull request is merged or closed.
- **Vercel preview deployments**: [Vercel](https://vercel.com/docs/deployments/environments) provides a Preview environment alongside Production out of the box, and pushing to a branch other than the production branch creates a preview deployment. If the preview environment variables point at the branch's Supabase instance, you have a test environment disconnected from production. Conversely, **if the preview environment variables still point at the production database, "testing on preview" is writing to production.** Always check where those variables point before testing.
- **A local environment**: Supabase's [local development](https://supabase.com/docs/guides/local-development) setup runs the database, auth, and the rest on your machine with Docker. For checks that never need to be exposed publicly, that may be all you need.

Costs depend on your plan and how long things run, so this post does not quote figures. What you should compare is **the cost of a temporary environment** against **the preparation needed to run write tests in production (written agreement, backup checks, an individual-deletion procedure, someone on standby) plus the loss if something goes wrong.** If your migrations live in the repository, a test environment is easy to reproduce and the comparison tends to favor standing one up. On the other hand, for an app whose tables were created by hand in the dashboard with no migrations, building an environment identical to production is a major job in itself. In that case, exporting the schema as migrations is an improvement worth making before any assessment.

Even when you test in a separate environment, as SP 800-115 points out, differences from production can make you miss vulnerabilities. Cross-check results from the test environment against production's settings (environment variable names, auth settings, storage visibility).

---

## Summary: with only production, go in this order

To recap:

- **You can run an assessment with only production.** Split methods into "never touches production", "touches it read-only", and "needs staging or a written agreement", and the scope you can cover without breaking production becomes clear.
- **Start with review that never touches production.** Reading code, configuration, dependencies, and the database schema and policies is enough to find many serious problems, including broken authorization.
- **Keep read-only checks against production narrowly scoped.** Passive scanning does not attack, but the spider still runs. Authorization checks target only test data you created yourself.
- **Put the rules of engagement in writing before sending requests to production.** Targets, time window, source IPs, test accounts, stop conditions, contacts, data handling, signatures.
- **Write tests go to staging or happen under written agreement.** Restoring from backup also erases customers' writes, so "we can just roll back" does not hold. Temporarily spinning up a test environment can be the cheaper path.

If you want an outside party to assess an app that only has production, you can talk to me about a [security assessment and audit](/security-audit) that starts with review that never touches production. How far the work touches production is agreed in writing before anything begins. If you are on Next.js and Supabase and want to go deep on RLS and tenant isolation, the [Next.js × Supabase specialist security audit](/aegis/audit) is the better fit.
