home / skills / trantuananh-17 / product-reviews / security
This skill helps you audit security risk, enforce authentication, and prevent IDOR by applying proven patterns for auth, PII protection, and webhooks.
npx playbooks add skill trantuananh-17/product-reviews --skill securityReview the files below or copy the command above to add this skill to your agents.
---
name: security-audit
description: Use this skill when the user asks to "audit security", "check for vulnerabilities", "review authentication", "prevent IDOR", "protect customer data", "verify webhooks", "check HMAC", or any security-related review work. Provides security patterns for authentication, authorization, IDOR prevention, PII protection, and webhook verification.
---
# Security Patterns (packages/functions)
> For **API design patterns**, see `api-design` skill
## Critical Vulnerabilities
| Vulnerability | Risk | Example |
|--------------|------|---------|
| **IDOR** | High | User A accesses User B's data via `/api/customer/123` |
| **Unauthenticated PII** | Critical | Returning email in public API response |
| **Missing Auth** | Critical | `/popup/*` endpoints without authentication |
| **Shop Isolation** | Critical | Shop A accessing Shop B's data |
---
## Authentication
### Endpoint Types
| Endpoint Type | Auth Required | Example |
|--------------|---------------|---------|
| Admin API | Shop session + JWT | `/api/admin/*` |
| Storefront API | Customer token OR signature | `/api/storefront/*` |
| Popup/Widget | HMAC signature | `/popup/*` |
| Webhook | Shopify HMAC | `/webhooks/*` |
| Public | None (no sensitive data) | `/health`, `/status` |
### Admin Controller
```javascript
import {getCurrentShop} from '@functions/helpers/auth';
async function getCustomers(ctx) {
const shopId = getCurrentShop(ctx); // From authenticated session
const customers = await customerRepo.getByShopId(shopId);
ctx.body = {success: true, data: customers};
}
```
---
## IDOR Prevention
### Audit Checklist
| Check | Secure | Vulnerable |
|-------|--------|------------|
| Shop ID source | `getCurrentShop(ctx)`, `ctx.state.shop.id` | `ctx.params`, `ctx.query` |
| Query scoping | `.where('shopId', '==', shopId)` | `.doc(id).get()` alone |
| Ownership check | `if (resource.shopId !== shopId)` | Return data directly |
| Update/Delete | Verify ownership first | `repo.update(id, data)` |
### Grep Commands for Audit
```bash
grep -rn "ctx.params.shopId" controllers/
grep -rn "ctx.params.customerId" controllers/
grep -rn "getById(" repositories/
grep -rn ".doc(.*).get()" repositories/
```
### Secure Pattern
```javascript
async function getOrder(ctx) {
const shopId = getCurrentShop(ctx);
const {orderId} = ctx.params;
const order = await orderRepo.getById(orderId);
if (order.shopId !== shopId) {
ctx.status = 403;
return;
}
ctx.body = {success: true, data: order};
}
```
---
## PII Protection
### Classification
| Data Type | Classification | Public Endpoint? |
|-----------|---------------|------------------|
| Email, Phone, Address | PII | Never |
| Date of Birth | PII | Never |
| Payment Info | Sensitive PII | Never |
| First Name | Low Risk | With signature |
| Points, Tier | Non-PII | With signature |
### Secure Response
```javascript
// Only return non-sensitive data
ctx.body = {
firstName: customer.firstName,
points: customer.points,
tier: customer.tier
// Never: email, phone, address
};
```
---
## HMAC Verification
### Popup Signature
```javascript
import crypto from 'crypto';
function verifyPopupSignature(ctx) {
const {shopId, customerId, timestamp, signature} = ctx.query;
// Reject old requests
if (Date.now() - parseInt(timestamp) > 5 * 60 * 1000) return false;
const expected = crypto
.createHmac('sha256', process.env.POPUP_SECRET)
.update(`${shopId}:${customerId}:${timestamp}`)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
```
### Shopify Webhook
```javascript
function verifyShopifyWebhook(ctx) {
const hmac = ctx.get('X-Shopify-Hmac-Sha256');
const calculated = crypto
.createHmac('sha256', process.env.SHOPIFY_API_SECRET)
.update(ctx.request.rawBody, 'utf8')
.digest('base64');
return crypto.timingSafeEqual(Buffer.from(hmac), Buffer.from(calculated));
}
```
### Webhook Vulnerabilities
| Pattern | Risk |
|---------|------|
| HMAC bypass headers | CRITICAL |
| No HMAC verification | HIGH |
| Missing timestamp validation | MEDIUM |
---
## Input Validation
```javascript
async function updateCustomer(ctx) {
const shopId = getCurrentShop(ctx);
const {customerId} = ctx.params;
const {firstName, lastName} = ctx.request.body; // Whitelist fields
const customer = await customerRepo.getById(customerId);
if (customer.shopId !== shopId) {
ctx.status = 403;
return;
}
await customerRepo.update(customerId, {
firstName: firstName?.trim().slice(0, 50),
lastName: lastName?.trim().slice(0, 50)
});
}
```
---
## Database Security
### Firestore Rules
```
match /customers/{customerId} {
allow read, write: if request.auth != null
&& request.auth.token.shopId == resource.data.shopId;
}
```
### Storage Rules
```
match /shops/{shopId}/{allPaths=**} {
allow read, write: if request.auth != null
&& request.auth.token.shopId == shopId
&& request.resource.size < 5 * 1024 * 1024;
}
```
---
## Best Practices
| Do | Don't |
|----|-------|
| Get shopId from `getCurrentShop(ctx)` | Use `ctx.params.shopId` |
| Scope all queries by shopId | Query without shop filter |
| Whitelist response fields | Return full objects |
| Verify HMAC on webhooks | Trust headers blindly |
| Validate and sanitize inputs | Use `ctx.request.body` directly |
| Use `crypto.timingSafeEqual` | Compare strings with `===` |
---
## Security Checklist
```
Authentication:
□ Sensitive endpoints require auth
□ Shop ID from session, not params
□ Customer ID verified against token
Authorization:
□ Users access only their data
□ Shop isolation verified
□ No IDOR vulnerabilities
Data Protection:
□ No PII in unauthenticated responses
□ Response fields whitelisted
□ Inputs validated and sanitized
Webhooks:
□ HMAC verification on all webhooks
□ Timestamp validation
□ No bypass headers
```This skill provides a focused security audit checklist and concrete patterns for web APIs and services. It highlights high-risk issues like IDOR, unauthenticated PII exposure, missing auth, shop isolation, webhook HMAC verification, and input validation. Use it to quickly assess authentication, authorization, PII handling, webhook integrity, and database rules.
It inspects endpoint types, auth requirements, and common code patterns to identify insecure sources of shop or user identity (params/query vs. session). It verifies HMAC webhook and popup signature logic, checks ownership enforcement for reads/updates/deletes, and ensures PII is never returned from public endpoints. The skill provides grep commands, secure code snippets, Firestore/storage rule examples, and a checklist for manual audits.
How do I detect IDOR quickly in a codebase?
Search for direct getById/.doc(id).get() calls and usages of ctx.params or ctx.query for shop/user IDs. Replace with patterns that use getCurrentShop(ctx) and ownership checks before returning data.
What if I can’t change an upstream webhook to include an HMAC?
If HMAC is unavailable, require a shared secret in a separate header, restrict IPs, add timestamp+nonce checks, and treat the endpoint as high risk until a proper signature is added.