Get unstuck

Hint 1: Parameters are not string formatting

Pass values with execute(sql, (category, pattern)); the ? placeholders keep them out of the SQL text. Never use +, %, .format or an f-string to put a value into the query.

Hint 2: A LIKE pattern still needs care

The % and _ in a search term are LIKE wildcards. Escape them (and the escape character) and add ESCAPE so the term matches literally.

Reveal the reference patch and explanation
Fixedsearch.pyPython
def search_products(connection, category, term):
    like = term.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
    sql = (
        "SELECT id, name, price FROM products "
        "WHERE category = ? AND name LIKE ? ESCAPE '\\' "
        "AND active = 1 ORDER BY id"
    )
    return connection.execute(sql, (category, "%" + like + "%")).fetchall()

With parameters, the term "' OR 1=1 --" is just a string to match, so it finds no product named that and cannot change the WHERE clause; a UNION SELECT term likewise matches nothing and never reaches the secrets table. An ordinary term such as "cable" returns the active cables in the category, ordered by id. A term containing a literal % matches a percent sign rather than every row, because the pattern escapes it with ESCAPE. The category is bound too, so a category of "' OR '1'='1" selects nothing. active = 1 stays in the query, so inactive products never appear.

Compare it with your own fix, then rerun the tests. The editor is not changed when you open this explanation.

Inspect the actual test harness

The browser and the lab download run the same cases, including cases that catch common wrong fixes, such as comparing with one fixture value instead of the real rule. The download includes the complete test adapter and cryptographic fixtures.

Download the grader, python/cases.mjs
How the browser runs your Python

Your code runs in CPython 3.14.2 compiled to WebAssembly (Pyodide 314.0.7), in a Web Worker of its own. The test cases and the pass or fail verdict run in a second worker, which only sends your functions their inputs and reads back the values they return, so changing built-ins or the harness inside Python cannot change the result. All the cases together have a 8-second limit.

The first run downloads about 6.3 MB from this site, which your browser then keeps; later runs start in a second or two. Other pages never load it.

Available: the Python standard library, including sqlite3, ipaddress, pathlib, os.path and urllib.parse, and print(), whose output appears with your results.

Not available: network access, subprocesses and threads, and other packages from PyPI. The lab download runs the same cases with your local python3 (3.12 or newer).

Use the lesson in your own system

The lab uses an in-memory sqlite3 database the grader fills, and tests one query. Parameters bind values, not identifiers: a dynamic table or column name still needs an allowlist, and ORDER BY on a user-chosen column needs one too. Real applications also apply least-privilege database accounts and per-tenant authorization; parameterization stops the injection, not the authorization checks.

Your next task: Where does your code build SQL (or any query language) by concatenation or an f-string, and could each value move to a bound parameter?

Python sqlite3: placeholders and SQL injection

Related guide: Fix SQL injection in Python sqlite3