TL;DR

This tutorial shows you how to go from a raw OpenAPI spec to a runnable OWASP API Security Top 10 test suite using Claude Code's openapi-* skills. Each skill produces an artifact the next skill reads, so the pipeline is incremental , you can stop at any step, review output, and continue when ready

OWAS Top 10 API Test Case findings

Hurl Test Case ?

The skills generate test cases as Hurl files, plain-text HTTP test files that you run directly from the terminal

Four-stage illustration following a token in orange: login request, response containing the token, the capture block that stores it, and the next request that injects it into its header

Hurl is the right choice for API security testing because:

  • No runtime dependency , one binary, no Node.js, no Python virtualenv, no test framework bootstrap

  • Human-readable , each .hurl file is a readable sequence of HTTP request → expected response, making findings easy to review and share

  • Variables for chaining , [Captures] extracts values (tokens, IDs) from one response and injects them into the next request, so auth flows work automatically

Compare a login-then-access test in Hurl versus a custom script:

# Hurl: self-contained, no dependencies
POST {{base_url}}/api/login
Content-Type: application/json
{"username": "alice", "password": "alice123"}

HTTP 200
[Captures]
token: jsonpath "$.token"

GET {{base_url}}/api/getNote?username=bob
Authorization: Bearer {{token}}

HTTP *
[Asserts]
status != 200   # BOLA: should be 403, not 200

The same test in a custom script requires importing an HTTP client, writing auth boilerplate, and manually checking the response, typically 20+ lines. The Hurl version is 12 lines and runs with one command.

Installing the skills

To confirm they are available, type / in Claude Code and look for:

/openapi-parse
/openapi-api-dependency
/openapi-test-context
/openapi-owasp-hurl
/openapi-runner-hurl
/openapi-finding-reporting
/openapi-security-test

Installing Hurl

You must install the hurl binary separately before running tests:

Command: Send below command to terminal

# macOS
brew install hurl
# Linux (Debian/Ubuntu)
sudo apt install hurl
# Linux (via Cargo)
cargo install hurl
# Windows / manual download
# https://github.com/Orange-OpenSource/hurl/releases

Prerequisites

Before you start, confirm you have:

  • Claude Code running in the project directory

  • An OpenAPI spec file (openapi.json, swagger.json, or swagger.yaml) in the project root

  • hurl installed

  • The target API running locally or at a known base URL

Pipeline overview

The six skills run in sequence. Each step produces a file the next step reads:

Vertical flowchart of six pipeline steps; each step box has a side branch to the output file it writes, which the next step reads. Steps are color-coded as interactive, generation, or output

Step 1: Parsing the OpenAPI spec

Skill: /openapi-parse

Why you need this: Every downstream skill needs a clean, structured view of the API. Parsing once up front avoids re-reading the raw spec repeatedly and gives you a human-readable summary you can share with teammates before testing starts

Table-figure of six API endpoints with method, path, and auth; three rows are tinted and tagged as risks, with a callout explaining each below

Prompt: Send below prompt to Claude Code

/openapi-parse

The skill locates openapi.json in the project root, reads it, and writes openapi-summary.md

Example output (openapi-summary.md for DVAPI):

## Meta
- Title: DVAPI (Damn Vulnerable API)
- Version: 1.0.0
- Base URL: http://localhost:3000
- Auth schemes: bearerAuth (HTTP Bearer JWT)

## Endpoints

| Method | Path                 | Auth       | Summary                          |
|--------|----------------------|------------|----------------------------------|
| POST   | /api/register        | none       | Register a new user              |
| POST   | /api/login           | none       | Login and obtain JWT token       |
| GET    | /api/profile         | bearerAuth | Get own account details          |
| GET    | /api/getNote         | bearerAuth | Get saved note (query: username) |
| POST   | /api/addNoteWithLink | bearerAuth | Add note from external URL       |
| POST   | /api/getticket       | bearerAuth | Get ticket (admin only)          |

## Notable security observations
- /api/getNote takes a username query param ,  potential BOLA/IDOR
- /api/getticket described as admin-only but no admin scheme defined ,  BFLA issue
- /api/addNoteWithLink fetches a URL server-side ,  explicit SSRF vector

Step 2: Mapping API dependencies

Skill: /openapi-api-dependency

Why you need this: Security tests need to run in the right order , you must register and log in before calling protected endpoints. You also need to know which endpoints return IDs (tickets, resources) that other tests will use as input. Without this map, generated test files guess at the setup sequence and frequently fail due to missing tokens or resource IDs.

Dependency diagram showing public endpoints producing a token used by protected endpoints, and two chains where one endpoint’s output feeds another’s required input, marked in orange

Prompt: Send below prompt to Claude Code

/openapi-api-dependency

The skill reads openapi-summary.md and writes openapi-dependencies.md.

Example output:

## Auth Flow
1. POST /api/register ,  create test account (public)
2. POST /api/login    ,  obtain JWT token (public, returns: token)
3. All protected endpoints ,  pass token as Authorization: Bearer <token>

## Data Dependencies
- POST /api/getticket requires: ticketno
  (obtained from POST /api/addTicket response field: "your ticketId is :<number>")
- GET /api/user/{username} requires: username (path param ,  use known username)

## Recommended Test Session Setup Sequence
1. POST /api/register → {"username": "testuser", "password": "testpass"}
2. POST /api/login    → save token from response
3. Use token for all subsequent requests

Step 3: Collecting test context (interactive)

Skill: /openapi-test-context

Why you need this: The OWASP test cases need to know your actual tech stack to generate the right attack payloads. A MongoDB backend gets NoSQL injection probes; an AWS deployment gets SSRF probes targeting the metadata endpoint at 169.254.169.254. Generic test values without this context miss the most critical vulnerabilities for your specific stack.

Mapping figure: a context panel of stack answers on the left, three endpoint cards of generated payloads on the right, with the strongest input-to-payload link highlighted in orange

This is the only interactive step. The skill asks all questions in one message , you answer what you know and type unknown for anything you don't.

Prompt: Send below prompt to Claude Code

/openapi-test-context

Questions the skill asks (grouped):

Category

Example questions

Tech stack

Backend language/framework? Database type? Cloud provider?

Environment

Base URL? HTTPS enforced?

User accounts

Regular user credentials? Admin credentials?

Auth tokens

Paste a valid JWT, or leave blank to generate at runtime

Known resource IDs

Ticket numbers? User IDs to use in IDOR tests?

Constraints

Endpoints to avoid? Rate limits? Cleanup after tests?

Example answer session for DVAPI:

Tech stack: Node.js / Express
Database: MongoDB
Cloud: Docker / local
Base URL: http://localhost:3000
Regular user: alice / alice123
Admin user: admin
Known ticket: 104918
Constraints: none

What the skill generates from those answers (openapi-test-context.md excerpt):

### POST /api/login
**Attack values (MongoDB stack):**
- username: `""`, `null`, `{"$gt":""}`, `{"$regex":".*"}`, `"admin"`
- password: `""`, `null`, `"password"`, `"123456"`

### POST /api/addNoteWithLink
**Attack values:**
- url: `"http://127.0.0.1/"`, `"http://169.254.169.254/latest/meta-data/"`,
        `"file:///etc/passwd"`, `"dict://localhost:6379/INFO"`

### GET /api/getNote
**Attack values (BOLA probe):**
- username: own username (baseline), alice's username as bob (IDOR probe), `"admin"`

Step 4: Generating OWASP Hurl test files

Skill: /openapi-owasp-hurl

Why you need this: This skill translates all three context files into 10 concrete, runnable .hurl test files , one per OWASP API Security Top 10 2023 category. It spawns 10 subagents in parallel (one per category), so all 10 files are ready in roughly the time it takes to write one

Vertical anatomy of a test file; rows for setup, baseline, and attack probes, each tinted by role and tagged with an expected-status badge

Prompt: Send below prompt to Claude Code

/openapi-owasp-hurl

Output: 10 .hurl files in security_test_cases/, plus security_test_cases/vars.env.

File

OWASP category

api1_bola_idor.hurl

API1: Broken Object Level Authorization

api2_broken_authentication.hurl

API2: Broken Authentication

api3_broken_object_property_level_authorization.hurl

API3: Mass Assignment / Excessive Data Exposure

api4_unrestricted_resource_consumption.hurl

API4: Unrestricted Resource Consumption

api5_broken_function_level_authorization.hurl

API5: Broken Function Level Authorization

api6_unrestricted_business_flows.hurl

API6: Unrestricted Business Flows

api7_ssrf.hurl

API7: Server Side Request Forgery

api8_security_misconfiguration.hurl

API8: Security Misconfiguration

api9_improper_inventory_management.hurl

API9: Improper Inventory Management

api10_unsafe_consumption_of_apis.hurl

API10: Unsafe Consumption of APIs

Example , api1_bola_idor.hurl (BOLA/IDOR tests for DVAPI):

# ============================================================
# API1 ,  Broken Object Level Authorization (BOLA/IDOR)
# DVAPI ,  http://localhost:3000
# ============================================================

# Setup: register and login as user alice
POST {{base_url}}/api/register
Content-Type: application/json
{"username": "alice_test", "password": "alice123"}

HTTP *

POST {{base_url}}/api/login
Content-Type: application/json
{"username": "alice_test", "password": "alice123"}

HTTP 200
[Captures]
token_alice: jsonpath "$.token"

# Setup: register and login as user bob
POST {{base_url}}/api/register
Content-Type: application/json
{"username": "bob_test", "password": "bob123"}

HTTP *

POST {{base_url}}/api/login
Content-Type: application/json
{"username": "bob_test", "password": "bob123"}

HTTP 200
[Captures]
token_bob: jsonpath "$.token"

# Baseline: alice reads her own note (expect 200)
GET {{base_url}}/api/getNote?username=alice_test
Authorization: Bearer {{token_alice}}

HTTP 200
[Asserts]
jsonpath "$.status" == "success"

# BOLA probe: alice reads bob's note using bob's username in query param
# Expected: 403. If 200 → IDOR vulnerability confirmed.
GET {{base_url}}/api/getNote?username=bob_test
Authorization: Bearer {{token_alice}}

HTTP *
[Asserts]
status != 200   # VULNERABLE if 200: alice can read bob's note

# Vertical escalation: regular user reads admin's note
GET {{base_url}}/api/getNote?username=admin
Authorization: Bearer {{token_alice}}

HTTP *
[Asserts]
status != 200   # VULNERABLE if 200: regular user can read admin's data

# Unauthenticated access: no token
GET {{base_url}}/api/getNote?username=alice_test

HTTP 401

Example , api7_ssrf.hurl (SSRF tests for DVAPI):

# ============================================================
# API7 ,  Server Side Request Forgery (SSRF)
# DVAPI ,  POST /api/addNoteWithLink fetches remote URLs
# ============================================================

POST {{base_url}}/api/login
Content-Type: application/json
{"username": "{{username}}", "password": "{{password}}"}

HTTP 200
[Captures]
token: jsonpath "$.token"

# Baseline: benign external URL (expect 200)
POST {{base_url}}/api/addNoteWithLink
Authorization: Bearer {{token}}
Content-Type: application/json
{"url": "https://example.com/note.txt"}

HTTP 200

# SSRF probe: fetch localhost
# Expected: 400/403. If 200 with server content → SSRF confirmed.
POST {{base_url}}/api/addNoteWithLink
Authorization: Bearer {{token}}
Content-Type: application/json
{"url": "http://127.0.0.1/"}

HTTP *
[Asserts]
status != 200   # VULNERABLE if 200

# SSRF probe: AWS metadata endpoint
POST {{base_url}}/api/addNoteWithLink
Authorization: Bearer {{token}}
Content-Type: application/json
{"url": "http://169.254.169.254/latest/meta-data/"}

HTTP *
[Asserts]
status != 200           # VULNERABLE if 200
body not contains "ami-id"  # Critical: IAM credentials exposure

# SSRF probe: file:// scheme
POST {{base_url}}/api/addNoteWithLink
Authorization: Bearer {{token}}
Content-Type: application/json
{"url": "file:///etc/passwd"}

HTTP *
[Asserts]
status != 200
body not contains "root:"

Example , vars.env (generated alongside the .hurl files):

base_url=http://localhost:3000
username=alice_test
password=alice123
admin_username=admin
admin_password=FILL_ME_IN

Edit vars.env to fill in your real credentials before running.

Step 5: Running the tests

Skill: /openapi-runner-hurl

Why you need this: Running hurl manually for 10 files, interpreting exit codes, and distinguishing real vulnerabilities from test-setup errors takes judgment. This skill executes all files, reads the assertion errors, and categorizes each one , confirmed vulnerability, assertion mismatch, or setup issue , so you don't misreport a false positive.

Figure pairing an exit-code key with a results table; each category row shows a red FAIL or green PASS badge and a short finding description

Prompt: Send below prompt to Claude Code

/openapi-runner-hurl

The skill checks that hurl is installed, validates vars.env, runs all .hurl files, and prints a findings table.

You can also run tests manually without the skill:

Command: Send below command to terminal

# Run all categories
hurl --variables-file security_test_cases/vars.env \
     --test security_test_cases/*.hurl

# Run a single category
hurl --variables-file security_test_cases/vars.env \
     --test security_test_cases/api1_bola_idor.hurl

# Run with verbose output (shows full request/response for every entry)
hurl --variables-file security_test_cases/vars.env \
     --verbose security_test_cases/api7_ssrf.hurl

# Generate an HTML report
hurl --variables-file security_test_cases/vars.env \
     --report-html reports/hurl-security-report \
     security_test_cases/*.hurl
# Open: reports/hurl-security-report/index.html

Example findings table printed by /openapi-runner-hurl:

## OWASP API Security Test Results

| Category      | File                              | Result | Finding                                       |
|---------------|-----------------------------------|--------|-----------------------------------------------|
| API1 BOLA     | api1_bola_idor.hurl               | FAIL   | IDOR on GET /api/getNote?username= (got 200)  |
| API2 Auth     | api2_broken_authentication.hurl   | FAIL   | JWT alg:none accepted (got 200)               |
| API3 Mass Asgn| api3_broken_object_property...    | PASS   | ,                                              |
| API4 Rate Lmt | api4_unrestricted_resource...     | FAIL   | No 429 after 50 rapid login attempts          |
| API5 BFLA     | api5_broken_function_level...     | FAIL   | Regular user accessed /api/getticket (200)    |
| API7 SSRF     | api7_ssrf.hurl                    | FAIL   | SSRF via http://127.0.0.1/ returned 200       |
| API8 Misconfig| api8_security_misconfiguration... | FAIL   | Stack trace in 500 response body              |

Step 6: Generating the finding report

Skill: /openapi-finding-reporting

Why you need this: Raw hurl output lists assertion errors but doesn't explain severity, CVSS scores, or what to fix. This skill turns the findings table into a structured security report with evidence, remediation guidance, and severity ordering , ready to share with developers or include in a pentest report

Finding examples

Prompt: Send below prompt to Claude Code

/openapi-finding-reporting

The skill reads the findings from /openapi-runner-hurl's output and writes finding-report.md.

Example finding-report.md excerpt:

# Security Finding Report
**Target:** http://localhost:3000
**Test suite:** Hurl
**Generated:** 2026-10-10
**Total findings:** 5 confirmed, 1 manual review

---

## Summary

| Category | Severity | Status    |
|----------|----------|-----------|
| API1 ,  BOLA/IDOR          | High     | CONFIRMED |
| API2 ,  Broken Auth        | Critical | CONFIRMED |
| API5 ,  BFLA               | High     | CONFIRMED |
| API7 ,  SSRF               | Critical | CONFIRMED |
| API8 ,  Misconfiguration   | Medium   | CONFIRMED |

---

## Findings

### [API7] Server Side Request Forgery (SSRF)
**Severity:** Critical
**CVSS Base Score:** 9.1
**Affected endpoint:** `POST /api/addNoteWithLink`

**Description:**
The endpoint accepts a user-supplied URL and fetches it server-side without
validating the target. An attacker can direct the server to fetch internal
resources, cloud metadata endpoints, or local files.

**Evidence:**

Request:
```http
POST /api/addNoteWithLink HTTP/1.1
Authorization: Bearer <token>
Content-Type: application/json
{"url": "http://127.0.0.1/"}

Response:
{"status": "success", "note": "<!DOCTYPE html>..."}
```

Remediation:
Parse the URL before fetching; reject requests where the resolved IP falls
in RFC 1918 private ranges (10.x, 172.16-31.x, 192.168.x) or the link-local
range (169.254.x.x). Use an allowlist of permitted domains rather than a
denylist of blocked IPs.

Follow-Up Prompts

Once the initial test suite runs, these prompts extend coverage:

Regenerate a single OWASP category after fixing a finding:

Prompt: Send below prompt to Claude Code

/openapi-owasp-hurl
Re-generate only the api7_ssrf.hurl file. The endpoint POST /api/addNoteWithLink
now validates the URL scheme. Update the test file to add probes for DNS rebinding
and redirect-based SSRF bypass. Keep all existing probes.

Add custom test cases for a specific endpoint not covered by the OWASP templates:

Prompt: Send below prompt to Claude Code

Write a Hurl test file for POST /api/profile/upload that tests:
1. Valid JPEG upload (baseline, expect 200)
2. SVG file with embedded XSS payload
3. PHP webshell with .jpg extension
4. Zero-byte file
5. 50MB file (expect 413 or similar)
Save to security_test_cases/custom_file_upload.hurl.
Use {{base_url}}, {{token}}, and {{username}} variables from vars.env.

Rerun only the failed categories after a fix:

Prompt: Send below prompt to Claude Code

/openapi-runner-hurl
Run only api1_bola_idor.hurl and api7_ssrf.hurl. The fix for BOLA was merged
to main. Confirm whether the assertion at GET /api/getNote?username= now returns
403 instead of 200.

Further Reading

MCP Servers:

Skills & Agents:

Config Management:

Ready to apply AI to your Security Engineering ?

Subscribe to Secengai Newsletter for weekly actionable content on AI for security engineers.