Claude Code for Security Testing: Penetration Testing Patterns and Automated Scanning — Claude Skills 360 Blog
Blog / Security / Claude Code for Security Testing: Penetration Testing Patterns and Automated Scanning
Security

Claude Code for Security Testing: Penetration Testing Patterns and Automated Scanning

Published: June 2, 2026
Read time: 9 min read
By: Claude Skills 360

Security testing is most effective when it’s embedded in the development workflow — not left to a quarterly pentest. Claude Code integrates security checks into daily development: reviewing code for OWASP vulnerabilities as it’s written, scanning dependencies for CVEs, detecting secrets before they’re committed, and writing security-focused test cases that verify attack resistance.

This guide covers security testing with Claude Code: OWASP Top 10 patterns, automated scanning integration, secrets detection, and security-focused testing.

Security Code Review

Review this API endpoint for security vulnerabilities.
Focus on injection attacks, authentication bypass, and data exposure.

Claude Code applies a systematic security lens to code. Common findings on API handlers:

1. Missing input validation (Injection)

// Vulnerable — direct user input in query
app.get('/users', async (req, res) => {
  const { role } = req.query;
  const users = await db.query(`SELECT * FROM users WHERE role = '${role}'`); // SQL injection
  res.json(users);
});

// Secure — parameterized query + enum validation
app.get('/users', async (req, res) => {
  const role = z.enum(['admin', 'user', 'moderator']).parse(req.query.role);
  const users = await db.query('SELECT id, name, email FROM users WHERE role = $1', [role]);
  res.json(users);
});

2. Excessive data exposure

// Vulnerable — returns all user fields including sensitive ones
const user = await User.findById(id);
res.json(user); // Includes: password_hash, 2fa_secret, stripe_customer_id

// Secure — explicit field selection
const user = await User.findById(id).select('id name email createdAt');
res.json(user);

3. Missing authorization (IDOR)

// Vulnerable — any authenticated user can access any order
app.get('/orders/:id', authenticate, async (req, res) => {
  const order = await Order.findById(req.params.id);
  res.json(order);
});

// Secure — verify ownership
app.get('/orders/:id', authenticate, async (req, res) => {
  const order = await Order.findOne({ 
    _id: req.params.id,
    userId: req.user.id, // Must belong to requesting user
  });
  if (!order) return res.status(404).json({ error: 'Order not found' });
  res.json(order);
});

CLAUDE.md Security Rules

## Security Rules (enforced in code review)
- All SQL queries: parameterized only — no string interpolation
- All user input: validated with zod schema before use
- All API responses: explicit field selection — never return raw DB objects
- All authenticated endpoints: verify resource ownership (userId check)
- CORS: explicit allowlist — no wildcard in production
- Rate limiting: all auth endpoints, all user-facing APIs
- Error messages: never expose stack traces or DB details to clients
- File uploads: validate MIME type + extension, store outside webroot, scan for malware

Automated SAST Integration

Add SAST scanning to our CI pipeline.
Fail the build on high-severity findings.
# .github/workflows/security.yml
name: Security Scanning

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  sast:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      
    steps:
      - uses: actions/checkout@v4

      # Semgrep — rule-based SAST (fast, low false positives)
      - name: Run Semgrep
        uses: semgrep/semgrep-action@v1
        with:
          config: >-
            p/owasp-top-ten
            p/typescript
            p/nodejs
            p/secrets
          generateSarif: true
        env:
          SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}

      # Upload results to GitHub Security tab
      - name: Upload SARIF results
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: semgrep.sarif

  dependency-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      
      - name: Audit npm dependencies
        run: npm audit --audit-level=high
        
      # Snyk for more comprehensive scanning
      - name: Snyk dependency scan
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high

Secrets Detection

We accidentally committed an API key 3 months ago.
Set up prevention so it never happens again.

Pre-commit hook with detect-secrets:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline']

  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.0
    hooks:
      - id: gitleaks
# Initialize the secrets baseline (marks known false positives as OK)
pip install detect-secrets
detect-secrets scan > .secrets.baseline

# Install the git hooks
pip install pre-commit
pre-commit install

GitHub Actions to scan existing history:

  secrets-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Need full history for historical scan
          
      - name: Scan for secrets with gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}

Custom patterns for your specific secrets:

# .gitleaks.toml
[extend]
useDefault = true

[[rules]]
description = "Our internal API key format"
regex = '''MYAPP_[A-Z0-9]{32}'''
tags = ["api-key", "internal"]

[[rules]]
description = "Stripe secret key"
regex = '''sk_(test|live)_[0-9a-zA-Z]{24,}'''
tags = ["stripe", "payment"]

Security-Focused Testing

Write tests that specifically verify our auth system
can't be bypassed. Test for common attack patterns.
// tests/security/auth.test.ts
describe('Authentication security', () => {
  describe('JWT tampering', () => {
    it('rejects tokens with modified payload', async () => {
      const validToken = await generateToken({ userId: '123', role: 'user' });
      
      // Decode and modify payload without valid signature
      const parts = validToken.split('.');
      const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
      payload.role = 'admin'; // Privilege escalation attempt
      const tamperedToken = `${parts[0]}.${Buffer.from(JSON.stringify(payload)).toString('base64url')}.${parts[2]}`;
      
      const response = await request(app)
        .get('/api/admin/users')
        .set('Authorization', `Bearer ${tamperedToken}`);
      
      expect(response.status).toBe(401);
    });
    
    it('rejects expired tokens', async () => {
      const expiredToken = jwt.sign(
        { userId: '123', role: 'user' },
        process.env.JWT_SECRET!,
        { expiresIn: '-1s' }, // Already expired
      );
      
      const response = await request(app)
        .get('/api/me')
        .set('Authorization', `Bearer ${expiredToken}`);
      
      expect(response.status).toBe(401);
    });
  });

  describe('IDOR prevention', () => {
    it('prevents accessing another user\'s orders', async () => {
      const user1Token = await loginAs('[email protected]');
      const user2Order = await createOrderForUser('[email protected]');
      
      const response = await request(app)
        .get(`/api/orders/${user2Order.id}`)
        .set('Authorization', `Bearer ${user1Token}`);
      
      expect(response.status).toBe(404); // Not 403 — don't reveal existence
    });
  });

  describe('SQL injection prevention', () => {
    it('handles malicious input in search', async () => {
      const maliciousInput = "'; DROP TABLE users; --";
      
      const response = await request(app)
        .get('/api/search')
        .query({ q: maliciousInput })
        .set('Authorization', `Bearer ${validToken}`);
      
      // Should return empty results, not error
      expect(response.status).toBe(200);
      expect(response.body.results).toEqual([]);
      
      // Verify users table is still intact
      const userCount = await db('users').count('*');
      expect(Number(userCount[0].count)).toBeGreaterThan(0);
    });
  });

  describe('Rate limiting', () => {
    it('blocks brute force login attempts', async () => {
      const attempts = Array(10).fill(null).map(() =>
        request(app).post('/api/auth/login').send({
          email: '[email protected]',
          password: 'wrong-password',
        })
      );
      
      const responses = await Promise.all(attempts);
      
      // After 5 failures, should be rate limited
      const rateLimitedCount = responses.filter(r => r.status === 429).length;
      expect(rateLimitedCount).toBeGreaterThan(0);
    });

    it('returns Retry-After header when rate limited', async () => {
      // Exceed rate limit
      for (let i = 0; i < 6; i++) {
        await request(app).post('/api/auth/login').send({ email: '[email protected]', password: 'wrong' });
      }
      
      const response = await request(app)
        .post('/api/auth/login')
        .send({ email: '[email protected]', password: 'wrong' });
      
      expect(response.status).toBe(429);
      expect(response.headers['retry-after']).toBeDefined();
    });
  });
});

XSS Prevention Testing

Write tests that verify our input sanitization
prevents stored XSS attacks.
describe('XSS prevention', () => {
  const xssPayloads = [
    '<script>alert("xss")</script>',
    '<img src="x" onerror="alert(1)">',
    'javascript:alert(1)',
    '<svg onload="alert(1)">',
    '"><script>alert(1)</script>',
  ];

  it.each(xssPayloads)('sanitizes XSS payload in comment: %s', async (payload) => {
    const token = await loginTestUser();
    
    // Submit payload as user content
    const createResponse = await request(app)
      .post('/api/comments')
      .set('Authorization', `Bearer ${token}`)
      .send({ content: payload });
    
    expect(createResponse.status).toBe(201);
    const commentId = createResponse.body.id;
    
    // Retrieve and verify it's sanitized
    const getResponse = await request(app)
      .get(`/api/comments/${commentId}`)
      .set('Authorization', `Bearer ${token}`);
    
    const savedContent = getResponse.body.content;
    
    // Should not contain executable script tags
    expect(savedContent).not.toMatch(/<script/i);
    expect(savedContent).not.toMatch(/javascript:/i);
    expect(savedContent).not.toMatch(/onerror=/i);
    expect(savedContent).not.toMatch(/onload=/i);
  });
});

Dependency Vulnerability Management

We have 15 high-severity CVEs in our dependencies.
Help me triage and remediate them efficiently.
# Audit and output structured report
npm audit --json > audit-results.json
Parse this npm audit JSON output.
Group findings by: fix available (update), fix requires major version bump (review),
and no fix (evaluate alternative package).
Prioritize by severity and whether the vulnerable code path is actually reachable.

Claude Code parses the audit output and generates a prioritized remediation plan:

## Remediation Plan

### Auto-fix (run npm audit fix):
- lodash: 4.17.15 → 4.17.21 (PROTOTYPE_POLLUTION, patch available)
- follow-redirects: 1.14.7 → 1.15.4 (URL_REDIRECT, patch available)

### Manual update required (semver major — review for breaking changes):
- axios: 0.21.1 → 1.6.0 (SSRF, major version bump — check for API changes)
  Breaking changes in v1.x: [list of changes]

### No fix available — evaluate alternatives:
- node-forge: CVE-2022-24771 (RSA padding oracle) — replace with native crypto module
  Estimated effort: 4 hours
  Affected code: src/lib/certificate.ts

For the full security audit methodology including OWASP Top 10 and CSP headers, see the security audit guide. For code review with security criteria embedded in the process, see the code review guide. The Claude Skills 360 bundle includes a security testing skill set for automated OWASP scanning, penetration testing patterns, and compliance checklists. Start with the free tier to run a security scan on your codebase.

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free