Spaces:
Sleeping
Sleeping
security: patch 5 critical grader and environment vulnerabilities
Browse files
__pycache__/models.cpython-312.pyc
CHANGED
|
Binary files a/__pycache__/models.cpython-312.pyc and b/__pycache__/models.cpython-312.pyc differ
|
|
|
server/__pycache__/environment.cpython-312.pyc
CHANGED
|
Binary files a/server/__pycache__/environment.cpython-312.pyc and b/server/__pycache__/environment.cpython-312.pyc differ
|
|
|
server/__pycache__/grader.cpython-312.pyc
CHANGED
|
Binary files a/server/__pycache__/grader.cpython-312.pyc and b/server/__pycache__/grader.cpython-312.pyc differ
|
|
|
server/environment.py
CHANGED
|
@@ -116,7 +116,7 @@ class DbMigrationEnvironment(Environment):
|
|
| 116 |
def _is_read_query(self, sql: str) -> bool:
|
| 117 |
"""Check if SQL is a read-only query (SELECT or certain PRAGMAs)."""
|
| 118 |
stripped = sql.strip().upper()
|
| 119 |
-
if stripped.startswith("SELECT"):
|
| 120 |
return True
|
| 121 |
# PRAGMA table_info, foreign_key_list, etc. are read-only
|
| 122 |
if stripped.startswith("PRAGMA") and "=" not in stripped:
|
|
@@ -318,7 +318,7 @@ class DbMigrationEnvironment(Environment):
|
|
| 318 |
|
| 319 |
# --- A3: Dangerous SQL Blacklist ---
|
| 320 |
sql_lower = sql_command.lower()
|
| 321 |
-
if "pragma
|
| 322 |
execution_result = "Security Error: Disabling PRAGMA foreign_keys is strictly explicitly forbidden."
|
| 323 |
action_error = "pragma_off_blocked"
|
| 324 |
elif _DANGEROUS_PATTERNS.search(sql_command):
|
|
|
|
| 116 |
def _is_read_query(self, sql: str) -> bool:
|
| 117 |
"""Check if SQL is a read-only query (SELECT or certain PRAGMAs)."""
|
| 118 |
stripped = sql.strip().upper()
|
| 119 |
+
if stripped.startswith("SELECT") or stripped.startswith("WITH"):
|
| 120 |
return True
|
| 121 |
# PRAGMA table_info, foreign_key_list, etc. are read-only
|
| 122 |
if stripped.startswith("PRAGMA") and "=" not in stripped:
|
|
|
|
| 318 |
|
| 319 |
# --- A3: Dangerous SQL Blacklist ---
|
| 320 |
sql_lower = sql_command.lower()
|
| 321 |
+
if re.search(r"pragma\s+foreign_keys\s*=\s*(off|0)", sql_lower):
|
| 322 |
execution_result = "Security Error: Disabling PRAGMA foreign_keys is strictly explicitly forbidden."
|
| 323 |
action_error = "pragma_off_blocked"
|
| 324 |
elif _DANGEROUS_PATTERNS.search(sql_command):
|
server/grader.py
CHANGED
|
@@ -61,6 +61,11 @@ def _get_column_names(conn: sqlite3.Connection, table: str) -> Set[str]:
|
|
| 61 |
return {col["name"] for col in _get_column_info(conn, table)}
|
| 62 |
|
| 63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
def _get_row_count(conn: sqlite3.Connection, table: str) -> int:
|
| 65 |
"""Get row count. Returns 0 on error."""
|
| 66 |
try:
|
|
@@ -95,12 +100,12 @@ def _has_foreign_key(conn: sqlite3.Connection, table: str, ref_table: str) -> bo
|
|
| 95 |
|
| 96 |
|
| 97 |
def _count_foreign_keys(conn: sqlite3.Connection, table: str) -> int:
|
| 98 |
-
"""Count
|
| 99 |
try:
|
| 100 |
cursor = conn.execute(f"PRAGMA foreign_key_list([{table}])")
|
| 101 |
refs = set()
|
| 102 |
for row in cursor.fetchall():
|
| 103 |
-
refs.add(row[
|
| 104 |
return len(refs)
|
| 105 |
except Exception:
|
| 106 |
return 0
|
|
@@ -199,6 +204,7 @@ class StateReconciler:
|
|
| 199 |
self._golden_table_data[table] = {
|
| 200 |
"columns": _get_column_info(self._golden_conn, table),
|
| 201 |
"col_names": _get_column_names(self._golden_conn, table),
|
|
|
|
| 202 |
"rows": _get_all_rows(self._golden_conn, table),
|
| 203 |
"row_count": _get_row_count(self._golden_conn, table),
|
| 204 |
"fk_count": _count_foreign_keys(self._golden_conn, table),
|
|
@@ -274,9 +280,9 @@ class StateReconciler:
|
|
| 274 |
|
| 275 |
if table in agent_tables:
|
| 276 |
tables_found += 1
|
| 277 |
-
#
|
| 278 |
-
agent_cols =
|
| 279 |
-
golden_cols = golden_info["
|
| 280 |
if golden_cols:
|
| 281 |
col_overlap = len(agent_cols & golden_cols) / len(golden_cols)
|
| 282 |
total_col_match += col_overlap
|
|
@@ -329,7 +335,12 @@ class StateReconciler:
|
|
| 329 |
conn.execute("PRAGMA foreign_keys = ON")
|
| 330 |
cursor = conn.execute("PRAGMA integrity_check")
|
| 331 |
result = cursor.fetchone()[0]
|
| 332 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
except Exception:
|
| 334 |
pass
|
| 335 |
|
|
|
|
| 61 |
return {col["name"] for col in _get_column_info(conn, table)}
|
| 62 |
|
| 63 |
|
| 64 |
+
def _get_column_signatures(conn: sqlite3.Connection, table: str) -> Set[Tuple[str, str]]:
|
| 65 |
+
"""Get (name, type) tuples for strict schema grading."""
|
| 66 |
+
return {(col["name"], col["type"]) for col in _get_column_info(conn, table)}
|
| 67 |
+
|
| 68 |
+
|
| 69 |
def _get_row_count(conn: sqlite3.Connection, table: str) -> int:
|
| 70 |
"""Get row count. Returns 0 on error."""
|
| 71 |
try:
|
|
|
|
| 100 |
|
| 101 |
|
| 102 |
def _count_foreign_keys(conn: sqlite3.Connection, table: str) -> int:
|
| 103 |
+
"""Count unique FK constraints for a table using the FK id."""
|
| 104 |
try:
|
| 105 |
cursor = conn.execute(f"PRAGMA foreign_key_list([{table}])")
|
| 106 |
refs = set()
|
| 107 |
for row in cursor.fetchall():
|
| 108 |
+
refs.add(row[0]) # row[0] is the sequential ID of the foreign key constraint
|
| 109 |
return len(refs)
|
| 110 |
except Exception:
|
| 111 |
return 0
|
|
|
|
| 204 |
self._golden_table_data[table] = {
|
| 205 |
"columns": _get_column_info(self._golden_conn, table),
|
| 206 |
"col_names": _get_column_names(self._golden_conn, table),
|
| 207 |
+
"col_signatures": _get_column_signatures(self._golden_conn, table),
|
| 208 |
"rows": _get_all_rows(self._golden_conn, table),
|
| 209 |
"row_count": _get_row_count(self._golden_conn, table),
|
| 210 |
"fk_count": _count_foreign_keys(self._golden_conn, table),
|
|
|
|
| 280 |
|
| 281 |
if table in agent_tables:
|
| 282 |
tables_found += 1
|
| 283 |
+
# Signature (name + type) comparison
|
| 284 |
+
agent_cols = _get_column_signatures(conn, table)
|
| 285 |
+
golden_cols = golden_info["col_signatures"]
|
| 286 |
if golden_cols:
|
| 287 |
col_overlap = len(agent_cols & golden_cols) / len(golden_cols)
|
| 288 |
total_col_match += col_overlap
|
|
|
|
| 335 |
conn.execute("PRAGMA foreign_keys = ON")
|
| 336 |
cursor = conn.execute("PRAGMA integrity_check")
|
| 337 |
result = cursor.fetchone()[0]
|
| 338 |
+
|
| 339 |
+
# Explicitly run foreign_key_check to catch orphaned rows
|
| 340 |
+
fk_cursor = conn.execute("PRAGMA foreign_key_check")
|
| 341 |
+
fk_violations = fk_cursor.fetchall()
|
| 342 |
+
|
| 343 |
+
integrity_ok = (result == "ok" and len(fk_violations) == 0)
|
| 344 |
except Exception:
|
| 345 |
pass
|
| 346 |
|