File size: 8,556 Bytes
f0dc3b9
 
912c51b
f0dc3b9
912c51b
 
 
 
 
 
 
 
 
 
 
 
 
 
f0dc3b9
 
 
912c51b
 
 
f0dc3b9
 
 
 
 
 
 
 
 
 
 
 
 
912c51b
f0dc3b9
 
 
 
 
 
 
 
 
 
 
 
 
912c51b
f0dc3b9
 
 
 
 
 
 
 
 
 
 
 
912c51b
f0dc3b9
 
912c51b
f0dc3b9
 
 
 
 
 
 
 
 
912c51b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f0dc3b9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
912c51b
f0dc3b9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
912c51b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f0dc3b9
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
"""
Benchmark dataset for bug detection evaluation.

Each fixture has:
  - id:            unique identifier
  - description:   human-readable summary
  - clean:         if True, no bugs expected (measures false positive rate)
  - code:          source snippet to analyse
  - expected_bugs: list of keywords that MUST appear in critical/warnings
                   for the detection to count as a true positive

Fixture breakdown:
  Buggy (11): sql_injection, hardcoded_secret, missing_error_handling,
              infinite_loop_risk, race_condition, toctou_vulnerability,
              async_await_misuse, missing_auth_decorator, n_plus_one_query,
              insecure_deserialization, path_traversal
  Clean (4):  clean_function, clean_dataclass, clean_async_handler,
              clean_auth_check
"""

BENCHMARK_FIXTURES = [

    # ── buggy fixtures ──────────────────────────────────────────────────────

    {
        "id": "sql_injection",
        "description": "Raw string formatting in SQL query",
        "clean": False,
        "code": """\
def get_user(username: str):
    import sqlite3
    conn = sqlite3.connect("app.db")
    query = f"SELECT * FROM users WHERE name = '{username}'"
    return conn.execute(query).fetchone()
""",
        "expected_bugs": ["sql", "injection", "format"],
    },

    {
        "id": "hardcoded_secret",
        "description": "API key hardcoded in source",
        "clean": False,
        "code": """\
API_KEY = "sk-abc123supersecretkey9999"

def call_api():
    import httpx
    return httpx.get("https://api.example.com", headers={"X-Key": API_KEY})
""",
        "expected_bugs": ["secret", "hardcoded", "api_key", "key"],
    },

    {
        "id": "missing_error_handling",
        "description": "File open with no exception handling",
        "clean": False,
        "code": """\
def read_config(path: str) -> dict:
    with open(path) as f:
        import json
        return json.load(f)
""",
        "expected_bugs": ["error", "exception", "handling"],
    },

    {
        "id": "infinite_loop_risk",
        "description": "While loop with no guaranteed exit and no sleep",
        "clean": False,
        "code": """\
def wait_for_ready(service):
    while not service.is_ready():
        pass  # no timeout, no sleep
    return True
""",
        "expected_bugs": ["loop", "timeout", "infinite", "blocking"],
    },

    {
        "id": "race_condition",
        "description": "Shared mutable counter incremented without a lock",
        "clean": False,
        "code": """\
import threading

counter = 0

def increment():
    global counter
    for _ in range(100_000):
        counter += 1  # read-modify-write is not atomic

threads = [threading.Thread(target=increment) for _ in range(4)]
for t in threads:
    t.start()
for t in threads:
    t.join()
""",
        "expected_bugs": ["race", "lock", "thread", "atomic", "concurrent"],
    },

    {
        "id": "toctou_vulnerability",
        "description": "Time-of-check to time-of-use: file existence checked then opened separately",
        "clean": False,
        "code": """\
import os

def read_if_exists(path: str) -> str:
    if os.path.exists(path):          # check
        with open(path) as f:         # use β€” file may have changed between check and open
            return f.read()
    return ""
""",
        "expected_bugs": ["toctou", "race", "time-of-check", "exists", "atomic"],
    },

    {
        "id": "async_await_misuse",
        "description": "Blocking I/O call inside an async function starves the event loop",
        "clean": False,
        "code": """\
import asyncio
import time
import httpx

async def fetch_data(url: str) -> str:
    time.sleep(5)                   # blocks the entire event loop
    response = httpx.get(url)       # sync HTTP call inside async context
    return response.text
""",
        "expected_bugs": ["blocking", "sleep", "async", "event loop", "sync"],
    },

    {
        "id": "missing_auth_decorator",
        "description": "Admin endpoint with no authentication or authorisation check",
        "clean": False,
        "code": """\
from fastapi import APIRouter

router = APIRouter()

@router.delete("/admin/users/{user_id}")
async def delete_user(user_id: int):
    # No auth check β€” any caller can delete any user
    return {"deleted": user_id}
""",
        "expected_bugs": ["auth", "authentication", "authorization", "permission", "security"],
    },

    {
        "id": "n_plus_one_query",
        "description": "N+1 query: separate DB hit for every item in a loop",
        "clean": False,
        "code": """\
from app.db import session
from app.models import Order, Product

def get_order_products(order_ids: list[int]) -> list[dict]:
    results = []
    for order_id in order_ids:                          # N iterations
        order    = session.query(Order).get(order_id)  # query 1
        product  = session.query(Product).get(order.product_id)  # query 2
        results.append({"order": order.id, "product": product.name})
    return results
""",
        "expected_bugs": ["n+1", "query", "loop", "database", "join", "eager"],
    },

    {
        "id": "insecure_deserialization",
        "description": "pickle.loads() called on untrusted user input",
        "clean": False,
        "code": """\
import pickle
from flask import request

def load_session():
    data = request.cookies.get("session")
    if data:
        return pickle.loads(bytes.fromhex(data))   # arbitrary code execution
    return {}
""",
        "expected_bugs": ["pickle", "deserializ", "untrusted", "arbitrary", "code execution"],
    },

    {
        "id": "path_traversal",
        "description": "User-controlled filename used in file open without sanitisation",
        "clean": False,
        "code": """\
import os
from flask import request, send_file

UPLOAD_DIR = "/var/uploads"

def download_file():
    filename = request.args.get("file")             # user controlled
    path = os.path.join(UPLOAD_DIR, filename)       # ../../../etc/passwd works
    return send_file(path)
""",
        "expected_bugs": ["path", "traversal", "sanitiz", "directory", "../"],
    },

    # ── clean fixtures ──────────────────────────────────────────────────────

    {
        "id": "clean_function",
        "description": "Well-typed, documented, no issues",
        "clean": True,
        "code": """\
from typing import Optional


def divide(a: float, b: float) -> Optional[float]:
    \"\"\"Safely divide a by b, returning None on division by zero.\"\"\"
    if b == 0:
        return None
    return a / b
""",
        "expected_bugs": [],
    },

    {
        "id": "clean_dataclass",
        "description": "Clean dataclass with validation",
        "clean": True,
        "code": """\
from dataclasses import dataclass, field
from typing import List


@dataclass
class Order:
    id: str
    items: List[str] = field(default_factory=list)
    total: float = 0.0

    def add_item(self, item: str, price: float) -> None:
        self.items.append(item)
        self.total += price
""",
        "expected_bugs": [],
    },

    {
        "id": "clean_async_handler",
        "description": "Properly async handler using httpx.AsyncClient with timeout",
        "clean": True,
        "code": """\
import httpx


async def fetch_data(url: str, timeout: float = 10.0) -> str:
    \"\"\"Fetch URL asynchronously with explicit timeout.\"\"\"
    async with httpx.AsyncClient(timeout=timeout) as client:
        response = await client.get(url)
        response.raise_for_status()
        return response.text
""",
        "expected_bugs": [],
    },

    {
        "id": "clean_auth_check",
        "description": "FastAPI endpoint with proper JWT auth dependency",
        "clean": True,
        "code": """\
from fastapi import APIRouter, Depends, HTTPException, status
from app.auth import get_current_user, User

router = APIRouter()


@router.delete("/admin/users/{user_id}")
async def delete_user(
    user_id: int,
    current_user: User = Depends(get_current_user),
) -> dict:
    \"\"\"Delete a user. Requires authenticated admin.\"\"\"
    if not current_user.is_admin:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admins only")
    return {"deleted": user_id}
""",
        "expected_bugs": [],
    },
]