AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Api Security Tester

skill-kalshamsi-claude-security-skills-api-security-tester · by kalshamsi

Use when auditing REST or GraphQL API endpoints, checking API authentication and authorization, hunting BOLA or broken access control, reviewing rate limiting, or covering the OWASP API Security Top 10.

No reviews yet
0 installs
11 views
0.0% view→install

Install

$ agentstack add skill-kalshamsi-claude-security-skills-api-security-tester

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-kalshamsi-claude-security-skills-api-security-tester)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Api Security Tester? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

API Security Tester

This skill performs static code analysis of REST and GraphQL API implementations for vulnerabilities mapped to the OWASP API Security Top 10:2023. It identifies 10 categories of API-specific security issues — broken authorization, authentication flaws, excessive data exposure, resource consumption abuse, SSRF, and more — across JavaScript/TypeScript (Express, Fastify, NestJS), Python (Flask, Django, FastAPI), Go (net/http, Gin), and Java (Spring Boot). Each finding is mapped to CWE and OWASP API Top 10:2023 standards with UNSAFE/SAFE code pairs for remediation.

When to Use

  • When the user asks to "audit API security", "review API endpoints", or "check for API vulnerabilities"
  • When the user mentions "OWASP API Top 10", "BOLA", "broken authorization", or "API authentication issues"
  • When reviewing REST API route handlers, GraphQL resolvers, or API middleware
  • When a pull request modifies API authentication, authorization, or input validation logic
  • When the user asks about "rate limiting", "mass assignment", "excessive data exposure", or "SSRF"
  • When scanning code that defines API routes (Express app.get, FastAPI @app.get, Spring @GetMapping, etc.)
  • When reviewing GraphQL schemas, resolvers, or query depth/complexity settings

When NOT to Use

  • When the user is asking about cryptographic implementation issues (use crypto-audit)
  • When the user wants live/runtime API penetration testing (use a DAST tool like nuclei or burp)
  • When reviewing general code quality unrelated to API security
  • When the user is asking about infrastructure-level security (use iac-scanner)
  • When the user wants container security scanning (use docker-scout-scanner)
  • When the user asks about TLS/cipher configuration — you MUST decline and recommend crypto-audit or security-headers-audit
  • When the user asks about XSS, CSRF, or OWASP Web Top 10 issues (not OWASP API Top 10) — you MUST decline, explain that this skill covers OWASP API Top 10:2023 only, and recommend security-review

Prerequisites

Tool Installed (Preferred)

No external tool required. This skill uses code analysis only.

All 10 checks are performed through pattern matching and code inspection of API route definitions, middleware, resolvers, and configuration files. No CLI tool needs to be installed, configured, or invoked.

Tool Not Installed (Fallback)

This skill is always available as a pure analysis skill. There is no fallback mode because there is no external tool dependency. All checks run directly through code analysis.

Workflow

  1. Detect project frameworks — Inspect project files to determine which API frameworks are in use:
  • JavaScript/TypeScript: package.json for express, fastify, @nestjs/core, apollo-server, graphql-yoga
  • Python: requirements.txt/pyproject.toml for flask, django, fastapi, graphene, strawberry-graphql
  • Go: go.mod for github.com/gin-gonic/gin, github.com/gorilla/mux, github.com/99designs/gqlgen
  • Java: pom.xml/build.gradle for spring-boot-starter-web, spring-boot-starter-graphql
  1. Identify API-relevant files — Search for files that define routes, controllers, resolvers, and middleware:
  • Route definitions: app.get(), app.post(), @app.route(), @GetMapping, router.Handle()
  • Middleware: app.use(), authentication/authorization decorators, guards, interceptors
  • GraphQL: schema definitions (.graphql, .gql), resolvers, type definitions
  • Configuration: CORS settings, rate limiter config, security headers
  1. Run the 10 OWASP API Security checks against each identified file (see Checks section below).
  2. For each finding:

a. Determine severity (Critical / High / Medium / Low) using the Reference Tables b. Map to the relevant CWE identifier c. Map to the relevant OWASP API Security Top 10:2023 category d. Record file path and line number e. Generate the UNSAFE pattern found and the corresponding SAFE fix f. Draft a remediation recommendation

  1. Deduplicate and sort findings by severity: Critical > High > Medium > Low.
  2. Generate the findings report using the Findings Format below.
  3. Summarize — State total findings, breakdown by severity, and top 3 remediation priorities.

Checks

Check 1: Broken Object Level Authorization (BOLA)

CWE-639 (Authorization Bypass Through User-Controlled Key) | API1:2023 | Severity: Critical

WHY: BOLA is the most prevalent API vulnerability. It occurs when an API endpoint accepts an object ID from the user (e.g., /api/users/123/orders) but does not verify that the authenticated user is authorized to access that specific object. Attackers simply change the ID to access other users' data.

UNSAFE:

// Express — no ownership check, any authenticated user can access any order
app.get('/api/orders/:orderId', authenticate, async (req, res) => {
  const order = await Order.findById(req.params.orderId);
  res.json(order); // No check that order belongs to req.user
});
# FastAPI — direct object access without ownership verification
@app.get("/api/users/{user_id}/profile")
async def get_profile(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    return user  # Any caller can access any user's profile
// Gin — no authorization check on resource ownership
func GetDocument(c *gin.Context) {
    docID := c.Param("id")
    doc, _ := db.FindDocument(docID)
    c.JSON(200, doc) // No check that doc belongs to authenticated user
}
// Spring Boot — fetches resource by ID without ownership check
@GetMapping("/api/accounts/{accountId}")
public Account getAccount(@PathVariable Long accountId) {
    return accountRepository.findById(accountId)
        .orElseThrow(() -> new NotFoundException("Account not found"));
    // No check that accountId belongs to the authenticated principal
}

SAFE:

// Verify object ownership before returning data
app.get('/api/orders/:orderId', authenticate, async (req, res) => {
  const order = await Order.findById(req.params.orderId);
  if (!order) return res.status(404).json({ error: 'Not found' });
  if (order.userId !== req.user.id) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  res.json(order);
});
# FastAPI — enforce ownership via query filter
@app.get("/api/users/{user_id}/profile")
async def get_profile(
    user_id: int,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db),
):
    if current_user.id != user_id and not current_user.is_admin:
        raise HTTPException(status_code=403, detail="Forbidden")
    user = db.query(User).filter(User.id == user_id).first()
    return user

Check 2: Broken Authentication

CWE-287 (Improper Authentication) | API2:2023 | Severity: Critical

WHY: Weak or missing authentication allows attackers to impersonate legitimate users. Common issues include endpoints with no authentication middleware, weak token validation, credentials sent over unencrypted channels, no brute-force protection on login, and tokens that never expire.

UNSAFE:

// Express — sensitive endpoint with no authentication middleware
app.get('/api/admin/users', async (req, res) => {
  const users = await User.find({});
  res.json(users); // No auth middleware — publicly accessible
});

// Weak JWT validation — no signature verification
app.use((req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  const decoded = jwt.decode(token); // decode WITHOUT verify!
  req.user = decoded;
  next();
});
# FastAPI — no authentication on sensitive endpoint
@app.delete("/api/users/{user_id}")
async def delete_user(user_id: int, db: Session = Depends(get_db)):
    db.query(User).filter(User.id == user_id).delete()
    db.commit()
    return {"status": "deleted"}  # No auth — anyone can delete users
// Spring Boot — security config disabling auth on sensitive paths
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/api/**").permitAll(); // All API endpoints open
    }
}

SAFE:

// Apply authentication middleware to all sensitive routes
const authenticate = require('./middleware/authenticate');

app.get('/api/admin/users', authenticate, requireRole('admin'), async (req, res) => {
  const users = await User.find({});
  res.json(users);
});

// Properly verify JWT tokens
app.use((req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET, {
      algorithms: ['HS256'],
      maxAge: '1h',
    });
    req.user = decoded;
    next();
  } catch (err) {
    res.status(401).json({ error: 'Invalid or expired token' });
  }
});
# FastAPI — require authentication via dependency
@app.delete("/api/users/{user_id}")
async def delete_user(
    user_id: int,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db),
):
    if not current_user.is_admin:
        raise HTTPException(status_code=403, detail="Admin required")
    db.query(User).filter(User.id == user_id).delete()
    db.commit()
    return {"status": "deleted"}

Check 3: Broken Object Property Level Authorization

CWE-213 (Exposure of Sensitive Information Due to Incompatible Policies) | API3:2023 | Severity: High

WHY: This category combines excessive data exposure and mass assignment. APIs often return entire database objects instead of only the fields the client needs, leaking sensitive properties (passwords, internal IDs, roles). Conversely, accepting unfiltered input allows attackers to modify properties they should not (e.g., setting role: "admin" via a profile update).

UNSAFE:

// Express — returns full user object including password hash and role
app.get('/api/users/:id', authenticate, async (req, res) => {
  const user = await User.findById(req.params.id);
  res.json(user); // Exposes password, role, internalNotes, etc.
});

// Mass assignment — spreads all body fields into update
app.put('/api/users/:id', authenticate, async (req, res) => {
  await User.findByIdAndUpdate(req.params.id, req.body); // Attacker can set role: "admin"
  res.json({ status: 'updated' });
});
# FastAPI — returns full ORM model (includes password_hash, is_admin)
@app.get("/api/users/{user_id}")
async def get_user(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    return user  # Serializes ALL columns including sensitive ones

# Mass assignment via **kwargs
@app.put("/api/users/{user_id}")
async def update_user(user_id: int, data: dict, db: Session = Depends(get_db)):
    db.query(User).filter(User.id == user_id).update(data)  # Any field can be overwritten
    db.commit()
// Spring Boot — returns entire entity
@GetMapping("/api/users/{id}")
public User getUser(@PathVariable Long id) {
    return userRepository.findById(id).orElseThrow(); // Exposes all fields
}

SAFE:

// Use a DTO/projection to return only safe fields
app.get('/api/users/:id', authenticate, async (req, res) => {
  const user = await User.findById(req.params.id).select('name email avatar');
  res.json(user);
});

// Whitelist allowed update fields
app.put('/api/users/:id', authenticate, async (req, res) => {
  const allowed = ['name', 'email', 'avatar'];
  const updates = Object.fromEntries(
    Object.entries(req.body).filter(([key]) => allowed.includes(key))
  );
  await User.findByIdAndUpdate(req.params.id, updates);
  res.json({ status: 'updated' });
});
# FastAPI — use Pydantic response model to control output
class UserResponse(BaseModel):
    id: int
    name: str
    email: str

class UserUpdate(BaseModel):
    name: Optional[str]
    email: Optional[str]
    # role and is_admin are NOT included — cannot be set by user

@app.get("/api/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int, db: Session = Depends(get_db)):
    return db.query(User).filter(User.id == user_id).first()

@app.put("/api/users/{user_id}")
async def update_user(user_id: int, data: UserUpdate, db: Session = Depends(get_db)):
    db.query(User).filter(User.id == user_id).update(data.dict(exclude_unset=True))
    db.commit()

Check 4: Unrestricted Resource Consumption

CWE-770 (Allocation of Resources Without Limits or Throttling) | API4:2023 | Severity: High

WHY: APIs without rate limiting, pagination limits, or request size constraints are vulnerable to denial-of-service attacks. Attackers can exhaust server resources by sending high-volume requests, requesting massive result sets, or uploading oversized payloads. GraphQL APIs are especially vulnerable to deep/complex query attacks.

UNSAFE:

// Express — no rate limiting, no pagination limit
app.get('/api/users', authenticate, async (req, res) => {
  const users = await User.find({}); // Returns ALL users — could be millions
  res.json(users);
});

// No request size limit
app.use(express.json()); // Default limit is 100kb but often overridden:
app.use(express.json({ limit: '500mb' })); // Dangerously large
# FastAPI — no pagination, no rate limiting
@app.get("/api/logs")
async def get_logs(db: Session = Depends(get_db)):
    return db.query(Log).all()  # Returns unbounded result set

# GraphQL — no query depth or complexity limits
schema = strawberry.Schema(query=Query)  # No max_depth or cost analysis
// Gin — no rate limiting
func GetAllRecords(c *gin.Context) {
    var records []Record
    db.Find(&records) // Unbounded query
    c.JSON(200, records)
}
// Spring Boot — no pagination
@GetMapping("/api/products")
public List getAllProducts() {
    return productRepository.findAll(); // Returns entire table
}

SAFE:

// Apply rate limiting and enforce pagination
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100 });
app.use('/api/', limiter);

app.get('/api/users', authenticate, async (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = Math.min(parseInt(req.query.limit) || 20, 100); // Cap at 100
  const users = await User.find({}).skip((page - 1) * limit).limit(limit);
  res.json({ data: users, page, limit });
});

// Sensible request size limit
app.use(express.json({ limit: '1mb' }));
# FastAPI — pagination with limits, rate limiting via middleware
@app.get("/api/logs")
async def get_logs(
    skip: int = Query(0, ge=0),
    limit: int = Query(20, ge=1, le=100),
    db: Session = Depends(get_db),
):
    return db.query(Log).offset(skip).limit(limit).all()

Check 5: Broken Function Level Authorization

CWE-285 (Improper Authorization) | API5:2023 | Severity: Critical

WHY: APIs often expose administrative functions alongside regular user functions. If access control checks are missing or inconsistent, a regular user can invoke admin-only operations by simply calling the endpoint directly (e.g., DELETE /api/users/123 or POST /api/admin/config). This is especially common when admin and user routes share a codebase.

UNSAFE:

// Express — admin endpoints without role checks
app.delete('/api/users/:id', authenticate, async (req, res) => {
  await User.findByIdAndDelete(req.params.id); // Any authenticated user can delete
  res.json({ status: 'deleted' });
});

app.post('/api/admin/settings', authenticate, async (req, res) => {
  await Settings.update(req.body);

…

## 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.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.