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
.hurlfile is a readable sequence of HTTP request → expected response, making findings easy to review and shareVariables 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 200The 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
Download skill from: https://github.com/mfakbar127/agent-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-testInstalling 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/releasesPrerequisites
Before you start, confirm you have:
Claude Code running in the project directory
An OpenAPI spec file (
openapi.json,swagger.json, orswagger.yaml) in the project roothurlinstalledThe 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-parseThe 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 vectorStep 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-dependencyThe 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 requestsStep 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-contextQuestions 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: noneWhat 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-hurlOutput: 10 .hurl files in security_test_cases/, plus security_test_cases/vars.env.
File | OWASP category |
|---|---|
| API1: Broken Object Level Authorization |
| API2: Broken Authentication |
| API3: Mass Assignment / Excessive Data Exposure |
| API4: Unrestricted Resource Consumption |
| API5: Broken Function Level Authorization |
| API6: Unrestricted Business Flows |
| API7: Server Side Request Forgery |
| API8: Security Misconfiguration |
| API9: Improper Inventory Management |
| 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 401Example , 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_INEdit 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-hurlThe 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.htmlExample 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-reportingThe 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.
