Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions backend/app/_masops_smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import sqlite3


def get_user(db_path, user_id, cache={}):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mutable default argument creates persistent shared cache

Medium Severity

Using a mutable default argument (cache={}) means the dictionary is shared across all calls to get_user for the lifetime of the process. The cache can never be invalidated, so stale data will be returned indefinitely after the first lookup for a given user_id. This is a well-known Python gotcha that leads to surprising behavior.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f0bfbf3. Configure here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Mutable default argument cache={} - shared between function calls. Use None as default and create a new dict inside the function.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: cache={} creates shared state across calls; use None and initialize inside the function to avoid cross-request/stale cache behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/app/_masops_smoke.py, line 4:

<comment>`cache={}` creates shared state across calls; use `None` and initialize inside the function to avoid cross-request/stale cache behavior.</comment>

<file context>
@@ -0,0 +1,19 @@
+import sqlite3
+
+
+def get_user(db_path, user_id, cache={}):
+    if user_id in cache:
+        return cache[user_id]
</file context>

if user_id in cache:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The cache is keyed solely by user_id, but the function accepts different db_path values. If the same user_id is looked up against a second database, the cached result from the first database is returned without querying the new one. Include db_path in the cache key.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/app/_masops_smoke.py, line 5:

<comment>The cache is keyed solely by `user_id`, but the function accepts different `db_path` values. If the same `user_id` is looked up against a second database, the cached result from the first database is returned without querying the new one. Include `db_path` in the cache key.</comment>

<file context>
@@ -0,0 +1,19 @@
+
+
+def get_user(db_path, user_id, cache={}):
+    if user_id in cache:
+        return cache[user_id]
+    conn = sqlite3.connect(db_path)
</file context>

return cache[user_id]
Comment on lines +4 to +6
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include the database path in the cache key

When this helper is called for more than one SQLite database in the same process, the shared default cache is keyed only by user_id, so looking up the same ID in a second db_path returns rows from the first database without opening the second one. Scope the cache per database or include db_path in the key to avoid cross-database stale results.

Useful? React with 👍 / 👎.

conn = sqlite3.connect(db_path)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The sqlite3 connection is never closed. Every non-cached call to get_user leaks a connection, eventually exhausting file descriptors or database locks. Use a context manager (with sqlite3.connect(db_path) as conn:) or explicitly call conn.close() after use.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/app/_masops_smoke.py, line 7:

<comment>The `sqlite3` connection is never closed. Every non-cached call to `get_user` leaks a connection, eventually exhausting file descriptors or database locks. Use a context manager (`with sqlite3.connect(db_path) as conn:`) or explicitly call `conn.close()` after use.</comment>

<file context>
@@ -0,0 +1,19 @@
+def get_user(db_path, user_id, cache={}):
+    if user_id in cache:
+        return cache[user_id]
+    conn = sqlite3.connect(db_path)
+    cur = conn.cursor()
+    cur.execute("SELECT * FROM users WHERE id = '%s'" % user_id)
</file context>

cur = conn.cursor()
cur.execute("SELECT * FROM users WHERE id = '%s'" % user_id)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Parameterize the user lookup query

If user_id can come from a request or other untrusted input, interpolating it directly into the SQL lets values like ' OR '1'='1 alter the predicate and return unintended user rows. Use a parameterized SQLite query instead of string formatting so the ID is treated as data.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQL injection via string interpolation in query

High Severity

user_id is directly interpolated into the SQL query string using % formatting, creating a classic SQL injection vulnerability. A malicious user_id value like ' OR '1'='1 would bypass filtering and leak all rows. The sqlite3 module supports parameterized queries (? placeholders) which would prevent this.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f0bfbf3. Configure here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: SQL injection vulnerability - using string formatting in SQL query. Use parameterized queries: cur.execute("SELECT * FROM users WHERE id = ?", (user_id,))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: This query is vulnerable to SQL injection because it interpolates user_id directly into SQL.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/app/_masops_smoke.py, line 9:

<comment>This query is vulnerable to SQL injection because it interpolates `user_id` directly into SQL.</comment>

<file context>
@@ -0,0 +1,19 @@
+        return cache[user_id]
+    conn = sqlite3.connect(db_path)
+    cur = conn.cursor()
+    cur.execute("SELECT * FROM users WHERE id = '%s'" % user_id)
+    rows = cur.fetchall()
+    cache[user_id] = rows
</file context>
Suggested change
cur.execute("SELECT * FROM users WHERE id = '%s'" % user_id)
cur.execute("SELECT * FROM users WHERE id = ?", (user_id,))

rows = cur.fetchall()
cache[user_id] = rows
return rows
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Database connection is never closed, causing resource leak

Medium Severity

The sqlite3 connection opened via sqlite3.connect(db_path) is never closed — there's no conn.close() call and no with context manager. Every non-cached call to get_user leaks a connection, which can exhaust file descriptors or database locks over time.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f0bfbf3. Configure here.

Comment on lines +1 to +12


def first_or_none(items=[]):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Mutable default argument items=[] - shared between function calls. Use None as default and create a new list inside the function.

try:
return items[0]
except:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Bare except: catches all exceptions including SystemExit and KeyboardInterrupt. Catch specific exceptions like IndexError instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Bare except: catches all BaseException subclasses including KeyboardInterrupt and SystemExit, making the process unresponsive to interrupt/shutdown signals. Narrow this to except (IndexError, TypeError): or at minimum except Exception:.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/app/_masops_smoke.py, line 18:

<comment>Bare `except:` catches all `BaseException` subclasses including `KeyboardInterrupt` and `SystemExit`, making the process unresponsive to interrupt/shutdown signals. Narrow this to `except (IndexError, TypeError):` or at minimum `except Exception:`.</comment>

<file context>
@@ -0,0 +1,19 @@
+def first_or_none(items=[]):
+    try:
+        return items[0]
+    except:
+        pass
</file context>

pass
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bare except silently swallows critical system exceptions

Low Severity

The bare except: in first_or_none catches all exceptions deriving from BaseException, including KeyboardInterrupt, SystemExit, and GeneratorExit. This silently suppresses signals that are meant to terminate the process, making the program unresponsive to interrupt signals or graceful shutdown requests when this code path is active. Only IndexError (and perhaps TypeError) is expected here.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f0bfbf3. Configure here.

Comment on lines +15 to +19