All skills
asyrafhussin avatar

/api-design-patterns

@1de3a7a

RESTful API design, error handling, versioning, and best practices. Use when designing APIs, reviewing endpoints, implementing error responses, or setting up API structure. Triggers on "design API", "review API", "REST best practices", or "API patterns".

Use this Skill: https://skilld.dev/gh/asyrafhussin/agent-skills/api-design-patterns

This session only. Nothing lands on disk.

rulessec-sensitive-data.md

≈1.8k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Protect Sensitive Data in Responses

Impact: CRITICAL (Prevents data leaks and privacy violations)

Never expose sensitive information like passwords, tokens, internal IDs, or PII in API responses.

Incorrect

// ❌ Exposing password hash
{
  "id": 123,
  "email": "user@example.com",
  "passwordHash": "$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.n",
  "name": "John Doe"
}

// ❌ Exposing API keys
{
  "user": {
    "id": 123,
    "apiKey": "sk_live_abc123xyz789",
    "secretKey": "whsec_secret_key_here"
  }
}

// ❌ Exposing internal IDs
{
  "user": {
    "id": 123,
    "internalDatabaseId": "mongo_507f1f77bcf86cd799439011",
    "stripeCustomerId": "cus_abc123"
  }
}

// ❌ Exposing other users' data
{
  "order": {
    "id": 456,
    "customer": {
      "ssn": "123-45-6789",
      "creditCard": "4111111111111111",
      "dateOfBirth": "1990-01-15"
    }
  }
}
// ❌ Returning entire database record
app.get('/users/:id', async (req, res) => {
  const user = await db.findUser(req.params.id);
  res.json(user); // Exposes ALL fields including sensitive ones
});

Problems:

  • Exposed password hashes enable offline brute force attacks
  • API keys in responses allow unauthorized access to third-party services
  • Internal IDs help attackers enumerate resources and map infrastructure
  • PII exposure violates privacy regulations (GDPR, CCPA, PCI-DSS)
  • Returning full database records exposes fields never intended for clients

Correct

// ✅ Explicit field selection per context
const userPublicFields = ['id', 'name', 'avatar', 'createdAt'];
const userPrivateFields = [...userPublicFields, 'email', 'settings'];
const userAdminFields = [...userPrivateFields, 'roles', 'lastLoginAt', 'status'];

function sanitizeUser(user, context = 'public') {
  const allowedFields = {
    public: userPublicFields,
    private: userPrivateFields,
    admin: userAdminFields
  }[context];

  return Object.fromEntries(
    Object.entries(user).filter(([key]) => allowedFields.includes(key))
  );
}

// Use explicit field selection
app.get('/users/:id', authenticate, async (req, res) => {
  const user = await db.findUser(req.params.id);

  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }

  // Determine context based on requester
  let context = 'public';
  if (req.user.id === user.id) {
    context = 'private';
  } else if (req.user.roles.includes('admin')) {
    context = 'admin';
  }

  res.json(sanitizeUser(user, context));
});

// Use DTOs/serializers
class UserResponse {
  constructor(user, includePrivate = false) {
    this.id = user.id;
    this.name = user.name;
    this.avatar = user.avatar;
    this.createdAt = user.createdAt;

    if (includePrivate) {
      this.email = user.email;
      this.settings = user.settings;
    }
  }
}

app.get('/users/:id', async (req, res) => {
  const user = await db.findUser(req.params.id);
  const includePrivate = req.user?.id === user.id;
  res.json(new UserResponse(user, includePrivate));
});

// Mask sensitive data
function maskEmail(email) {
  const [local, domain] = email.split('@');
  const maskedLocal = local[0] + '*'.repeat(local.length - 2) + local.slice(-1);
  return `${maskedLocal}@${domain}`;
}

function maskPhone(phone) {
  return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
}

function maskCreditCard(number) {
  return '**** **** **** ' + number.slice(-4);
}

// Apply masking in responses
app.get('/users/:id', async (req, res) => {
  const user = await db.findUser(req.params.id);

  res.json({
    id: user.id,
    name: user.name,
    email: req.user.id === user.id ? user.email : maskEmail(user.email),
    phone: maskPhone(user.phone),
    paymentMethod: {
      type: 'card',
      last4: user.creditCard.slice(-4),
      brand: user.cardBrand
    }
  });
});
# ✅ FastAPI with Pydantic response models
from pydantic import BaseModel, EmailStr, Field
from typing import Optional

# Internal model (full data)
class UserInternal(BaseModel):
    id: int
    email: str
    password_hash: str
    name: str
    ssn: Optional[str]
    api_key: str
    stripe_customer_id: str

# Public response model (safe to expose)
class UserPublic(BaseModel):
    id: int
    name: str
    avatar_url: Optional[str]

    class Config:
        # Only include fields defined in this model
        extra = 'forbid'

# Private response model (for account owner)
class UserPrivate(UserPublic):
    email: EmailStr
    settings: dict

# Admin response model
class UserAdmin(UserPrivate):
    status: str
    roles: list
    last_login_at: Optional[str]

@app.get("/users/{user_id}", response_model=UserPublic)
async def get_user(user_id: int, current_user = Depends(get_current_user)):
    user = await db.get_user(user_id)

    if current_user.id == user_id:
        return UserPrivate(**user.dict())
    elif "admin" in current_user.roles:
        return UserAdmin(**user.dict())
    else:
        return UserPublic(**user.dict())

# Mask sensitive data in logs
import logging

class SensitiveDataFilter(logging.Filter):
    PATTERNS = [
        (r'"password":\s*"[^"]*"', '"password": "[REDACTED]"'),
        (r'"token":\s*"[^"]*"', '"token": "[REDACTED]"'),
        (r'"apiKey":\s*"[^"]*"', '"apiKey": "[REDACTED]"'),
        (r'\b\d{3}-\d{2}-\d{4}\b', '[SSN REDACTED]'),
        (r'\b\d{16}\b', '[CARD REDACTED]'),
    ]

    def filter(self, record):
        import re
        message = record.getMessage()
        for pattern, replacement in self.PATTERNS:
            message = re.sub(pattern, replacement, message)
        record.msg = message
        return True
// ✅ Safe user response
{
  "id": 123,
  "name": "John Doe",
  "avatar": "https://cdn.example.com/avatars/123.jpg",
  "email": "j***n@example.com",
  "memberSince": "2023-01-15"
}

// ✅ Safe payment method response
{
  "paymentMethods": [
    {
      "id": "pm_abc123",
      "type": "card",
      "brand": "visa",
      "last4": "4242",
      "expiryMonth": 12,
      "expiryYear": 2025
    }
  ]
}

Sensitive Data Checklist

Field Action Reason
Password hash Never expose Security
API keys Never expose Security
SSN/Tax ID Never/mask PII/Compliance
Full credit card Never expose PCI-DSS
Internal IDs Usually hide Information disclosure
Email (other users) Mask Privacy
Phone Mask Privacy
Address Context-dependent Privacy

Benefits:

  • Prevents account takeover from exposed credentials
  • Meets privacy regulation requirements (GDPR, CCPA, PCI-DSS)
  • Reduces attack surface by hiding internal system identifiers
  • Context-based field exposure follows the principle of least privilege
  • Data masking provides useful info without exposing full values
  • Simplifies compliance audits with clear data handling policies

Reference: OWASP Sensitive Data Exposure

Source: SKILL.md on GitHub

1 alert16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill is a comprehensive documentation library for RESTful API design patterns. It provides best practices for resource design, error handling, security, and documentation. The skill contains no executable code, malicious instructions, or hidden functionality, and its references are restricted to well-known technical documentation sources.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    4/28 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 1de3a7a. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated 7 months ago
metadata
{
  "author": "agent-skills",
  "version": "2.0.0"
}

README badge

README badge for asyrafhussin/agent-skills/api-design-patterns