Baseline
Applies when: Writing test fixtures, seed data, illustrative doc/comment records, error messages, logs, telemetry, screenshots or recordings shared outside the org, migrations/ETL between environments, or LLM prompts, fine-tuning sets, and RAG corpora.
Always:
- Use reserved test ranges for identifiers that have one (
*@example.com, the 555-01xx phone block, RFC 5737 IPs, provider test card numbers, SSA-reserved SSN ranges) instead of a real-looking value.
- Generate names, addresses, and other free-form identifiers with a seeded synthetic generator (
faker, @faker-js/faker, etc.), not free-form invention.
- Refuse to copy production rows into development, staging, demo, or test environments, including "just one row" or hash-based "anonymization."
- Keep emails, phone numbers, full names, addresses, government IDs, DOB, and payment/bank numbers out of logs, errors, and telemetry; identify by internal ID instead.
- Use synthetic test accounts for anything leaving the organization (demos, screenshots, recordings, shared transcripts).
- Screen data going into an LLM prompt, fine-tuning set, or RAG corpus with a PII/PHI/PCI scrubber before ingestion.
Never:
- A real, or real-looking, email, phone number, SSN, or card number in a fixture, comment, or doc example.
- Copying production rows into a lower environment, including a single row "to reproduce a bug."
- Hashing names or emails and calling it anonymization.
- Logging or erroring with a user's email, phone, full name, address, government ID, or payment/bank number.
- Recording or screenshotting the production app against a real customer account.
Open the full component before acting: it has the examples, the remediation steps, and the detection patterns.
Why
Production data carries legal, contractual, and operational
obligations that test environments are not designed to honor.
Every time a real customer record appears in a fixture file, a
comment, a screenshot, a chat transcript, or a debug log, the
organization picks up the same obligations for that copy as for
the original — without the controls that protect the original.
The failures this skill blocks share a common shape: a real
customer's data ends up somewhere the customer's data was never
authorized to be.
- A test fixture committed to git uses a real customer's email,
phone, or address. The repository becomes a partial copy of the
customer database, retained for the life of the project.
- A debug log includes a real request body — full names, SSN-like
identifiers, payment-card-shaped numbers — and the log is
shipped to a third-party observability vendor.
- A demo recording shows the production app with a real customer
account loaded. The recording is shared on a public marketing
page.
- "Just for testing," a developer pulls a row from the production
users table into a local SQLite database. The local database
is now production data without production controls.
- An error message embeds the user's email or phone for debugging.
The error is logged, indexed, and surfaced in an analytics
dashboard a customer-success team can search.
This is a tractable problem: every domain that uses real
identifiers has a reserved test range or synthetic-data tool. The
agent's job is to default to the synthetic option and refuse the
real one.
When to apply
Apply this skill before the agent writes, recommends, or
commits any of the following:
- Test fixtures, factories, seed files, demo data,
db/seeds.rb,
fixtures/, tests/fixtures/, __tests__/data/, or any other
data file consumed by an automated test or local dev environment.
- Code comments, docstrings, README examples, or documentation
that includes an illustrative user record.
- Error messages, log lines, telemetry payloads, or anything
shipped to an observability vendor.
- Screenshots, screen recordings, marketing assets, or anything
shared outside the organization.
- Migration scripts, ETL pipelines, or anything moving rows
between environments — particularly
prod → anything-else.
- LLM prompts, fine-tuning datasets, evaluation suites, or RAG
ingestion corpora.
- Bug-report templates and incident-response artifacts where
reproducers might include real data.
This skill is the data-content counterpart to database-safety
(operations on rows), secrets-management (credential lifecycle),
and secret-blocking (credential patterns). All four apply
together — a fixture that contains both a real email and a real
API key fails this skill and secret-blocking.
Rules
Rule 1 — Test identifiers use reserved test ranges, never real values
For every field that has a reserved test range, the agent uses
that range by default:
| Field |
Reserved range |
Why |
| Email |
*@example.com, *@example.org, *@example.net |
RFC 2606 — reserved for documentation |
| Phone (US) |
555-01xx block (e.g., +1-415-555-0100) |
Reserved for fiction; never assigned |
| Domain |
example.com, example.org, example.net, *.test, *.invalid |
RFC 2606 — reserved |
| IPv4 |
192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24 |
RFC 5737 — reserved for documentation |
| IPv6 |
2001:db8::/32 |
RFC 3849 — reserved for documentation |
| Credit card |
Provider test PANs (Stripe 4242 4242 4242 4242, etc.) |
Issuer test ranges — never authorize |
| SSN (US) |
000-00-0000 through 000-99-9999, 666-*, 9xx-* |
SSA-reserved ranges (never issued) |
Real values for any of these fields are prohibited even if the
agent believes the value is fictional. If the agent generates an
email address that "looks made up," it goes to example.com. If a
phone number is needed and the agent doesn't know whether the
number is real, the agent uses the 555-01xx block.
Rule 2 — Synthetic data comes from a generator, not from imagination
For names, addresses, dates of birth, and other identifiers
without a single reserved range, the agent uses a deterministic
synthetic-data generator rather than free-form invention:
| Language |
Generator |
| Python |
faker |
| Node.js |
@faker-js/faker |
| Ruby |
faker |
| Go |
gofakeit / go-faker/faker |
| Java |
java-faker / datafaker |
| .NET |
Bogus |
Generators are preferred because they:
- Produce values flagged as obviously synthetic by tooling
(downstream consumers can detect "this is faker output").
- Avoid the small but non-zero chance of "inventing" a real
person's name + DOB combination.
- Make fixtures reproducible across runs when seeded.
Rule 3 — No production data is copied into non-production environments
Direct copying of production rows into development, staging, demo,
test, or any environment with lower controls is prohibited. This
includes:
pg_dump prod | psql staging
SELECT * FROM users INTO OUTFILE followed by import elsewhere.
- A custom script that "anonymizes" production data by hashing
names and emails (hashing is not anonymization — it preserves
joinability and is reversible for low-entropy fields).
- Pulling a single row from the production database "just to
reproduce a bug locally."
When realistic test data is needed, the agent uses the team's
documented subset/synthetic pipeline. If the team doesn't have
one, the agent surfaces that as a gap rather than silently
copying.
The only exception is a tightly-scoped, audited refresh from
production into a separate environment specifically designed
to inherit production controls (a "prod-mirror" staging
environment with the same SOC2/HIPAA/PCI scope). That refresh is
not run ad-hoc by the agent.
Rule 4 — Errors and logs do not contain user-identifying data
Application logs, error messages, and telemetry payloads must not
contain:
- Email addresses (use the user ID instead).
- Phone numbers.
- Full names (first + last together).
- Postal addresses.
- Government identifiers (SSN, national ID, passport number,
driver's license).
- Date of birth.
- Payment card numbers (full PAN), bank account numbers, routing
numbers.
- Health information (diagnoses, medications, procedure codes).
- Precise geolocation (street-level lat/long).
- Authentication artifacts (cross-references
secrets-management).
Where debugging requires correlating a log entry to a user, the
agent uses a stable internal identifier (user_id, account_id,
request_id, trace_id) and the consumer of the log resolves
the identifier to a user through an authenticated lookup if and
when needed.
For inspectability without exposure, the agent emits the shape
of the data ("email length 27, domain corp.example.com") rather
than the data itself.
Rule 5 — Demos, recordings, and shared transcripts use synthetic accounts
Anything that leaves the organization's controlled environment —
demo videos, screenshots in blog posts, screen-shares with
prospects, training materials, conference talks, support
transcripts shared with vendors — uses synthetic test accounts
populated with synthetic data.
The agent does not record, screenshot, or share an interaction
with the production app loaded against a real customer account.
If asked to capture a flow that requires a logged-in user, the
agent first switches to a documented test account.
Rule 6 — LLM prompts, fine-tuning sets, and RAG corpora are screened
Data ingested into a model — used as few-shot examples in a
prompt, included in a fine-tuning dataset, or chunked into a
vector store for RAG — is screened for PII/PHI/PCI before it goes
in. Once data is inside a model's training set or a vector index,
it cannot be reliably removed.
The screening pass:
- Strips email addresses, phone numbers, government identifiers,
payment card numbers using a deterministic scrubber (Microsoft
Presidio, AWS Comprehend Detect PII, Google DLP, or an internal
equivalent).
- Replaces names with role tokens (
<USER>, <AGENT>) where the
identity is not load-bearing.
- Logs the scrubbing pass (what was redacted, how many tokens,
which file) so a future audit can verify the input went through
the scrubber.
The agent does not include raw customer transcripts, raw support
tickets, or raw email threads in a prompt or training set without
this pass running first.
Negative examples
# ❌ Real-looking PII in a fixture
USERS = [
{"name": "John Smith", "email": "jsmith@gmail.com", "phone": "415-555-1234"},
{"name": "Jane Doe", "email": "jane@acme.com", "ssn": "123-45-6789"},
]
# ❌ Real credit card number, even "for testing"
CARD = "4532 7654 3210 9876"
// ❌ Real-feeling email in a code comment / docstring
// Example: send a notification to john.smith@gmail.com
# ❌ Logging the full user object on error
log.error(f"signup failed for user={user.__dict__}")
# user contains email, phone, dob, address
# ❌ Error message embeds the email
raise ValueError(f"email {email} is already registered")
# ❌ Copying production rows into local dev
pg_dump $PROD_URL -t users --data-only | psql $DEV_URL
# ❌ "Just one row" copy
psql $PROD_URL -c "COPY (SELECT * FROM orders WHERE id = 12345) TO STDOUT" \
| psql $DEV_URL -c "COPY orders FROM STDIN"
# ❌ Hash-as-anonymization (still reversible/joinable)
df["email"] = df["email"].apply(lambda e: hashlib.sha256(e.encode()).hexdigest())
df.to_csv("dev_users.csv")
# ❌ Real support transcript embedded in a fine-tuning JSONL
{"messages": [
{"role": "user", "content": "hi this is jane.doe@realco.com, my order #4521 ..."},
{"role": "assistant", "content": "Hi Jane, I see your order ..."}
]}
# ❌ Screen recording of the production app with a real customer loaded
# (no code snippet — surfaces as a policy violation in review)
Remediation
Test fixture with synthetic data (Python)
# ✅ Synthetic and obviously so
from faker import Faker
fake = Faker()
Faker.seed(42) # reproducible
USERS = [
{
"id": i,
"name": fake.name(),
"email": fake.email(), # ends in @example.com etc.
"phone": f"+1-415-555-{i:04d}", # reserved 555-01xx block
"address": fake.address(),
"dob": fake.date_of_birth().isoformat(),
}
for i in range(100)
]
Provider test data for payments
# ✅ Stripe test card numbers — never authorize real charges
STRIPE_TEST_CARDS = {
"visa_success": "4242424242424242",
"visa_declined": "4000000000000002",
"visa_3ds": "4000002500003155",
# See https://docs.stripe.com/testing for the full set
}
Logging with user_id, not email
# ✅ Stable internal identifier, no PII
log.info("signup completed", extra={"user_id": user.id, "tenant_id": user.tenant_id})
# ✅ Shape of the input on validation failure, not the input itself
log.warn("email validation failed",
extra={"length": len(email), "has_at": "@" in email})
Error messages without PII
# ✅ User-friendly message, no PII in the exception
raise BusinessError(
code="email_already_registered",
message="This email address is already registered.",
# No email in the payload — the API client already has it
)
Synthetic-data pipeline (subset + scrubbed)
1. Subset:
- SELECT ~1% of rows from each table, scoped to a small set of
synthetic tenant IDs created in production specifically for
this purpose (or to a documented internal-test tenant).
2. Scrub:
- Run the subset through Presidio / Comprehend / DLP.
- Replace emails with `<id>@example.com`, phones with
`+1-415-555-NNNN`, names with faker output, postal addresses
with faker output, payment card numbers with Stripe test
PANs.
3. Verify:
- Sample 1% of the scrubbed output. Confirm no real customer
identifier survives.
4. Ship:
- Materialize as a versioned dataset (S3 + manifest).
- Refresh on a documented cadence.
LLM input scrubbing
# ✅ Presidio scrub before any prompt that includes user content
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine
analyzer = AnalyzerEngine()
anonymizer = AnonymizerEngine()
def scrub_for_llm(text: str) -> tuple[str, list[dict]]:
results = analyzer.analyze(text=text, language="en")
anonymized = anonymizer.anonymize(text=text, analyzer_results=results)
return anonymized.text, [
{"entity": r.entity_type, "start": r.start, "end": r.end}
for r in results
]
scrubbed, log = scrub_for_llm(user_message)
# log_to_audit_trail(scrubbed=scrubbed, redactions=log)
prompt_llm(scrubbed)
Bug reproducer using synthetic data
# ✅ Reproduce the bug with faker-generated data rather than the
# real row, then attach the reproducer (not the row) to the ticket
def reproduce_bug_12345():
user = User(
email=fake.email(),
name=fake.name(),
signup_at=datetime(2026, 5, 17, 14, 32),
)
# exercise the failing code path
process_signup(user)
Production detection heuristics
Treat the data context as production-class (escalating the
protocol) when any of these match:
- The source table, file, or dataset name contains
users,
accounts, customers, members, patients, subscribers,
payments, invoices, transactions, pii_*, phi_*,
pci_*.
- The source database, S3 bucket, or storage path contains
prod, production, live, customer, tenant, or a
customer/tenant identifier.
- The fields involved include email, phone, name, address, DOB,
SSN/national ID, card number, bank account, diagnosis code,
medication, prescription number, geolocation, or government
identifier.
- The destination is anything outside the production environment
(dev, staging, demo, test, marketing, vendor, support, AI/ML
training).
If any signal matches, all six rules apply without exception, and
Rule 3 (no production-to-non-production copy) becomes a hard
refusal — the agent does not propose, generate, or run a copy
operation even if the user frames it as a one-off.
References
1---2name: pii-and-test-data3description: Block real customer data from appearing in test fixtures, code comments, documentation, debug output, or shared transcripts. Require synthetic generators (`faker`, `@faker-js/faker`, provider test cards), reserved test ranges (555 phone numbers, `@example.com` emails, RFC 5737 IPs), and redaction of PII/PHI/PCI from logs and error messages. Refuse to copy production rows into development environments under any framing.4license: MIT5---67## Baseline89**Applies when:** Writing test fixtures, seed data, illustrative doc/comment records, error messages, logs, telemetry, screenshots or recordings shared outside the org, migrations/ETL between environments, or LLM prompts, fine-tuning sets, and RAG corpora.1011**Always:**12- Use reserved test ranges for identifiers that have one (`*@example.com`, the `555-01xx` phone block, RFC 5737 IPs, provider test card numbers, SSA-reserved SSN ranges) instead of a real-looking value.13- Generate names, addresses, and other free-form identifiers with a seeded synthetic generator (`faker`, `@faker-js/faker`, etc.), not free-form invention.14- Refuse to copy production rows into development, staging, demo, or test environments, including "just one row" or hash-based "anonymization."15- Keep emails, phone numbers, full names, addresses, government IDs, DOB, and payment/bank numbers out of logs, errors, and telemetry; identify by internal ID instead.16- Use synthetic test accounts for anything leaving the organization (demos, screenshots, recordings, shared transcripts).17- Screen data going into an LLM prompt, fine-tuning set, or RAG corpus with a PII/PHI/PCI scrubber before ingestion.1819**Never:**20- A real, or real-looking, email, phone number, SSN, or card number in a fixture, comment, or doc example.21- Copying production rows into a lower environment, including a single row "to reproduce a bug."22- Hashing names or emails and calling it anonymization.23- Logging or erroring with a user's email, phone, full name, address, government ID, or payment/bank number.24- Recording or screenshotting the production app against a real customer account.2526Open the full component before acting: it has the examples, the remediation steps, and the detection patterns.2728## Why2930Production data carries legal, contractual, and operational31obligations that test environments are not designed to honor.32Every time a real customer record appears in a fixture file, a33comment, a screenshot, a chat transcript, or a debug log, the34organization picks up the same obligations for that copy as for35the original — without the controls that protect the original.3637The failures this skill blocks share a common shape: **a real38customer's data ends up somewhere the customer's data was never39authorized to be**.4041- A test fixture committed to git uses a real customer's email,42 phone, or address. The repository becomes a partial copy of the43 customer database, retained for the life of the project.44- A debug log includes a real request body — full names, SSN-like45 identifiers, payment-card-shaped numbers — and the log is46 shipped to a third-party observability vendor.47- A demo recording shows the production app with a real customer48 account loaded. The recording is shared on a public marketing49 page.50- "Just for testing," a developer pulls a row from the production51 `users` table into a local SQLite database. The local database52 is now production data without production controls.53- An error message embeds the user's email or phone for debugging.54 The error is logged, indexed, and surfaced in an analytics55 dashboard a customer-success team can search.5657This is a tractable problem: every domain that uses real58identifiers has a reserved test range or synthetic-data tool. The59agent's job is to default to the synthetic option and refuse the60real one.6162## When to apply6364Apply this skill **before** the agent writes, recommends, or65commits any of the following:6667- Test fixtures, factories, seed files, demo data, `db/seeds.rb`,68 `fixtures/`, `tests/fixtures/`, `__tests__/data/`, or any other69 data file consumed by an automated test or local dev environment.70- Code comments, docstrings, README examples, or documentation71 that includes an illustrative user record.72- Error messages, log lines, telemetry payloads, or anything73 shipped to an observability vendor.74- Screenshots, screen recordings, marketing assets, or anything75 shared outside the organization.76- Migration scripts, ETL pipelines, or anything moving rows77 between environments — particularly `prod` → anything-else.78- LLM prompts, fine-tuning datasets, evaluation suites, or RAG79 ingestion corpora.80- Bug-report templates and incident-response artifacts where81 reproducers might include real data.8283This skill is the data-content counterpart to `database-safety`84(operations on rows), `secrets-management` (credential lifecycle),85and `secret-blocking` (credential patterns). All four apply86together — a fixture that contains both a real email and a real87API key fails this skill *and* `secret-blocking`.8889## Rules9091### Rule 1 — Test identifiers use reserved test ranges, never real values9293For every field that has a reserved test range, the agent uses94that range by default:9596| Field | Reserved range | Why |97|---|---|---|98| Email | `*@example.com`, `*@example.org`, `*@example.net` | RFC 2606 — reserved for documentation |99| Phone (US) | `555-01xx` block (e.g., `+1-415-555-0100`) | Reserved for fiction; never assigned |100| Domain | `example.com`, `example.org`, `example.net`, `*.test`, `*.invalid` | RFC 2606 — reserved |101| IPv4 | `192.0.2.0/24`, `198.51.100.0/24`, `203.0.113.0/24` | RFC 5737 — reserved for documentation |102| IPv6 | `2001:db8::/32` | RFC 3849 — reserved for documentation |103| Credit card | Provider test PANs (Stripe `4242 4242 4242 4242`, etc.) | Issuer test ranges — never authorize |104| SSN (US) | `000-00-0000` through `000-99-9999`, `666-*`, `9xx-*` | SSA-reserved ranges (never issued) |105106Real values for any of these fields are prohibited even if the107agent believes the value is fictional. If the agent generates an108email address that "looks made up," it goes to `example.com`. If a109phone number is needed and the agent doesn't know whether the110number is real, the agent uses the `555-01xx` block.111112### Rule 2 — Synthetic data comes from a generator, not from imagination113114For names, addresses, dates of birth, and other identifiers115without a single reserved range, the agent uses a deterministic116synthetic-data generator rather than free-form invention:117118| Language | Generator |119|---|---|120| Python | `faker` |121| Node.js | `@faker-js/faker` |122| Ruby | `faker` |123| Go | `gofakeit` / `go-faker/faker` |124| Java | `java-faker` / `datafaker` |125| .NET | `Bogus` |126127Generators are preferred because they:128129- Produce values flagged as obviously synthetic by tooling130 (downstream consumers can detect "this is faker output").131- Avoid the small but non-zero chance of "inventing" a real132 person's name + DOB combination.133- Make fixtures reproducible across runs when seeded.134135### Rule 3 — No production data is copied into non-production environments136137Direct copying of production rows into development, staging, demo,138test, or any environment with lower controls is prohibited. This139includes:140141- `pg_dump prod | psql staging`142- `SELECT * FROM users INTO OUTFILE` followed by import elsewhere.143- A custom script that "anonymizes" production data by hashing144 names and emails (hashing is not anonymization — it preserves145 joinability and is reversible for low-entropy fields).146- Pulling a single row from the production database "just to147 reproduce a bug locally."148149When realistic test data is needed, the agent uses the team's150documented subset/synthetic pipeline. If the team doesn't have151one, the agent surfaces that as a gap rather than silently152copying.153154The only exception is a tightly-scoped, audited refresh from155production into a separate environment **specifically designed156to inherit production controls** (a "prod-mirror" staging157environment with the same SOC2/HIPAA/PCI scope). That refresh is158not run ad-hoc by the agent.159160### Rule 4 — Errors and logs do not contain user-identifying data161162Application logs, error messages, and telemetry payloads must not163contain:164165- Email addresses (use the user ID instead).166- Phone numbers.167- Full names (first + last together).168- Postal addresses.169- Government identifiers (SSN, national ID, passport number,170 driver's license).171- Date of birth.172- Payment card numbers (full PAN), bank account numbers, routing173 numbers.174- Health information (diagnoses, medications, procedure codes).175- Precise geolocation (street-level lat/long).176- Authentication artifacts (cross-references `secrets-management`).177178Where debugging requires correlating a log entry to a user, the179agent uses a stable internal identifier (`user_id`, `account_id`,180`request_id`, `trace_id`) and the consumer of the log resolves181the identifier to a user through an authenticated lookup if and182when needed.183184For inspectability without exposure, the agent emits the **shape**185of the data ("email length 27, domain `corp.example.com`") rather186than the data itself.187188### Rule 5 — Demos, recordings, and shared transcripts use synthetic accounts189190Anything that leaves the organization's controlled environment —191demo videos, screenshots in blog posts, screen-shares with192prospects, training materials, conference talks, support193transcripts shared with vendors — uses synthetic test accounts194populated with synthetic data.195196The agent does not record, screenshot, or share an interaction197with the production app loaded against a real customer account.198If asked to capture a flow that requires a logged-in user, the199agent first switches to a documented test account.200201### Rule 6 — LLM prompts, fine-tuning sets, and RAG corpora are screened202203Data ingested into a model — used as few-shot examples in a204prompt, included in a fine-tuning dataset, or chunked into a205vector store for RAG — is screened for PII/PHI/PCI before it goes206in. Once data is inside a model's training set or a vector index,207it cannot be reliably removed.208209The screening pass:210211- Strips email addresses, phone numbers, government identifiers,212 payment card numbers using a deterministic scrubber (Microsoft213 Presidio, AWS Comprehend Detect PII, Google DLP, or an internal214 equivalent).215- Replaces names with role tokens (`<USER>`, `<AGENT>`) where the216 identity is not load-bearing.217- Logs the scrubbing pass (what was redacted, how many tokens,218 which file) so a future audit can verify the input went through219 the scrubber.220221The agent does not include raw customer transcripts, raw support222tickets, or raw email threads in a prompt or training set without223this pass running first.224225## Negative examples226227```python228# ❌ Real-looking PII in a fixture229USERS = [230 {"name": "John Smith", "email": "jsmith@gmail.com", "phone": "415-555-1234"},231 {"name": "Jane Doe", "email": "jane@acme.com", "ssn": "123-45-6789"},232]233```234235```python236# ❌ Real credit card number, even "for testing"237CARD = "4532 7654 3210 9876"238```239240```javascript241// ❌ Real-feeling email in a code comment / docstring242// Example: send a notification to john.smith@gmail.com243```244245```python246# ❌ Logging the full user object on error247log.error(f"signup failed for user={user.__dict__}")248# user contains email, phone, dob, address249```250251```python252# ❌ Error message embeds the email253raise ValueError(f"email {email} is already registered")254```255256```bash257# ❌ Copying production rows into local dev258pg_dump $PROD_URL -t users --data-only | psql $DEV_URL259260# ❌ "Just one row" copy261psql $PROD_URL -c "COPY (SELECT * FROM orders WHERE id = 12345) TO STDOUT" \262 | psql $DEV_URL -c "COPY orders FROM STDIN"263```264265```python266# ❌ Hash-as-anonymization (still reversible/joinable)267df["email"] = df["email"].apply(lambda e: hashlib.sha256(e.encode()).hexdigest())268df.to_csv("dev_users.csv")269```270271```python272# ❌ Real support transcript embedded in a fine-tuning JSONL273{"messages": [274 {"role": "user", "content": "hi this is jane.doe@realco.com, my order #4521 ..."},275 {"role": "assistant", "content": "Hi Jane, I see your order ..."}276]}277```278279```bash280# ❌ Screen recording of the production app with a real customer loaded281# (no code snippet — surfaces as a policy violation in review)282```283284## Remediation285286### Test fixture with synthetic data (Python)287288```python289# ✅ Synthetic and obviously so290from faker import Faker291fake = Faker()292Faker.seed(42) # reproducible293294USERS = [295 {296 "id": i,297 "name": fake.name(),298 "email": fake.email(), # ends in @example.com etc.299 "phone": f"+1-415-555-{i:04d}", # reserved 555-01xx block300 "address": fake.address(),301 "dob": fake.date_of_birth().isoformat(),302 }303 for i in range(100)304]305```306307### Provider test data for payments308309```python310# ✅ Stripe test card numbers — never authorize real charges311STRIPE_TEST_CARDS = {312 "visa_success": "4242424242424242",313 "visa_declined": "4000000000000002",314 "visa_3ds": "4000002500003155",315 # See https://docs.stripe.com/testing for the full set316}317```318319### Logging with user_id, not email320321```python322# ✅ Stable internal identifier, no PII323log.info("signup completed", extra={"user_id": user.id, "tenant_id": user.tenant_id})324325# ✅ Shape of the input on validation failure, not the input itself326log.warn("email validation failed",327 extra={"length": len(email), "has_at": "@" in email})328```329330### Error messages without PII331332```python333# ✅ User-friendly message, no PII in the exception334raise BusinessError(335 code="email_already_registered",336 message="This email address is already registered.",337 # No email in the payload — the API client already has it338)339```340341### Synthetic-data pipeline (subset + scrubbed)342343```text3441. Subset:345 - SELECT ~1% of rows from each table, scoped to a small set of346 synthetic tenant IDs created in production specifically for347 this purpose (or to a documented internal-test tenant).3482. Scrub:349 - Run the subset through Presidio / Comprehend / DLP.350 - Replace emails with `<id>@example.com`, phones with351 `+1-415-555-NNNN`, names with faker output, postal addresses352 with faker output, payment card numbers with Stripe test353 PANs.3543. Verify:355 - Sample 1% of the scrubbed output. Confirm no real customer356 identifier survives.3574. Ship:358 - Materialize as a versioned dataset (S3 + manifest).359 - Refresh on a documented cadence.360```361362### LLM input scrubbing363364```python365# ✅ Presidio scrub before any prompt that includes user content366from presidio_analyzer import AnalyzerEngine367from presidio_anonymizer import AnonymizerEngine368369analyzer = AnalyzerEngine()370anonymizer = AnonymizerEngine()371372def scrub_for_llm(text: str) -> tuple[str, list[dict]]:373 results = analyzer.analyze(text=text, language="en")374 anonymized = anonymizer.anonymize(text=text, analyzer_results=results)375 return anonymized.text, [376 {"entity": r.entity_type, "start": r.start, "end": r.end}377 for r in results378 ]379380scrubbed, log = scrub_for_llm(user_message)381# log_to_audit_trail(scrubbed=scrubbed, redactions=log)382prompt_llm(scrubbed)383```384385### Bug reproducer using synthetic data386387```python388# ✅ Reproduce the bug with faker-generated data rather than the389# real row, then attach the reproducer (not the row) to the ticket390def reproduce_bug_12345():391 user = User(392 email=fake.email(),393 name=fake.name(),394 signup_at=datetime(2026, 5, 17, 14, 32),395 )396 # exercise the failing code path397 process_signup(user)398```399400## Production detection heuristics401402Treat the data context as production-class (escalating the403protocol) when **any** of these match:404405- The source table, file, or dataset name contains `users`,406 `accounts`, `customers`, `members`, `patients`, `subscribers`,407 `payments`, `invoices`, `transactions`, `pii_*`, `phi_*`,408 `pci_*`.409- The source database, S3 bucket, or storage path contains410 `prod`, `production`, `live`, `customer`, `tenant`, or a411 customer/tenant identifier.412- The fields involved include email, phone, name, address, DOB,413 SSN/national ID, card number, bank account, diagnosis code,414 medication, prescription number, geolocation, or government415 identifier.416- The destination is anything outside the production environment417 (dev, staging, demo, test, marketing, vendor, support, AI/ML418 training).419420If any signal matches, all six rules apply without exception, and421Rule 3 (no production-to-non-production copy) becomes a hard422refusal — the agent does not propose, generate, or run a copy423operation even if the user frames it as a one-off.424425## References426427- Stripe testing reference — https://docs.stripe.com/testing428- Faker (Python) — https://faker.readthedocs.io/429- @faker-js/faker (Node) — https://fakerjs.dev/430- RFC 2606 — Reserved Top Level DNS Names — https://www.rfc-editor.org/rfc/rfc2606431- RFC 5737 — IPv4 Address Blocks Reserved for Documentation — https://www.rfc-editor.org/rfc/rfc5737432- RFC 3849 — IPv6 Address Prefix Reserved for Documentation — https://www.rfc-editor.org/rfc/rfc3849433- Microsoft Presidio — https://microsoft.github.io/presidio/434- AWS Comprehend Detect PII — https://docs.aws.amazon.com/comprehend/latest/dg/how-pii.html435- Google Cloud DLP — https://cloud.google.com/dlp436- GDPR Data Protection Impact Assessment — https://gdpr.eu/data-protection-impact-assessment-template/437- HIPAA Safe Harbor de-identification — https://www.hhs.gov/hipaa/for-professionals/privacy/special-topics/de-identification/