Agent Skill
2/7/2026

security-audit

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.

T
trantuananh
0GitHub Stars
1Views
npx skills add trantuananh-17/product-reviews

SKILL.md

Namesecurity-audit
DescriptionUse 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.

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

VulnerabilityRiskExample
IDORHighUser A accesses User B's data via /api/customer/123
Unauthenticated PIICriticalReturning email in public API response
Missing AuthCritical/popup/* endpoints without authentication
Shop IsolationCriticalShop A accessing Shop B's data

Authentication

Endpoint Types

Endpoint TypeAuth RequiredExample
Admin APIShop session + JWT/api/admin/*
Storefront APICustomer token OR signature/api/storefront/*
Popup/WidgetHMAC signature/popup/*
WebhookShopify HMAC/webhooks/*
PublicNone (no sensitive data)/health, /status

Admin Controller

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

CheckSecureVulnerable
Shop ID sourcegetCurrentShop(ctx), ctx.state.shop.idctx.params, ctx.query
Query scoping.where('shopId', '==', shopId).doc(id).get() alone
Ownership checkif (resource.shopId !== shopId)Return data directly
Update/DeleteVerify ownership firstrepo.update(id, data)

Grep Commands for Audit

grep -rn "ctx.params.shopId" controllers/
grep -rn "ctx.params.customerId" controllers/
grep -rn "getById(" repositories/
grep -rn ".doc(.*).get()" repositories/

Secure Pattern

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 TypeClassificationPublic Endpoint?
Email, Phone, AddressPIINever
Date of BirthPIINever
Payment InfoSensitive PIINever
First NameLow RiskWith signature
Points, TierNon-PIIWith signature

Secure Response

// Only return non-sensitive data
ctx.body = {
  firstName: customer.firstName,
  points: customer.points,
  tier: customer.tier
  // Never: email, phone, address
};

HMAC Verification

Popup Signature

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

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

PatternRisk
HMAC bypass headersCRITICAL
No HMAC verificationHIGH
Missing timestamp validationMEDIUM

Input Validation

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

DoDon't
Get shopId from getCurrentShop(ctx)Use ctx.params.shopId
Scope all queries by shopIdQuery without shop filter
Whitelist response fieldsReturn full objects
Verify HMAC on webhooksTrust headers blindly
Validate and sanitize inputsUse ctx.request.body directly
Use crypto.timingSafeEqualCompare 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
Skills Info
Original Name:security-auditAuthor:trantuananh