Skip to content

security-pii

Load when reviewing code that handles personal data in logs, test fixtures, error responses, serialized output, URLs, telemetry, or git history.

From plugin
vexjoy-agent
413198 skills198 agents10 commands86 hooks
Install
$ npx -y skills add notque/vexjoy-agent --agent claude-code

How it fires

How this agent gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.

Context preview

The summary Claude sees to decide when to auto-load this agent.

Load when reviewing code that handles personal data in logs, test fixtures, error responses, serialized output, URLs, telemetry, or git history.

Agent definition

security-pii.md

PII Exposure Patterns

Load when reviewing code that handles personal data in logs, test fixtures, error responses, serialized output, URLs, telemetry, or git history.

PII exposure: personally identifiable information appears in a context with broader visibility, longer retention, or lower trust than intended. Use synthetic data in tests, structured identifiers in logs, explicit field selection in API responses.

---

Use Synthetic Data in Test Fixtures

Tests, fixtures, snapshots, cassettes, and seed files must use obviously synthetic identifiers.

Correct Pattern

**Python:**

fixture = {
    "email": "user@example.com",
    "org_slug": "org-slug",
    "name": "Jane Doe",
    "ip": "198.51.100.23",       # RFC 5737
    "arr_usd": 120000,
    "renewal_date": "2026-01-01",
}

**TypeScript:**

const fixture = {
  email: 'user@example.com',
  orgSlug: 'org-slug',
  name: 'John Doe',
  ip: '203.0.113.42',  // RFC 5737 TEST-NET-3
  monthlySpend: 1000,
  seatCount: 25,
};

Safe Domains and IP Ranges

| Type | Safe Values | Standard | |------|-------------|----------| | Email domains | `example.com`, `example.org`, `example.net`, `.invalid` | RFC 2606 | | IPv4 | `192.0.2.0/24`, `198.51.100.0/24`, `203.0.113.0/24` | RFC 5737 | | IPv6 | `2001:db8::/32` | RFC 3849 | | Names | `Jane Doe`, `John Doe`, `Alice`, `Bob`, `Acme Corp` | Convention |

Why This Matters

Real data in git history cannot be fully purged. A single real email is a GDPR data subject access request waiting to happen.

Detection

rg -n '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' tests/ fixtures/ --type py --type ts | \
  rg -v 'example\.(com|org|net)|noreply|test@|foo@|user@'

rg -n '\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b' tests/ fixtures/ | \
  rg -v '192\.0\.2\.|198\.51\.100\.|203\.0\.113\.|127\.0\.0\.|0\.0\.0\.0|10\.\|172\.(1[6-9]|2|3[01])\.|192\.168\.'

rg -n 'customer|account.*slug|org.*slug|tenant' tests/ fixtures/ --type py --type ts

---

Log Structured Identifiers Instead of Raw PII

Log internal ID, salted hash, or correlation ID — never raw email, IP, phone, or request body.

Correct Pattern

**Python:**

import hashlib

def hash_email(email: str) -> str:
    return hashlib.sha256(f"salt:{email}".encode()).hexdigest()[:12]

logger.warning(
    "identity lookup failed",
    extra={"user_id": user.id, "email_hash": hash_email(user.email)},
)

**TypeScript:**

logger.warn('signup failed', {
  userId: user.id,
  reason: 'validation_failed',
});

**Go:**

slog.Warn("identity lookup failed",
    "user_id", user.ID,
    "error", err,
)

Why This Matters

PII in logs creates a secondary data store bypassing access controls. Log aggregators retain data for months, index for full-text search, and expose to broad teams.

Detection

rg -n 'logger\.\w+\(.*email|logger\.\w+\(.*REMOTE_ADDR|logger\.\w+\(.*request\.(body|data|META)' --type py
rg -n 'logger\.\w+\(.*email|logger\.\w+\(.*req\.(body|ip)|console\.\w+\(.*email' --type ts
rg -n 'set_user\(|set_tag\(.*email|set_extra\(.*email' --type py
rg -n 'slog\.\w+\(.*email|log\.\w+\(.*email|zap\.\w+\(.*email' --type go

---

Keep PII Out of URL Query Strings and Path Segments

Emails, phone numbers, names in URLs appear in browser history, server logs, CDN logs, referrer headers, analytics.

Correct Pattern

**Python:**

request.session["login_error"] = "invalid_magic_code"
return redirect("/login/error")

**TypeScript:**

return redirect('/oauth/error?reason=invalid_code');

Detection

rg -n 'redirect.*email=|redirect.*user=|redirect.*phone=' --type py --type ts
rg -n 'f".*\?.*email|`.*\?.*email|\?.*email=' --type py --type ts

---

Exclude PII Fields from Serialized API Responses

Declare explicit field lists excluding PII not required by the caller.

Correct Pattern

**Python (DRF):**

class UserPublicSerializer(ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'display_name', 'avatar_url']

**TypeScript (Prisma):**

const user = await prisma.user.findUnique({
  where: { id },
  select: { id: true, displayName: true, avatarUrl: true },
});
return res.json(user);

**GraphQL:**

type UserPublic {
  id: ID!
  displayName: String!
  avatarUrl: String
}

Why This Matters

`fields = '__all__'` exposes every column including fields added by future migrations. Full-row Prisma queries return password hashes, 2FA secrets.

Detection

rg -n "fields = '__all__'" --type py
rg -n 'email|phone|ipAddress|password' --type graphql --type ts | rg 'type |interface '
rg -n 'findUnique\(|findFirst\(' --type ts | rg -v 'select:'

---

Scrub PII from Error Responses and Exception Handlers

Error responses: generic message + correlation ID. Stack traces, SQL, request bodies stay server-side.

Correct Pattern

**Python:**

import uuid

@app.errorhandler(Exception)
def handle_error(error):
    correlation_id = str(uuid.uuid4())
    app.logger.error("unhandled exception",
        extra={"correlation_id": correlation_id, "error": str(error)}, exc_info=True)
    return {"error": "internal server error", "reference": correlation_id}, 500

**TypeScript:**

app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  const correlationId = crypto.randomUUID();
  console.error({ correlationId, error: err.stack });
  res.status(500).json({ error: 'internal server error', reference: correlationId });
});

Detection

rg -n 'err\.stack|error\.stack|traceback|exc_info' --type py --type ts | rg -v 'logger\.|console\.'
rg -n 'request\.(body|data|form)|req\.body' --type py --type ts | rg 'return |res\.(json|send)'
rg -n 'set_user\(|set_context\(|set_extra\(' --type py | rg 'email|phone|ip|address'

---

Detect Common PII Patterns in Code

# Email addresses (excluding safe domains)
rg -n '[a-zA-Z0-9._%+-]+@
Read more
Ships withvexjoy-agent

Essays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.

Get the whole plugin, auto-invoked