AgentSkillsCN

security-vulnerability

为 Buzz Stack 提供 OWASP 漏洞防护、XSS/CSRF 预防、密钥管理与安全扫描方案。

SKILL.md
--- frontmatter
name: security-vulnerability
description: OWASP vulnerabilities, XSS/CSRF prevention, secret management, and security scanning for Buzz Stack.
argument-hint: Describe the security concern - injection, XSS, dependency vulnerability, secret exposure, etc.

Security & Vulnerability Prevention

Overview

Security isn't optional. This skill covers common vulnerabilities, prevention patterns, and security-first thinking for modern web applications.

Why it matters:

  • Vulnerabilities lead to data breaches ($4.45M average cost)
  • Users trust you with their data (GDPR, CCPA compliance needed)
  • Security debt grows with inaction
  • Prevention is 100x cheaper than remediation

Core Concepts

1. OWASP Top 10 (Know Them)

typescript
// 1. Broken Access Control
// WRONG: Trusting client to enforce permissions
const deleteUser = (id: string) => {
  return fetch(`/api/users/${id}`, { method: 'DELETE' });
};

// RIGHT: Verify permissions on server
async function deleteUser(userId: string, currentUser: User) {
  if (userId === currentUser.id || currentUser.role === 'admin') {
    // Only allow deleting self or admin deleting anyone
    await db.users.delete(userId);
  } else {
    throw new Error('Forbidden');
  }
}

// 2. Cryptographic Failures
// WRONG: Storing passwords in plain text
const user = { name: 'Alice', password: 'MyPassword123' };

// RIGHT: Hash and salt passwords
import bcrypt from 'bcrypt';
const hashedPassword = await bcrypt.hash('MyPassword123', 10);
const user = { name: 'Alice', password: hashedPassword };

// 3. Injection (SQL, Command, etc.)
// WRONG: String concatenation in queries
const query = `SELECT * FROM users WHERE email = '${email}'`;

// RIGHT: Use parameterized queries
const user = await db.users.findUnique({ where: { email } }); // Prisma handles it

// 4. Insecure Design
// RIGHT: Design security in from the start
function createUser(data: RegisterRequest) {
  // Validate email format
  // Enforce strong passwords
  // Rate limit registrations
  // Log security events
}

// 5. Security Misconfiguration
// WRONG: Exposing debug info in production
if (process.env.NODE_ENV === 'production') {
  console.error(error); // Don't log errors to client
}

// RIGHT: Log securely
function handleError(error: Error) {
  // Log full details to server logs
  console.error('[ERROR]', error);

  // Return sanitized error to client
  return {
    error: 'An error occurred. Please try again.',
  };
}

// 6. Vulnerable Components
// WRONG: Using outdated packages
package.json: { "lodash": "3.x" } // Outdated!

// RIGHT: Keep dependencies updated
// Regularly run: npm audit, npm update

// 7. Authentication Failures
// WRONG: Reusing user input as ID
const user = await db.users.findFirst({ where: { id: req.body.userId } });

// RIGHT: Use authenticated context
const user = await db.users.findFirst({ where: { id: req.user.id } });

// 8. Software Data Integrity
// RIGHT: Verify downloaded packages haven't been tampered with
// npm uses checksums and HTTPS for integrity

// 9. Logging & Monitoring Failures
// RIGHT: Log security events
function loginUser(email: string) {
  // Log ALL login attempts (success and failure)
  const user = await authenticate(email);
  logger.info('User logged in', { userId: user.id, timestamp: Date.now() });
}

// 10. Server-Side Template Injection
// WRONG: Injecting unsanitized user input into templates
ejs.render(`<h1>${userInput}</h1>`);

// RIGHT: Use safe templating
<h1>{userInput}</h1> // React auto-escapes

2. XSS (Cross-Site Scripting) Prevention

typescript
// ❌ WRONG: Rendering user input as HTML
function Comment({ text }: { text: string }) {
  return <div dangerouslySetInnerHTML={{ __html: text }} />;
  // User can inject: <script>alert('hacked')</script>
}

// ✅ CORRECT: React auto-escapes text
function Comment({ text }: { text: string }) {
  return <div>{text}</div>; // Auto-escaped
  // User input rendered as: &lt;script&gt;...&lt;/script&gt;
}

// If HTML is truly needed, sanitize it
import DOMPurify from 'dompurify';

function RichComment({ html }: { html: string }) {
  const clean = DOMPurify.sanitize(html);
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

// Content Security Policy (strongest defense)
// In HTTP headers or meta tag:
// <meta http-equiv="Content-Security-Policy"
//       content="script-src 'self'; object-src 'none';">
// Blocks all inline scripts unless whitelisted

3. CSRF (Cross-Site Request Forgery) Prevention

typescript
// ❌ WRONG: Accept state-changing requests without verification
app.post('/api/transfer', (req, res) => {
  // Any website can POST here if user is authenticated
  transferMoney(req.body);
});

// ✅ CORRECT: Require CSRF token
// 1. Server generates token on page load
const csrfToken = crypto.randomBytes(32).toString('hex');
// Send to client in response

// 2. Client includes token in forms
<form method="POST" action="/api/transfer">
  <input type="hidden" name="csrf" value={csrfToken} />
  {/* Form fields */}
</form>

// 3. Server validates token before processing
app.post('/api/transfer', (req, res) => {
  if (req.body.csrf !== req.session.csrfToken) {
    throw new Error('CSRF token invalid');
  }
  transferMoney(req.body);
});

// Or use SameSite cookies (modern approach)
// Set-Cookie: session=abc123; SameSite=Strict
// Prevents cross-site cookie inclusion entirely

4. Secret Management

typescript
// ❌ WRONG: Committing secrets to git
api_key = "sk-abc123def456"  // In .env or code

// ✅ CORRECT: Use environment variables (not in git)
// .env.local (never commit this!)
DATABASE_URL=postgresql://...
API_KEY=sk-abc123def456
STRIPE_SECRET=sk_test_...

// Access in code
const apiKey = process.env.API_KEY; // From env at runtime

// ✅ EVEN BETTER: Use secret management service
// AWS Secrets Manager, HashiCorp Vault, etc.
const apiKey = await secretsManager.get('api-key');

// For NEXT_PUBLIC_ variables (safe to expose)
// Only non-sensitive data
const PUBLIC_API_URL = process.env.NEXT_PUBLIC_API_URL;
// This gets embedded in client-side code

Deep Patterns

Pattern 1: Security Headers

typescript
// Set comprehensive security headers
function setSecurityHeaders(res: Response) {
  // Prevent MIME sniffing
  res.setHeader("X-Content-Type-Options", "nosniff");

  // Prevent clickjacking
  res.setHeader("X-Frame-Options", "DENY");

  // Prevent XSS (even if filter bypassed)
  res.setHeader("X-XSS-Protection", "1; mode=block");

  // Content Security Policy (strongest)
  res.setHeader(
    "Content-Security-Policy",
    "default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'",
  );

  // HSTS (force HTTPS)
  res.setHeader(
    "Strict-Transport-Security",
    "max-age=31536000; includeSubDomains",
  );

  // Referrer policy (privacy)
  res.setHeader("Referrer-Policy", "no-referrer");
}

Pattern 2: Input Validation & Sanitization

typescript
// Always validate & sanitize user input
function validateUserInput(input: unknown): string {
  if (typeof input !== "string") {
    throw new Error("Must be a string");
  }

  // Length check
  if (input.length > 1000) {
    throw new Error("Too long");
  }

  // Pattern check
  if (!/^[a-zA-Z0-9\s]*$/.test(input)) {
    throw new Error("Invalid characters");
  }

  // Trim whitespace
  return input.trim();
}

// Use schema validation library
import { z } from "zod";

const UserSchema = z.object({
  email: z.string().email(),
  password: z.string().min(12).regex(/[A-Z]/).regex(/[0-9]/),
  name: z.string().min(2).max(100),
});

function createUser(data: unknown) {
  const validated = UserSchema.parse(data); // Throws if invalid
  return saveUser(validated);
}

Pattern 3: Rate Limiting

typescript
// Prevent brute force attacks
class RateLimiter {
  private attempts = new Map<string, number[]>();

  isAllowed(userId: string, maxAttempts = 5, windowMs = 60000): boolean {
    const now = Date.now();
    const times = this.attempts.get(userId) || [];

    // Remove old attempts outside window
    const recentAttempts = times.filter((t) => now - t < windowMs);

    if (recentAttempts.length >= maxAttempts) {
      return false; // Too many attempts
    }

    // Record this attempt
    recentAttempts.push(now);
    this.attempts.set(userId, recentAttempts);

    return true;
  }
}

// Usage
const limiter = new RateLimiter();

app.post("/api/login", (req, res) => {
  if (!limiter.isAllowed(req.body.email)) {
    return res.status(429).send("Too many login attempts");
  }

  authenticateUser(req.body);
});

Pattern 4: Secure Cookie Settings

typescript
// Set httpOnly, Secure, SameSite flags
function setAuthCookie(res: Response, token: string) {
  res.setHeader(
    "Set-Cookie",
    [
      `auth=${token}`,
      "HttpOnly", // Not accessible to JavaScript
      "Secure", // Only over HTTPS
      "SameSite=Strict", // Not sent cross-site
      "Max-Age=3600", // Expires in 1 hour
      "Path=/",
    ].join("; "),
  );
}

Anti-Patterns to Avoid

typescript
// ❌ Wrong: Storing passwords in plain text
const user = { name: "Alice", password: "secret" };

// ✅ Correct: Hash passwords with salt
const hashedPassword = await bcrypt.hash("secret", 10);

// ❌ Wrong: Exposing error details to users
if (emailExists) {
  throw new Error("This email is already registered");
  // Attacker knows this account exists!
}

// ✅ Correct: Generic error messages for security
function registerUser(email: string) {
  // Don't reveal whether email exists
  return sendEmail(email, "If this email is registered..."); // Same message always
}

// ❌ Wrong: Trusting client-side validation only
if (amount > 0) {
  transfer(); // Client can be bypassed!
}

// ✅ Correct: Validate on server
app.post("/transfer", (req, res) => {
  const amount = parseInt(req.body.amount); // Re-parse
  if (amount <= 0) throw new Error("Invalid amount");
  transfer(amount);
});

Security Checklist

Before deployment:

  • All secrets stored in environment variables (never in code)
  • HTTPS enabled (Secure cookie flag set)
  • Input validation on all user inputs
  • CSRF tokens required for state-changing operations
  • Security headers set (CSP, HSTS, etc.)
  • Dependency vulnerabilities checked (npm audit)
  • Rate limiting on login/forms
  • Error messages don't expose sensitive info
  • Database queries parameterized (Prisma/ORM)
  • User permissions verified on server

Tools & Services

  • npm audit - Check dependencies for vulnerabilities
  • OWASP ZAP - Automated security scanner
  • Snyk - Continuous vulnerability monitoring
  • Auth0/Auth2go - Secure authentication services
  • Sentry - Error tracking without exposing data
  • HashiCorp Vault - Secret management

Common Vulnerabilities in Web Apps

VulnerabilityImpactPrevention
SQL InjectionAttacker reads entire databaseParameterized queries (Prisma)
XSSAttacker steals user sessionsSanitize input, auto-escape (React)
CSRFAttacker performs actions as userCSRF tokens, SameSite cookies
Weak AuthAttacker guesses passwordsStrong passwords, rate limiting, MFA
Broken AccessAttacker accesses others' dataServer-side permission checks

Key Questions

  1. What secrets does the app have? (API keys, DB creds, OAuth tokens)
  2. Who can access this data? (Auth model correct?)
  3. What if user input is malicious? (Sanitized?)
  4. Is this over HTTPS? (Always!)
  5. Are dependencies up-to-date? (Security patches current?)

Resources