api-gate-keeper
Python-based API test harness for web application security and reliability checks.
Contents
- About
- Quick Start
- Advanced Usage
- Test Categories — for users interpreting what each check does
About
apptest is a CLI that discovers a web app's API surface from its live OpenAPI schema and runs a broad set of security and reliability checks against it — authentication robustness, injection, authorization/IDOR, JWT misuse, mass assignment, SSRF, rate limiting, and more — with safe-by-default execution and CI-friendly reporting (JSON, Markdown, JUnit, SARIF).
It's built for teams that want a repeatable, automatable security signal in CI/CD without standing up a full scanning platform.
What It Does
- Discovers API endpoints automatically from OpenAPI specs (or manually seeded paths)
- Supports multiple auth strategies:
auto,bearer/API key,basic, username/password login, ornone - Runs 20+ security test categories (auth, authz/BOLA, JWT, SSRF, mass assignment, schema conformance, workflow chains, soak/reliability profiling, and more)
- Classifies every check as
pass,fail,skipped, orerror— with conservative defaults to keep CI signal trustworthy - Emits JSON, Markdown (summary/detail/failures), JUnit XML, and SARIF reports for CI gating and dashboards
- Ships
quick/standard/strictprofiles so you can start fast and dial up coverage over time
Quick Start
First-time run should only require three choices:
- target URL (
--base-url) - auth type (API key, username/password, or no auth)
- profile (
quick,standard, orstrict)
60-Second Path
Use this if you just want a first successful run with minimal setup.
pip install api-gate-keeper
apptest --base-url https://your-target.example --api-key <your-api-key> --profile quick
api-gate-keeper is published on PyPI, so a virtualenv is optional (recommended, but not required) for a quick trial run.
Demo
$ apptest --base-url https://api.example.com --api-key $API_KEY --profile standard
Web App API Security Runner v0.1.0
Profile: standard (categories: auth_robustness, authz, baseline, bola_matrix,
contract_conformance, error_leakage_and_debug_surface, file_upload_security,
jwt_security, mass_assignment, owasp, pen_test, privilege_escalation,
rate_limit_and_resource_abuse, schema_contract_and_validation, security_misc,
soak_profile, sql_injection, ssrf_and_redirects, token_security,
workflow_chains)
Target: https://api.example.com
Unsafe execution disabled (POST/PUT/PATCH/DELETE will be skipped)
Run Summary
Passed: 664
Failed: 27
Skipped: 462
Errors: 0
JSON report: reports/api.example.com-<timestamp>-api-security-report.json
Summary report: reports/api.example.com-<timestamp>-api-security-summary.md
Detail report: reports/api.example.com-<timestamp>-api-security-detail.md
Failures report: reports/api.example.com-<timestamp>-api-security-failures.md
The failures report leads with a prioritized Remediation Plan table (High/Medium/Low, deduplicated by fix) before the per-test evidence — see Test Categories for what each finding category means and why it matters.
Installing From Source
If you're developing on the tool itself, or want the latest unreleased changes:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -e .
Recommended first command (safe defaults):
apptest --base-url https://your-target.example --api-key <your-api-key> --profile quick
If the target does not expose an OpenAPI spec, you can seed known endpoints manually:
apptest --base-url https://your-target.example --api-key <your-api-key> \
--seed-endpoint GET:/api/v1/resources.json \
--seed-endpoint GET:/api/v1/accounts.json \
--profile quick
Run multiple targets in one command (parallel):
apptest --targets https://service-a.example,https://service-b.example --api-key <your-api-key> --profile quick
Which Profile Should I Pick?
| Profile | Use this when | Typical first use |
|---|---|---|
quick |
Fast feedback and first-time validation | Local smoke run |
standard |
Broader routine coverage without aggressive behavior | Daily or pre-merge checks |
strict |
Deepest and most assertive coverage | Security hardening and release gates |
Auth alternatives:
# username/password
apptest --base-url https://your-target.example --username user@example.com --password secret --profile quick
# public endpoints only
apptest --base-url https://your-target.example --profile quick --config apptest.yaml
If you use the no-auth flow, set this in apptest.yaml:
auth:
type: none
Minimal starter apptest.yaml (onboarding-focused):
base_url: https://your-target.example
auth:
type: bearer
header: "Authorization: Bearer ${TARGET_TOKEN}"
profile: quick
Common First-Run Issues
- 401/403 responses on most checks: verify API key/token, or switch to username/password auth.
- Connection or DNS failures: confirm
--base-urlis reachable from your environment. - TLS/certificate errors in non-production environments: retry with
--insecureonly for test environments. - Very few tests executed: ensure auth is valid and avoid
auth.type: noneunless testing public endpoints.
Advanced Usage
Authentication
Supported auth modes:
Auto (default):
- Uses
--api-keyif provided. - Otherwise attempts login with
--username/--password.
Bearer / API key header:
apptest --base-url https://your-target.example --api-key <your-api-key>
Username + password login:
apptest --base-url https://your-target.example --username user@example.com --password secret
HTTP basic auth (via config):
auth:
type: basic
username: ${APP_USERNAME}
password: ${APP_PASSWORD}
No auth (public API checks only):
auth:
type: none
Browser-copied session headers / cookies:
apptest \
--base-url https://your-target.example \
--request-header 'X-Requested-With: XMLHttpRequest' \
--cookie-header 'sessionid=abc123; other_cookie=value' \
--profile quick
Environment variable fallback is supported (precedence: CLI args > env vars > built-in defaults):
APPTEST_BASE_URLAPPTEST_API_KEYAPPTEST_USERNAMEAPPTEST_PASSWORDAPPTEST_AUTH_TYPEAPPTEST_AUTH_HEADERAPPTEST_AUTH_USERNAMEAPPTEST_AUTH_PASSWORDAPPTEST_CATEGORIESAPPTEST_SOAK_SAMPLESAPPTEST_SOAK_MAX_5XX_RATEAPPTEST_SOAK_MAX_EXCEPTION_RATEAPPTEST_SOAK_MAX_P95_MS
Example using env vars only:
APPTEST_BASE_URL=https://your-target.example \
APPTEST_USERNAME=your-user@example.com \
APPTEST_PASSWORD=your-password \
apptest
Config File (apptest.yaml)
If apptest.yaml exists in the current directory, it is auto-loaded.
You can also pass a custom path with --config.
Precedence is:
- CLI args
- Environment variables
apptest.yaml- Built-in defaults
Example apptest.yaml:
base_url: https://your-target.example
auth:
type: bearer
header: "Authorization: Bearer ${TARGET_TOKEN}"
profile: quick
timeout: 20
output_dir: reports
include_unsafe: false
unsafe_categories: all
categories: all
soak_samples: 12
soak_max_5xx_rate: 0.15
soak_max_exception_rate: 0.05
soak_max_p95_ms: 2500
Auth modes for auth.type: auto, bearer, api_key, basic, none.
For bearer and api_key, set auth.header (for example Authorization: Bearer <token> or x-api-key: <key>).
For browser-driven apps that require session cookies or extra request headers, you can also set:
auth:
type: none
cookie_header: ${TARGET_COOKIE_HEADER}
extra_headers:
X-Requested-With: XMLHttpRequest
Accept: application/json
CLI --request-header values override auth.extra_headers entries with the same header name.
Run with auto-loaded config:
apptest
Run with explicit config path:
apptest --config ./apptest.yaml
Initialize a starter config file:
apptest init
Initialize at custom path:
apptest init --path ./config/apptest.yaml
Overwrite existing config:
apptest init --force
Default Runtime Parameters
- base URL:
https://your-target.example - auth mode:
auto - username:
your-user@example.com(used byautologin fallback) - password:
your-password(used byautologin fallback)
Example Commands
Run with defaults (safe mode — skips mutating methods):
apptest --base-url https://your-target.example
Run with profile presets:
# reduced category set, safe mode
apptest --profile quick --base-url https://your-target.example
# all categories, safe mode
apptest --profile standard --base-url https://your-target.example
# all categories, unsafe enabled by default
apptest --profile strict --base-url https://your-target.example
Run specific categories only:
apptest --base-url https://your-target.example --categories auth_robustness
apptest --base-url https://your-target.example --categories token_security
apptest --base-url https://your-target.example --categories privilege_escalation
apptest --base-url https://your-target.example --categories file_upload_security --include-unsafe --unsafe-categories file_upload_security
apptest --base-url https://your-target.example --categories ssrf_and_redirects --include-unsafe --unsafe-categories ssrf_and_redirects
apptest --base-url https://your-target.example --categories rate_limit_and_resource_abuse
apptest --base-url https://your-target.example --categories error_leakage_and_debug_surface
apptest --base-url https://your-target.example --categories mass_assignment --include-unsafe --unsafe-categories mass_assignment
apptest --base-url https://your-target.example --categories schema_contract_and_validation --include-unsafe --unsafe-categories schema_contract_and_validation
apptest --base-url https://your-target.example --categories jwt_security
apptest --base-url https://your-target.example --categories contract_conformance
apptest --base-url https://your-target.example --categories bola_matrix
apptest --base-url https://your-target.example --categories workflow_chains --include-unsafe --unsafe-categories workflow_chains
apptest --base-url https://your-target.example --categories soak_profile
apptest --base-url https://your-target.example --categories baseline,owasp,authz
Tune soak profile thresholds/samples (CLI):
apptest \
--base-url https://your-target.example \
--categories soak_profile \
--soak-samples 8 \
--soak-max-5xx-rate 0.25 \
--soak-max-exception-rate 0.10 \
--soak-max-p95-ms 3500
Soak tuning precedence is:
- CLI flags
- environment variables
- config file keys
- category defaults
Tune soak profile thresholds via environment variables:
APPTEST_SOAK_SAMPLES=8 \
APPTEST_SOAK_MAX_5XX_RATE=0.25 \
APPTEST_SOAK_MAX_EXCEPTION_RATE=0.10 \
APPTEST_SOAK_MAX_P95_MS=3500 \
apptest --base-url https://your-target.example --categories soak_profile
Run with API key instead of username/password:
apptest --base-url https://your-target.example --api-key <your-api-key>
Run with explicit auth header from config/env:
APPTEST_AUTH_TYPE=bearer \
APPTEST_AUTH_HEADER="Authorization: Bearer <token>" \
apptest --base-url https://your-target.example
Run with custom target and creds:
apptest --base-url https://your-target.example --username your-user --password your-pass
Run with unsafe methods enabled for specific categories:
apptest --base-url https://your-target.example --include-unsafe --unsafe-categories baseline
Run with all categories fully unlocked:
apptest --base-url https://your-target.example --include-unsafe --unsafe-categories all
Disable TLS verification (only for test environments):
apptest --base-url https://your-target.example --insecure
Fail CI when findings are present:
apptest --base-url https://your-target.example --fail-on-findings
Fail CI only when thresholds are exceeded:
apptest --base-url https://your-target.example --max-failed 0 --max-errors 0
Generate JUnit XML output for CI test dashboards:
apptest --base-url https://your-target.example --junit-out reports/apptest-junit.xml
Generate SARIF output for GitHub code scanning:
apptest --base-url https://your-target.example --sarif-out reports/apptest.sarif.json
Write reports to explicit paths:
apptest \
--base-url https://your-target.example \
--categories auth_robustness \
--json-out reports/auth_robustness.json \
--md-summary-out reports/auth_robustness.summary.md \
--md-detail-out reports/auth_robustness.detail.md \
--sarif-out reports/auth_robustness.sarif.json
Upload SARIF to GitHub Code Scanning:
- name: Run apptest with SARIF
run: |
apptest \
--base-url "$APPTEST_BASE_URL" \
--api-key "$APPTEST_API_KEY" \
--sarif-out reports/apptest.sarif.json
- name: Upload SARIF to GitHub Code Scanning
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: reports/apptest.sarif.json
Output
Reports are written to reports/ — four files per run, prefixed with the target hostname:
<target-host>-<timestamp>-api-security-report.json— raw results<target-host>-<timestamp>-api-security-summary.md— per-category totals<target-host>-<timestamp>-api-security-detail.md— per-test detail table with PASS/FAIL/SKIP<target-host>-<timestamp>-api-security-failures.md— failures/errors only, with a prioritized Remediation Plan summary followed by the detail table
Categories Implemented
- baseline API execution
- penetration payload probes
- SQL injection probes
- OWASP-oriented checks
- additional security checks (CORS, basic rate probe)
- authorization and IDOR checks
- authentication robustness checks (invalid credential rejection, failed-login throttle signal)
- token security checks (malformed token, tampered token, missing Bearer prefix; skipped when bearer token auth is not in use)
- privilege escalation checks (likely admin endpoint access with current token)
- file upload security checks (multipart endpoint discovery, unsafe filename traversal and absolute path probes)
- SSRF and redirect checks (ServiceNow instance URL rejection for internal targets, redirect-style parameter discovery)
- rate-limit and resource-abuse checks (burst probes and oversized pagination or time-window requests)
- error leakage and debug surface checks (debug route exposure, unauthenticated debug access, stack-trace/internal error leakage markers)
- mass assignment checks (sensitive writable fields on authenticated JSON mutation endpoints)
- schema contract and validation checks (missing required fields, wrong types, unknown fields)
- JWT-specific token misuse checks (
alg:none, stripped signatures, futurenbf,kidabuse patterns) - response contract conformance checks (status-code drift, undocumented response fields, missing required response fields)
- BOLA matrix checks with harvested live IDs (neighbor ID probes and cross-service ID references)
- workflow lifecycle chain checks (create-read-update-delete flow health and auth enforcement)
- soak profile checks on safe endpoints (repeated request stability and latency thresholds; auth-required endpoints with no successful responses are classified as skipped to reduce false positives)
GitHub Actions Integration
Sample workflow (.github/workflows/apptest-scan.yml):
name: Web App Security Scan (apptest)
on:
pull_request:
branches: [ main, develop ]
push:
branches: [ main, develop ]
workflow_dispatch:
inputs:
base_url:
description: "Target web app base URL"
required: false
default: "https://your-target.example"
jobs:
apptest-scan:
runs-on: ubuntu-latest
timeout-minutes: 30
env:
DEFAULT_BASE_URL: https://your-target.example
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install apptest
run: |
python -m pip install --upgrade pip
pip install -e .
- name: Resolve target URL
id: target
shell: bash
run: |
if [ -n "${{ github.event.inputs.base_url }}" ]; then
echo "base_url=${{ github.event.inputs.base_url }}" >> "$GITHUB_OUTPUT"
elif [ -n "${{ vars.APPTEST_BASE_URL }}" ]; then
echo "base_url=${{ vars.APPTEST_BASE_URL }}" >> "$GITHUB_OUTPUT"
else
echo "base_url=${DEFAULT_BASE_URL}" >> "$GITHUB_OUTPUT"
fi
- name: Run apptest (API key preferred)
shell: bash
env:
APPTEST_API_KEY: ${{ secrets.APPTEST_API_KEY }}
APPTEST_USERNAME: ${{ secrets.APPTEST_USERNAME }}
APPTEST_PASSWORD: ${{ secrets.APPTEST_PASSWORD }}
run: |
set -euo pipefail
BASE_URL="${{ steps.target.outputs.base_url }}"
if [ -n "${APPTEST_API_KEY:-}" ]; then
apptest \
--base-url "$BASE_URL" \
--api-key "$APPTEST_API_KEY" \
--include-unsafe \
--unsafe-categories baseline
else
apptest \
--base-url "$BASE_URL" \
--username "${APPTEST_USERNAME}" \
--password "${APPTEST_PASSWORD}" \
--include-unsafe \
--unsafe-categories baseline
fi
- name: Enforce gate from JSON report
shell: bash
run: |
set -euo pipefail
python - <<'PY'
import json, glob, sys
report = sorted(glob.glob("reports/*-api-security-report.json"))[-1]
data = json.load(open(report))
failed = data.get("summary", {}).get("failed", 0)
errors = data.get("summary", {}).get("errors", 0)
print(f"Gate check: failed={failed}, errors={errors}")
if failed > 0 or errors > 0:
sys.exit(1)
PY
- name: Upload reports
if: always()
uses: actions/upload-artifact@v4
with:
name: apptest-reports
path: reports/
Required repository configuration:
- Secret
APPTEST_API_KEY(recommended), or bothAPPTEST_USERNAMEandAPPTEST_PASSWORD - Optional repository variable
APPTEST_BASE_URL
Simple Workflow (URL + Username + Password Only)
Use this minimal option if you want the easiest integration path.
name: apptest-simple
on:
workflow_dispatch:
pull_request:
branches: [ main ]
jobs:
apptest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install
run: |
python -m pip install --upgrade pip
pip install -e .
- name: Run apptest
env:
APPTEST_BASE_URL: ${{ secrets.APPTEST_BASE_URL }}
APPTEST_USERNAME: ${{ secrets.APPTEST_USERNAME }}
APPTEST_PASSWORD: ${{ secrets.APPTEST_PASSWORD }}
run: |
apptest \
--base-url "$APPTEST_BASE_URL" \
--username "$APPTEST_USERNAME" \
--password "$APPTEST_PASSWORD"
- name: Upload reports
if: always()
uses: actions/upload-artifact@v4
with:
name: apptest-reports
path: reports/
Required secrets for the simple workflow:
APPTEST_BASE_URLAPPTEST_USERNAMEAPPTEST_PASSWORD
Test Categories
Every test category in API Gate Keeper: what it checks, how it works, and why it matters. Each section ends with a Test Matrix table listing the exact runtime test_name identifiers, trigger/scope, expected secure outcome, and implementation notes.
1. Baseline
Category name: baseline
What it is
A smoke-test pass over every discovered API endpoint using the configured authentication credentials, with no payload injection or modification.
What it does
- Iterates every endpoint discovered from the OpenAPI schemas across all configured services.
- Fires each endpoint exactly as documented — correct method, a synthetic-but-valid sample body where needed, and live auth headers.
- Records the raw status code, response time, and any error conditions.
Why we need it
Baseline validates that the service is reachable, auth is working, and the test harness can communicate correctly before any attack-surface probes run. It also surfaces endpoints that return 5xx errors under normal conditions — a sign of pre-existing instability that should be investigated regardless of security intent. Without a baseline pass, failures in other categories are harder to interpret.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
baseline_request |
All discovered endpoints | No server-side failure under normal request construction | Unsafe methods are skipped unless unsafe execution is enabled for the category |
2. OWASP Core Controls
Category name: owasp
What it is
Checks for two fundamental OWASP API Security Top 10 controls: missing HTTP security headers and unauthenticated access to authenticated endpoints.
What it does
Security headers check (security_headers_check):
- Selects up to 6 representative GET endpoints (preferring public paths like
/health,/openapi,/docs). - Fires a GET and inspects the response headers for the following required headers:
X-Frame-Options— prevents clickjacking.X-Content-Type-Options— prevents MIME sniffing.Content-Security-Policy— restricts resource loading.Strict-Transport-Security— enforces HTTPS.
- Fails if any of these headers are missing from responses.
Authentication requirement check (auth_required_check):
- For every endpoint marked as requiring authentication in the OpenAPI spec, fires the request with no
Authorizationheader. - Expects a 401 or 403 response. A 2xx response is a failure — the endpoint is openly accessible without credentials.
Why we need it
Missing security headers are low-effort wins for attackers. Absent X-Frame-Options enables UI redressing attacks. Absent Content-Security-Policy expands XSS blast radius. Absent Strict-Transport-Security allows downgrade attacks on HTTPS enforcement. These headers are easily added and have no performance cost; their absence is always a regression.
The auth requirement check catches endpoints that slip past authentication middleware — a top-ranked OWASP API Security vulnerability (API2 — Broken Authentication). Even a single authenticated endpoint accidentally exposed without checks can be a critical finding.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
security_headers_check |
Representative GET endpoints | Required security headers are present | Missing headers are reported as failures |
auth_required_check |
Endpoints marked requires_auth |
Unauthenticated requests are denied (401/403) | A 2xx without auth is a failure |
3. SQL Injection
Category name: sql_injection
What it is
Injects classic SQL injection payloads into every discovered endpoint to detect whether the API reflects or processes raw SQL input without sanitization.
What it does
- Applies two representative injection strings to every discovered endpoint:
' OR '1'='1— boolean-based injection that tests if the WHERE clause is bypassable.admin' --— comment-based injection that tests if a login or filter query can be truncated.
- Passes each payload as the
attack_payloadto the shared executor, which injects it into available query parameters and body fields. - A 2xx response containing reflected payload content, or unexpected data being returned, is a failure signal.
Why we need it
SQL injection remains one of the most impactful and frequently exploited API vulnerabilities (OWASP A03 — Injection). A single unsanitized input can expose entire database tables, bypass authentication, or enable data destruction. APIs that handle filter parameters, search queries, or ID lookups are particularly at risk. Even when an ORM is used, raw query construction or improper parameterization in edge cases can re-introduce the vulnerability.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
sqli_payload:' OR '1'='1 |
All discovered endpoints with injected payload | Payload is rejected or safely handled | Parameterized SQLi variant |
sqli_payload:admin' -- |
All discovered endpoints with injected payload | Payload is rejected or safely handled | Comment-truncation SQLi variant |
4. Penetration Test Payloads
Category name: pen_test
What it is
A broader injection sweep covering path traversal, command injection, and cross-site scripting (XSS) payloads across every endpoint.
What it does
Applies three payloads to every discovered endpoint:
| Payload | Threat |
|---|---|
../../../../etc/passwd |
Path/directory traversal — attempts to read files outside the web root |
$(cat /etc/passwd) |
Command injection — attempts OS command execution via shell substitution |
<script>alert('xss')</script> |
Reflected XSS — tests if user-supplied markup is reflected unescaped |
Each payload is injected into query parameters and request bodies via the shared executor's attack_payload mechanism. Responses are checked for reflection or unexpected execution signals.
Why we need it
APIs that accept filenames, paths, or shell-like strings in parameters are vulnerable to path traversal and command injection, which can lead to server compromise. APIs that reflect input into responses (especially in error messages or rich outputs) are vulnerable to XSS, which can enable session hijacking in browser-based clients. This category catches the broadest injection attack classes beyond SQL with minimal probe count.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
pentest_payload:../../../../etc/passwd |
All discovered endpoints with payload injection | Traversal payload is rejected or sanitized | Path traversal probe |
pentest_payload:$(cat /etc/passwd) |
All discovered endpoints with payload injection | Command-like payload is not executed/reflected unsafely | Command injection probe |
pentest_payload:<script>alert('xss')</script> |
All discovered endpoints with payload injection | Script payload is encoded/rejected | Reflected XSS probe |
5. Security Miscellaneous
Category name: security_misc
What it is
Targeted checks for two frequently overlooked but critical controls: overly permissive CORS configuration and basic rate-limit health on the most stable endpoint.
What it does
CORS wildcard check (cors_wildcard_check):
- Sends an OPTIONS preflight request to the login endpoint (
/api/data/auth/login) withOrigin: https://evil.example. - Fails if the server responds with
Access-Control-Allow-Origin: *— indicating any origin can make cross-origin requests, including malicious ones.
Basic rate probe (basic_rate_probe):
- Fires 5 rapid GET requests to the first discovered
/healthendpoint. - Passes if all return non-5xx. A 5xx burst result indicates instability in the healthcheck path, which downstream health monitoring relies on.
Why we need it
A wildcard CORS policy on an authentication endpoint is a critical misconfiguration — it allows arbitrary web pages to make authenticated API calls on behalf of any user (CSRF/CORS abuse). This is especially damaging for APIs that use cookie-based or implicit-flow auth. The rate probe provides a confidence check that the service is robustly responsive under even trivial load, which is a prerequisite for trusting other category results.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
cors_wildcard_check |
Login preflight (OPTIONS) |
No wildcard Access-Control-Allow-Origin on sensitive auth flow |
* is a failure |
basic_rate_probe |
First discovered health endpoint | No 5xx under short rapid sequence | Lightweight stability signal |
6. Authorization (AuthZ)
Category name: authz
What it is
Verifies two fundamental authorization controls: unauthenticated access blocking and Insecure Direct Object Reference (IDOR) resistance.
What it does
Unauthenticated access check (authz_unauthenticated_access_check):
- For every authenticated endpoint, fires the request with no
Authorizationheader. - Expects 401 or 403. A 2xx is a failure.
IDOR path tamper check (idor_path_tamper_check):
- For each endpoint whose URL path contains parameters with ID-like names (
id,user,account,tenant,org,customer,project), replaces all such parameters with the synthetic value999999. - Fires the request with the current user's valid token.
- Expects 401, 403, or 404 — the resource doesn't belong to this user or doesn't exist.
- A 2xx response is a potential IDOR: an authenticated user is accessing a resource that likely belongs to another tenant or user.
Why we need it
IDOR is the top-ranked OWASP API Security vulnerability (API1 — Broken Object Level Authorization). It occurs when an API uses predictable IDs in paths but doesn't verify that the requesting user actually owns the referenced resource. Tenants accessing other tenants' data, users modifying other users' records, or any cross-account data leak all stem from IDOR. This category catches horizontal privilege escalation — one of the highest-impact class of findings in multi-tenant SaaS APIs.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
authz_unauthenticated_access_check |
Endpoints marked requires_auth |
Unauthenticated access is denied | Uses no auth header |
idor_path_tamper_check |
Auth-required ID-like path endpoints | Tampered object access is denied/hidden | Path params replaced with synthetic ID |
7. Authentication Robustness
Category name: auth_robustness
What it is
Validates that the login/authentication endpoint correctly rejects invalid credentials and shows signs of throttling on repeated failures.
What it does
Invalid credentials rejection (invalid_credentials_rejection_check):
- Locates the login endpoint by path/operation name heuristics (
login,signin,auth,token,session). - Inspects its JSON body schema to construct a realistic-looking but invalid payload (wrong email format + clearly wrong password).
- Fires the POST. Expects 400, 401, 403, 422, or 429. A 2xx is a failure — the API must not accept invalid credentials.
Failed login throttle signal (failed_login_throttle_signal_check):
- Sends 5 invalid login requests in rapid succession.
- Passes if any 429 response is observed, indicating rate limiting on authentication failures.
- Marks as skipped if no throttle signal is seen (absence of 429 is informational, not a hard failure, since some implementations use CAPTCHA or delay strategies instead).
Why we need it
An authentication endpoint that accepts invalid credentials is a critical vulnerability. Login throttling is a key defense against credential stuffing and brute-force attacks — without it, attackers can iterate millions of credential combinations until they succeed. OWASP API Security API2 (Broken Authentication) specifically identifies missing brute-force protection as a top risk. This category validates that the most sensitive endpoint in the API enforces minimum authentication hygiene.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
invalid_credentials_rejection_check |
Discovered login-like endpoint | Invalid credentials are rejected (non-2xx) | Primary auth hardening check |
failed_login_throttle_signal_check |
Repeated invalid login attempts | Throttle/lockout signal appears or absence is clearly marked | 429 is pass; no signal is informational skip |
auth_endpoint_discovery |
Category startup | Login-like endpoint exists for checks | Skipped if no suitable endpoint is discovered |
8. Token Security
Category name: token_security
What it is
Validates that the API correctly rejects malformed, tampered, and improperly formatted bearer tokens. This category only executes token-manipulation probes when runtime auth provides a bearer token context.
What it does
For up to 20 authenticated GET/HEAD/OPTIONS endpoints:
| Test | What it sends | Expected result |
|---|---|---|
malformed_bearer_token_check |
Authorization: Bearer not-a-valid-bearer-token |
400/401/403 |
tampered_bearer_token_check |
The live token with its last character changed | 400/401/403 |
missing_bearer_prefix_check |
The raw token string with no Bearer prefix |
400/401/403 |
jwt_format_detection |
N/A — skipped if token is not JWT-shaped | Informational |
A 2xx for any of the above is a failure.
If bearer token context is unavailable (for example auth.type: basic or auth.type: none), the category emits a skipped result explaining that bearer-token checks are not applicable for the current auth mode.
Why we need it
Token validation is a core part of API security. Implementations that accept tampered or structurally invalid tokens may be skipping signature verification, relying only on token presence rather than validity, or delegating validation inconsistently across services. A tampered token that returns 200 is a critical finding — it means the API is not verifying token integrity. These checks catch implementation bugs in JWT middleware, token parsing shortcuts, and missing validation paths that don't surface in happy-path testing.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
malformed_bearer_token_check |
Safe auth-required endpoints | Malformed bearer token is rejected | 400/401/403 expected |
tampered_bearer_token_check |
Safe auth-required endpoints | Tampered bearer token is rejected | Last token char is modified |
missing_bearer_prefix_check |
Safe auth-required endpoints | Improper auth header format is rejected | Missing Bearer prefix |
jwt_format_detection |
Token-shape check | JWT/non-JWT applicability is identified | Informational skip when token is not JWT-shaped |
token_security_token_presence |
Category startup | Bearer token context exists | Skipped in non-bearer auth modes |
token_security_target_discovery |
Category startup | Suitable endpoints are available for token probes | Skipped if no targets found |
9. Privilege Escalation
Category name: privilege_escalation
What it is
Checks whether a non-admin user can access admin-designated API endpoints using their standard token.
What it does
- Locates authenticated GET/HEAD/OPTIONS endpoints whose path or operation name contains admin-hint keywords:
admin,role,permission,rbac,tenant-admin,superuser. - Decodes the live JWT payload (without signature verification) and inspects role/permissions claims. If the current token already appears to be admin-privileged, all checks are skipped to avoid false positives.
- For non-admin tokens: fires each admin-hint endpoint. Expects 401, 403, or 404. A 2xx is a failure — a non-admin user reached an admin resource.
Why we need it
Vertical privilege escalation — a non-admin user gaining access to admin functionality — is a critical class of authorization failure (OWASP API5 — Broken Function Level Authorization). Admin endpoints frequently expose bulk operations, tenant management, user management, configuration changes, or data exports that can be catastrophically abused. This category checks that role-based access control is enforced at the endpoint level, not just in the UI layer.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
privileged_endpoint_access_check |
Admin-hint safe endpoints | Non-admin token cannot access privileged routes | If current token appears admin, category skips to avoid false positives |
10. File Upload Security
Category name: file_upload_security
What it is
Tests that file upload endpoints reject filenames that attempt to write outside the intended directory using path traversal or absolute path techniques.
What it does
For each discovered multipart/form-data upload endpoint (up to 10):
| Test | Filename used | Attack it mimics |
|---|---|---|
upload_path_traversal_filename_check |
../../../../tmp/__apptest_upload_traversal__.py |
Writing outside the upload directory via ../ sequences |
upload_absolute_path_filename_check |
/etc/__apptest_upload_absolute__.py |
Overwriting system files via absolute path |
Both tests first resolve a live application_id from the assets API to ensure the request has a valid context. The file content is a harmless Python print statement. Expects 400/403/404/413/415/422. A 2xx response with the traversal filename accepted is a failure.
Why we need it
Unvalidated file upload paths are a server-side path traversal vector. If a server uses the client-supplied filename to construct a filesystem path and doesn't sanitize it, an attacker can write arbitrary files to any location the process has write access to — including overwriting server configuration, planting web shells, or corrupting system files. File upload endpoints are also often more permissive by design (they expect binary input), making them a common gap in API input validation.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
upload_path_traversal_filename_check |
Authenticated multipart upload endpoints | Traversal filename is rejected/sanitized | Uses ../../.. filename variant |
upload_absolute_path_filename_check |
Authenticated multipart upload endpoints | Absolute-path filename is rejected/sanitized | Uses /etc/... filename variant |
file_upload_target_discovery |
Category startup | Upload endpoints are discoverable | Skipped when no multipart targets exist |
file_upload_application_context_discovery |
Category startup | Required application_id context is discoverable |
Skipped when upload context cannot be resolved |
11. Mass Assignment
Category name: mass_assignment
What it is
Tests whether API mutation endpoints silently accept and apply privileged fields that are not documented in their request schema.
What it does
For each authenticated POST/PUT/PATCH endpoint with a JSON body schema (up to 20):
-
Builds a valid sample payload from the declared schema properties.
-
Injects additional fields that are not in the schema but represent privileged intent:
Injected field Sentinel value role__apptest_mass_assignment_role__roles["__apptest_mass_assignment_roles__"]is_admintrueadmintruetenant_id__apptest_mass_assignment_tenant__owner_id__apptest_mass_assignment_owner__permissions["__apptest_mass_assignment_permissions__"]status__apptest_mass_assignment_status__user_id__apptest_mass_assignment_user__organization_id__apptest_mass_assignment_org__ -
If the response is 2xx and any sentinel value is reflected in the response body, it is a fail — the server accepted and likely stored the injected field.
-
If the response is 2xx but no sentinel is reflected, it is skipped — the field may have been silently dropped, which is safer but still informational.
-
If the response is 4xx/422, it is a pass — the API correctly rejected undeclared fields.
Why we need it
Mass assignment (OWASP API3 — Broken Object Property Level Authorization) occurs when an API automatically binds all properties in a request body to the underlying data model without filtering. An attacker can set is_admin: true or role: admin on their own profile, elevate their privileges, or corrupt data ownership. This is especially common in frameworks that auto-bind request bodies to ORM models (Django REST Framework, ActiveRecord, etc.) when allowlisting is not explicitly configured.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
mass_assignment_sensitive_field_probe |
Authenticated JSON mutation endpoints | Sensitive undeclared fields are rejected or safely ignored | Reflection of sentinel values is a failure signal |
mass_assignment_target_discovery |
Category startup | Suitable JSON mutation endpoints are discoverable | Skipped when no valid targets exist |
12. Schema Contract and Validation
Category name: schema_contract_and_validation
What it is
Validates the strength of server-side input validation by testing how mutation endpoints respond to missing required fields, wrong-type values, and completely undocumented fields.
What it does
For each authenticated POST/PUT/PATCH endpoint with a JSON body schema (up to 20):
| Test | What is sent | Expected |
|---|---|---|
required_field_validation_check |
Valid payload with the first required field removed |
400/422 rejection |
wrong_type_validation_check |
Valid payload with the first typed field replaced by the wrong type (e.g., string "wrong-type" for an integer field) |
400/422 rejection |
unknown_field_handling_check |
Valid payload with an extra __apptest_unknown_field__: "__apptest_unknown_value__" injected |
400/422 preferred; 2xx with no reflection is skipped; 2xx with reflection is fail |
Why we need it
APIs that silently accept missing required fields, wrong types, or completely unknown fields signal weak or absent server-side validation. This creates several risks:
- Data integrity: Incomplete objects stored without required fields corrupt database state and downstream processing.
- Type confusion: Wrong types in business logic fields (e.g., passing a string where a count is expected) can cause unexpected behavior, bypasses, or crashes.
- Information leakage: Unknown fields accepted and reflected in responses reveal that the API is not using a strict allowlist, which means mass assignment defenses are likely also absent.
Robust schema validation is also a prerequisite for reliable API contracts — a poorly validating API is harder to maintain and extend safely.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
required_field_validation_check |
Authenticated JSON mutation endpoints | Missing required fields are rejected | 400/422 style validation expected |
wrong_type_validation_check |
Authenticated JSON mutation endpoints | Wrong-type field values are rejected | Type confusion defense check |
unknown_field_handling_check |
Authenticated JSON mutation endpoints | Unknown fields are rejected or safely ignored | Reflection of unknown field token is a failure signal |
schema_validation_target_discovery |
Category startup | Suitable JSON mutation endpoints are discoverable | Skipped when no valid targets exist |
13. SSRF and Redirects
Category name: ssrf_and_redirects
What it is
Tests whether the API accepts external URLs that point to private network addresses or cloud metadata endpoints — the primary SSRF (Server-Side Request Forgery) attack vectors.
What it does
Private address rejection (ssrf_private_address_rejection_check):
- Sends
PATCH /api/data/integrations/servicenow/instance-urlwithinstance_url: http://127.0.0.1:80/__apptest_ssrf_private__. - This simulates an attacker configuring an integration to point back at the server's own loopback interface.
- Fetches the current
instance_urlbefore the probe and restores it after if unsafe methods are enabled. - Expects rejection (400/401/403/404/422). A 2xx indicates the server accepted the private address.
Cloud metadata address rejection (ssrf_metadata_address_rejection_check):
- Same flow, but uses
http://169.254.169.254/latest/meta-data/— the AWS EC2 Instance Metadata Service (IMDS) address. - A server that fetches this URL on behalf of a user request can expose cloud credentials, IAM roles, and instance configuration.
Redirect target discovery (redirect_target_discovery):
- Scans all discovered endpoint parameters and JSON body fields for redirect-style names (
redirect,redirect_uri,return_url,return_to,callback_url,next). - Reports count for analyst awareness without actively probing open redirect behavior (informational).
Why we need it
SSRF is ranked OWASP API7 and is consistently exploited in cloud environments. Integration management endpoints that accept arbitrary URLs are the most common SSRF entry point in SaaS APIs. If an integration platform fetches the attacker-supplied URL server-side, the attacker can:
- Reach internal services not exposed to the internet.
- Access cloud instance metadata to steal IAM credentials.
- Pivot to other internal APIs and databases.
In a multi-tenant platform, SSRF via integration URL configuration is a high-severity vector because it bypasses both tenant isolation and network perimeter controls.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
ssrf_private_address_rejection_check |
ServiceNow instance URL mutation flow | Private/loopback target is rejected | Unsafe mutation flow |
ssrf_metadata_address_rejection_check |
ServiceNow instance URL mutation flow | Cloud metadata target is rejected | Unsafe mutation flow |
redirect_target_discovery |
Endpoint parameter/schema scan | Redirect-style targets are inventoried | Informational discovery check |
ssrf_target_discovery |
Category startup | SSRF-capable mutation target is discoverable | Skipped when no supported target exists |
ssrf_instance_url_context_discovery |
Category startup | Restorable integration context is discoverable | Skipped when safe restore context is unavailable |
14. Rate Limit and Resource Abuse
Category name: rate_limit_and_resource_abuse
What it is
Tests whether the API enforces rate limiting under burst conditions and rejects requests that specify absurdly large resource windows.
What it does
Burst rate limit signal (burst_rate_limit_signal_check):
- Selects up to 6 qualifying authenticated GET endpoints (no path params, path/operation hints match resource-heavy surfaces like
scans,reports,audit,applications,heatmap). - Sends 12 requests in rapid succession to each target.
- Passes if any 429 response or
Retry-Afterheader appears. Fails if any 5xx occurs (burst causing server errors). Skipped if no throttle signal (informational).
Oversized pagination/window check (oversized_pagination_window_check):
- Selects up to 6 qualifying GET endpoints that accept pagination or time-range parameters (
page_size,limit,offset,days). - Sends a single request with extreme values:
page_size=1,000,000,limit=1,000,000,offset=1,000,000,days=5000. - Expects 400/413/414/422 rejection. A 5xx is a failure (the request caused a server error). A 2xx is skipped (the server may have silently capped the value; deeper investigation needed).
Why we need it
APIs without rate limiting are vulnerable to denial-of-service, credential brute-forcing, and data enumeration attacks (OWASP API4 — Unrestricted Resource Consumption). Even authenticated APIs can be abused by a malicious tenant exhausting shared compute or database resources, degrading service for other users. Oversized pagination parameters are a common vector for memory exhaustion — querying millions of rows in a single request can crash database connections, exhaust heap, and cause service outages. These checks validate infrastructure-level defenses, not just application logic.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
burst_rate_limit_signal_check |
Selected authenticated GET endpoints | Throttle signal appears and no instability is induced | 429/Retry-After indicates active limiting |
oversized_pagination_window_check |
Endpoints with window/pagination params | Abusive ranges are rejected/bounded | 5xx under oversized request is a failure |
rate_limit_target_discovery |
Category startup | Suitable abuse-probe endpoints are discoverable | Skipped when no candidates exist |
15. Error Leakage and Debug Surface
Category name: error_leakage_and_debug_surface
What it is
Detects debug routes that should not be publicly exposed and tests whether error responses contain internal system details that aid attackers.
What it does
Debug route exposure check (debug_route_public_exposure_check):
- Scans all discovered endpoints for the keyword
debugin the path or operation ID. - If any such endpoint is marked as not requiring authentication, it fails immediately — a public debug route is a critical finding.
- If all debug routes are auth-protected, reports pass with count.
Debug route unauthenticated access check (debug_route_unauthenticated_access_check):
- Fires the first authenticated debug-route GET endpoint without an
Authorizationheader. - Expects 401/403/404. A 2xx means the auth protection on the debug route is not enforced.
Error response information leak check (error_response_information_leak_check):
- Sends a probe payload (
__apptest_error_probe__'"<script>alert(1)</script>) to up to 24 safe GET/HEAD/OPTIONS endpoints, prioritizing those with more query parameters (higher chance of triggering validation errors). - Scans responses for high-confidence leak markers (immediate fail):
- Python tracebacks (
Traceback (most recent call last)) - SQLAlchemy/psycopg2/SQLite3 exceptions
- Java stack traces (
java.lang.,NullPointerException) - Filesystem paths (
file "/...) - OS-level permission errors
- Python tracebacks (
- Scans for medium-confidence markers (skipped — informational):
- Generic
exception,error while processing,internal server error,debug
- Generic
Why we need it
Error leakage and exposed debug surfaces are classified under OWASP API8 — Security Misconfiguration and OWASP API9 — Improper Inventory Management.
Debug routes left enabled in production have provided root-level access, configuration dumps, memory inspection, and live query execution capabilities to attackers. Stack traces and exception messages in error responses reveal:
- Framework type and version (narrows available exploits).
- Database driver and query structure (enables targeted injection).
- Internal file paths and module structure (aids further reconnaissance).
- ORM model field names (aids mass assignment attacks).
All of this information is freely handed to an attacker without requiring any authentication. Conservative severity tiers (fail only on high-confidence markers, skipped on ambiguous signals) ensure this category produces actionable findings rather than noise.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
debug_route_public_exposure_check |
Schema inventory for debug routes | No public debug routes are exposed | Public debug route is immediate failure |
debug_route_unauthenticated_access_check |
Authenticated debug-route target | Unauthenticated debug access is denied | 2xx without auth is failure |
error_response_information_leak_check |
Safe endpoint payload probes | Error responses avoid sensitive internals | High-confidence leak markers fail; medium markers skip |
error_leakage_target_discovery |
Category startup | Suitable leak-probe endpoints are discoverable | Skipped when no safe targets exist |
16. JWT Security
Category name: jwt_security
What it is
JWT-specific token-hardening checks that go beyond basic malformed-token validation.
What it does
For bearer/JWT-auth contexts, sends targeted manipulated token variants to authenticated safe endpoints, including:
alg:none/alg:Noneheader variants- stripped-signature token forms
- expired and future-
nbfclaim variants kidtraversal and SQL-like injection header variants- algorithm-confusion probe when token shape allows it
The category fails when manipulated tokens receive successful access responses and skips when bearer/JWT context is not available.
Why we need it
JWT breakages commonly occur in algorithm selection, signature verification, and header handling paths. These failures can allow token forgery or bypass despite apparently correct auth middleware.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
jwt_alg_none |
JWT-auth safe endpoints | alg:none token variant is rejected |
Header tampering probe |
jwt_alg_none_capital |
JWT-auth safe endpoints | alg:None variant is rejected |
Case-variant parser hardening probe |
jwt_signature_stripped |
JWT-auth safe endpoints | Signature-stripped token is rejected | Signature verification probe |
jwt_expired_claim |
JWT-auth safe endpoints | Expired token claim is rejected | Temporal validation probe |
jwt_nbf_future_claim |
JWT-auth safe endpoints | Future nbf claim token is rejected |
Temporal validation probe |
jwt_kid_path_traversal |
JWT-auth safe endpoints | Unsafe kid path traversal header is rejected |
Header abuse probe |
jwt_kid_sql_injection |
JWT-auth safe endpoints | Unsafe kid SQL-like header is rejected |
Header abuse probe |
jwt_alg_confusion_rsa_to_hmac |
JWT-auth safe endpoints (asymmetric original alg only) | RS/ES/PS to HS256 confusion variant is rejected | Conditional probe |
jwt_security_token_presence |
Category startup | Bearer token context exists | Skipped in non-bearer auth modes |
jwt_security_format_detection |
Category startup | JWT-shaped token format is detected | Skipped when access token is not JWT-shaped |
jwt_security_target_discovery |
Category startup | Suitable safe auth targets are discoverable | Skipped when no targets exist |
17. Contract Conformance
Category name: contract_conformance
What it is
Response-level OpenAPI contract validation for status maps and payload structure.
What it does
- Verifies runtime status codes are declared in each endpoint's OpenAPI responses map
- Detects undocumented top-level response fields
- Detects missing required response fields
Why we need it
Contract drift weakens client safety assumptions and can quietly expose new data fields. It also reduces confidence in downstream policy and validation layers that rely on stable response schemas.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
response_status_code_conformance |
Safe endpoints with response schemas | Runtime status is declared in OpenAPI responses map | Undocumented status is failure |
response_undocumented_fields |
2xx JSON object responses with schema properties | No undeclared top-level fields appear | Extra fields are failure |
response_missing_required_fields |
2xx JSON object responses with required fields | All required fields are present | Missing required fields are failure |
contract_conformance_target_discovery |
Category startup | Suitable schema-backed targets are discoverable | Skipped when no targets exist |
18. BOLA Matrix
Category name: bola_matrix
What it is
Object-level authorization probing that uses harvested IDs from real API responses.
What it does
- Harvests ID-like values from collection/list responses
- Applies neighbor-ID mutation patterns where applicable
- Reuses harvested IDs across compatible ID-bearing endpoints
- Flags suspicious cross-object successful access patterns as potential BOLA/IDOR
Why we need it
Live object-ID probing provides stronger signal than synthetic path tampering alone and better matches real attacker behavior for horizontal authorization abuse.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
bola_neighbor_id_enumeration |
ID-bearing GET endpoints with harvested IDs | Adjacent/neighbor IDs are denied/hidden | Enumeration resistance probe |
bola_cross_service_id_probe |
ID-bearing GET endpoints with cross-service IDs | Cross-service object IDs are denied/isolated | Domain-isolation probe |
bola_uuid_variant_probe |
UUID-shaped harvested IDs | UUID variants are denied/hidden | Guessability/authorization probe |
bola_matrix_id_harvest |
Category startup | Real object IDs can be harvested | Skipped when harvest pool is empty |
bola_matrix_target_discovery |
Category startup | Suitable GET ID-path targets are discoverable | Skipped when no targets exist |
19. Workflow Chains
Category name: workflow_chains
What it is
Stateful lifecycle testing across create, read, update, and delete flows.
What it does
- Discovers resource-shaped endpoint groups
- Executes chained CRUD-style operations using extracted IDs
- Tests auth enforcement on step transitions
- Attempts cleanup of created resources when delete routes exist
Why we need it
Many authorization and state-management vulnerabilities only appear over multi-step workflows, not in isolated single-endpoint probes.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
workflow_create |
Per-resource chain create step | Resource create behaves safely and consistently | Starts chain lifecycle |
workflow_id_extraction |
Post-create response parsing | Created resource ID is extractable for chain continuation | Skip if ID cannot be extracted |
workflow_read |
Per-resource chain read step | Created resource read behaves as expected | Includes missing-resource signal when unexpected |
workflow_read_authz |
Unauthenticated read of created resource | Access is denied without auth | 2xx without auth is failure |
workflow_update |
Per-resource chain update step | Authorized update behaves safely | 400/422 may be informational skip |
workflow_update_authz |
Unauthenticated update attempt | Access is denied without auth | 2xx without auth is failure |
workflow_delete_authz |
Unauthenticated delete attempt | Access is denied without auth | 2xx without auth is failure |
workflow_delete |
Authorized delete/cleanup step | Created resource is removed or safely handled | Cleanup confirmation step |
workflow_chains_unsafe_gate |
Category startup | Unsafe mode is explicitly enabled | Skipped when --include-unsafe is not enabled |
workflow_chains_target_discovery |
Category startup | Resource chain candidates are discoverable | Skipped when no chain candidates are found |
20. Soak Profile
Category name: soak_profile
What it is
Light sustained-load profiling for safe endpoints to capture reliability and latency behavior.
What it does
- Replays safe methods repeatedly per endpoint
- Measures status distribution, exception rate, 5xx rate, and p95 latency
- Applies threshold-based pass/fail with auth-inaccessible endpoint skipping to avoid false failures
- Supports tuning via CLI/env/config values (
soak_samples,soak_max_5xx_rate,soak_max_exception_rate,soak_max_p95_ms)
Why we need it
Unstable endpoints can hide or distort security results. A soak profile adds confidence in test signal quality and highlights reliability regressions early.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
soak_endpoint_stability |
Repeated safe requests per endpoint | Exception/5xx rates remain within configured thresholds | Default stable outcome path |
soak_endpoint_latency |
Repeated safe requests per endpoint | p95 latency remains within configured threshold | Raised when latency threshold is breached |
soak_endpoint_access |
Auth-required endpoints with zero successful responses | Result is marked skipped to reduce false-fail noise | Evaluated before reliability thresholds |
soak_profile_target_discovery |
Category startup | Suitable safe targets are discoverable | Skipped when no safe endpoints are available |
21. NoSQL Injection
Category name: nosql_injection
What it is
Injects MongoDB-style query operator payloads into JSON request bodies to detect whether the API reflects or processes raw operator input without sanitization — the NoSQL analog of the sql_injection category.
What it does
-
Selects up to 20 discovered endpoints that accept a JSON request body.
-
Replaces the first body field's value with an operator payload instead of a normal scalar value:
Test Injected value Threat nosql_operator_ne{"$ne": null}Not-equal bypass — matches any document where the field isn't exactly this value, commonly used to bypass password/ID equality checks nosql_operator_gt{"$gt": ""}Greater-than bypass — matches any non-empty value, another equality-check bypass pattern nosql_operator_where{"$where": "1 == 1"}Server-side JavaScript execution — if evaluated, runs arbitrary logic against the database nosql_operator_regex{"$regex": ".*"}Regex bypass — matches any string value, another equality-check bypass pattern -
Only runs against POST/PUT/PATCH endpoints, so — like
mass_assignmentandschema_contract_and_validation— it requires--include-unsafe(orunsafe_categoriesincludingnosql_injection) to actually execute; without it, requests are skipped rather than silently omitted.
Why we need it
NoSQL databases (MongoDB and similar) parse query operators directly from JSON input. Where SQL injection relies on string concatenation, NoSQL injection exploits APIs that pass client-supplied JSON straight into a query filter without validating that fields are the expected scalar type. A login endpoint that accepts {"password": {"$ne": null}} instead of a string password can authenticate without knowing any valid password — a critical authentication bypass. This class of bug doesn't show up in SQL-focused testing and is easy to miss because the payload is syntactically valid JSON, not an escaped string.
Test Matrix
| Test Name | Trigger/Scope | Expected Secure Outcome | Notes |
|---|---|---|---|
nosql_operator_ne |
JSON-body mutation endpoints with tampered first field | $ne operator payload is rejected or type-validated |
Requires --include-unsafe |
nosql_operator_gt |
JSON-body mutation endpoints with tampered first field | $gt operator payload is rejected or type-validated |
Requires --include-unsafe |
nosql_operator_where |
JSON-body mutation endpoints with tampered first field | $where operator payload is rejected or type-validated |
Requires --include-unsafe |
nosql_operator_regex |
JSON-body mutation endpoints with tampered first field | $regex operator payload is rejected or type-validated |
Requires --include-unsafe |
nosql_injection_target_discovery |
Category startup | Suitable JSON-body endpoints are discoverable | Skipped when no eligible targets exist |
Summary Table
| # | Category | OWASP Reference | Safe by Default | Requires include_unsafe |
|---|---|---|---|---|
| 1 | baseline | — | Yes | No |
| 2 | owasp | API2, general headers | Yes | No |
| 3 | sql_injection | API1, A03 Injection | Yes | No |
| 4 | pen_test | A03 Injection, XSS | Yes | No |
| 5 | security_misc | CORS, general | Yes | No |
| 6 | authz | API1, API5 | Partial | Yes (PATCH/DELETE IDOR probes) |
| 7 | auth_robustness | API2 | Yes | No |
| 8 | token_security | API2 | Yes | No |
| 9 | privilege_escalation | API5 | Yes | No |
| 10 | file_upload_security | A03, path traversal | No | Yes (POST/PUT/PATCH) |
| 11 | mass_assignment | API3 | No | Yes (POST/PUT/PATCH) |
| 12 | schema_contract_and_validation | API3, input validation | No | Yes (POST/PUT/PATCH) |
| 13 | ssrf_and_redirects | API7 | Partial | Yes (PATCH + restore) |
| 14 | rate_limit_and_resource_abuse | API4 | Yes | No |
| 15 | error_leakage_and_debug_surface | API8, API9 | Yes | No |
| 16 | jwt_security | API2 | Yes | No |
| 17 | contract_conformance | API9, API10 | Yes | No |
| 18 | bola_matrix | API1 | Yes | No |
| 19 | workflow_chains | API1, API5 | No | Yes (stateful POST/PUT/PATCH/DELETE flows) |
| 20 | soak_profile | API4 (operational resilience) | Yes | No |
| 21 | nosql_injection | API1, A03 Injection | No | Yes (POST/PUT/PATCH) |
Release files for api-gate-keeper 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| api_gate_keeper-0.1.1.tar.gz | 106.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| api_gate_keeper-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 194.8 kB
Release files / api_gate_keeper-0.1.1.tar.gz
| Download URL | api_gate_keeper-0.1.1.tar.gz |
|---|---|
| Size | 106.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e149a69d9c615cd3b6213acdaf8f84b59c12efc6ed4739f30f3daf0e53943897
|
|
BLAKE2b-256 checksum How to use checksums |
8a653a93acc5ef6cd4b9a5c0096a3a404b623a652522360042fd11099ad258e8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.
Transparency logRelease files / api_gate_keeper-0.1.1-py3-none-any.whl
| Download URL | api_gate_keeper-0.1.1-py3-none-any.whl |
|---|---|
| Size | 87.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b0eec8e0b3ec8a7675c0552f9bbb4961797517d5314447e5b5010c98cbbcb12b
|
|
BLAKE2b-256 checksum How to use checksums |
66e4d2c6d8c9a5c209de68bc1c0432f73b8a6651d09946cce5da73d778b0f136
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.
Transparency log