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

Broken Access Control

skill-scholarly360-owasp-top10-web-skills-broken-access-control · by scholarly360

>

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

Install

$ agentstack add skill-scholarly360-owasp-top10-web-skills-broken-access-control

✓ 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 No
  • 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-scholarly360-owasp-top10-web-skills-broken-access-control)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
5mo 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 Broken Access Control? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Broken Access Control (A01:2025)

The #1 OWASP risk for two consecutive cycles. Found in 3.74% of applications with over 1.8 million occurrences and 32,654 CVEs. Access control enforces that users cannot act outside their intended permissions. Failures lead to unauthorized data disclosure, modification, or destruction.

> For deep reference on all CWEs and remediation patterns, see references/bac-detail.md.


What This Skill Covers

| Sub-category | Description | Key CWEs | |---|---|---| | Missing Authorization | Routes/endpoints with no auth guard | CWE-862, CWE-284 | | IDOR | Resource ownership not verified | CWE-639, CWE-285 | | CORS Misconfiguration | Wildcard or overly-permissive origins | CWE-284 | | SSRF | Outbound requests to internal/private IPs | CWE-918 | | CSRF | State-changing forms missing token validation | CWE-352 | | Privilege Escalation | Users accessing higher-privilege roles | CWE-269 | | JWT/Session Manipulation | Metadata tampering, session fixation | CWE-287 |


Workflow: Auditing a FastAPI or Flask App

Step 1 — Identify All Routes

FastAPI:

# List all routes and their dependencies
for route in app.routes:
    print(route.path, route.methods, route.dependencies)

Flask:

# Print all registered endpoints
for rule in app.url_map.iter_rules():
    print(rule.endpoint, rule.methods, rule.rule)

Flag any route that:

  • Handles POST, PUT, PATCH, DELETE without an auth dependency
  • Returns user-specific data (profile, orders, files) without ownership check
  • Has no rate limiting on sensitive operations

Step 2 — Check Authorization Guards

FastAPI — look for missing Depends():

# VULNERABLE — no auth check
@app.get("/users/{user_id}/data")
async def get_user_data(user_id: int):
    return db.query(UserData).filter_by(owner_id=user_id).all()

# SECURE — requires authenticated user
@app.get("/users/{user_id}/data")
async def get_user_data(user_id: int, current_user: User = Depends(get_current_user)):
    if current_user.id != user_id:
        raise HTTPException(status_code=403, detail="Forbidden")
    return db.query(UserData).filter_by(owner_id=user_id).all()

Flask — look for missing @login_required:

# VULNERABLE
@app.route("/dashboard")
def dashboard():
    return render_template("dashboard.html")

# SECURE
@app.route("/dashboard")
@login_required
def dashboard():
    return render_template("dashboard.html")

Step 3 — IDOR Check (Resource Ownership)

IDOR is the most common BAC failure. Every query that fetches a user-owned resource must compare resource.owner_id against current_user.id.

# VULNERABLE IDOR — attacker can request any document_id
@app.get("/documents/{doc_id}")
async def get_doc(doc_id: int, user=Depends(get_current_user)):
    return db.get(Document, doc_id)  # No ownership check!

# SECURE
@app.get("/documents/{doc_id}")
async def get_doc(doc_id: int, user=Depends(get_current_user)):
    doc = db.get(Document, doc_id)
    if not doc or doc.owner_id != user.id:
        raise HTTPException(status_code=404)  # 404 preferred over 403 (avoids enumeration)
    return doc

Test vector: Authenticate as User A, record a resource ID, then authenticate as User B and request that same ID. If you get data — it's an IDOR.


Step 4 — CORS Misconfiguration

FastAPI:

# VULNERABLE — wildcard origin
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True)

# SECURE — explicit allowlist
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://app.yourdomain.com"],
    allow_credentials=True,
    allow_methods=["GET", "POST"],
    allow_headers=["Authorization", "Content-Type"],
)

Flask:

# VULNERABLE
CORS(app, origins="*")

# SECURE
CORS(app, origins=["https://app.yourdomain.com"], supports_credentials=True)

> ⚠️ allow_origins=["*"] combined with allow_credentials=True is doubly dangerous — most browsers block this, but it signals deep misconfiguration.


Step 5 — SSRF (Now part of A01:2025)

Check any code that makes outbound HTTP requests with user-controlled URLs.

import ipaddress, re

PRIVATE_RANGES = [
    ipaddress.ip_network("10.0.0.0/8"),
    ipaddress.ip_network("172.16.0.0/12"),
    ipaddress.ip_network("192.168.0.0/16"),
    ipaddress.ip_network("127.0.0.0/8"),
    ipaddress.ip_network("169.254.0.0/16"),  # AWS metadata
]

def is_safe_url(url: str) -> bool:
    from urllib.parse import urlparse
    import socket
    try:
        host = urlparse(url).hostname
        ip = ipaddress.ip_address(socket.gethostbyname(host))
        return not any(ip in net for net in PRIVATE_RANGES)
    except Exception:
        return False

# In your endpoint:
@app.post("/fetch")
async def fetch_url(url: str, user=Depends(get_current_user)):
    if not is_safe_url(url):
        raise HTTPException(status_code=400, detail="URL not allowed")
    return await httpx.get(url)

Test vectors: http://169.254.169.254/latest/meta-data/ (AWS metadata), http://localhost:6379 (Redis), http://10.0.0.1/admin


Step 6 — CSRF (Flask-specific risk)

FastAPI APIs using JWT/Bearer tokens are generally CSRF-safe. Flask apps using session cookies need explicit protection.

# Flask — verify Flask-WTF is installed and CSRF is enabled
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)

# All state-changing forms must include {{ form.hidden_tag() }} or {{ csrf_token() }}

Check that WTF_CSRF_ENABLED = True in config (it is by default, but verify it hasn't been disabled).


Step 7 — JWT & Session Security

# Verify JWT expiry is set (short-lived: 15 min–1 hr)
jwt.encode({"sub": user_id, "exp": datetime.utcnow() + timedelta(minutes=30)}, SECRET_KEY)

# Flask — verify session security flags
app.config["SESSION_COOKIE_SECURE"] = True    # HTTPS only
app.config["SESSION_COOKIE_HTTPONLY"] = True  # No JS access
app.config["SESSION_COOKIE_SAMESITE"] = "Lax" # CSRF mitigation
app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(hours=2)

Quick Audit Checklist

Run through this for any Python web app:

  • [ ] Every non-public route has an auth dependency / decorator
  • [ ] All user-owned resource queries verify resource.owner_id == current_user.id
  • [ ] CORS origins are an explicit allowlist, not *
  • [ ] Outbound HTTP calls validate URL is not in private IP ranges
  • [ ] Flask apps have Flask-WTF CSRF protection on all state-changing forms
  • [ ] JWTs have short exp claims; sessions have SECURE, HTTPONLY, SAMESITE flags
  • [ ] Session IDs are invalidated on logout
  • [ ] Access control failures are logged and alerted on

Automated Scanning

| Tool | What it catches | Command | |---|---|---| | Bandit | SSRF patterns, hardcoded secrets, dangerous functions | bandit -r . -t B105,B106,B107,B501,B601 | | OWASP ZAP | IDOR, missing auth at runtime, CORS | zap-baseline.py -t http://localhost:8000 | | truffleHog | Leaked tokens/keys in git history | trufflehog git file://. --only-verified |


Key CWEs Reference

| CWE | Name | Typical Pattern | |---|---|---| | CWE-284 | Improper Access Control | Route accessible without any auth | | CWE-285 | Improper Authorization | Auth present but role/ownership not checked | | CWE-352 | CSRF | State-changing endpoint accepts cookie auth, no CSRF token | | CWE-639 | Auth Bypass via User-Controlled Key | IDOR — GET /orders/{id} with no ownership check | | CWE-862 | Missing Authorization | No @login_required or Depends(get_current_user) | | CWE-918 | SSRF | requests.get(user_input_url) without validation | | CWE-200 | Sensitive Info Exposure | Unauthorized user receives another's PII/data |


See Also

  • references/bac-detail.md — Expanded CWE descriptions, more attack examples, compliance mapping
  • OWASP A01:2025 official page: https://owasp.org/Top10/A012025-BrokenAccess_Control/

Source & license

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

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.