EXERCISE 2 — Parameterized or not? (the string-formatting trap) =============================================================== (a) db.query(`SELECT ... WHERE id = ${id}`) VULNERABLE. The `${id}` is a JS TEMPLATE LITERAL: the value of id is INTERPOLATED INTO THE STRING before db.query ever sees it. The driver receives one finished string with the value already baked in — identical to "... WHERE id = " + id. There is no separate value argument, so no binding occurs. id = "5 OR 1=1" injects. NOT parameterized. (b) db.query("SELECT ... WHERE id = ?", [id]) SAFE (parameterized). The query string contains a PLACEHOLDER (?) and NO value; the value is passed as a SEPARATE ARGUMENT ([id]). The driver parses the template and binds id as data. id = "5 OR 1=1" becomes a literal (invalid) id that matches nothing. Correct. (c) cur.execute("... = %s" % name) VULNERABLE. This is PYTHON STRING FORMATTING: "... = %s" % name builds the final string with name substituted in BEFORE execute() is called. execute() receives a pre-built string, no separate parameters. The %s here is Python's str-format operator, NOT a SQL placeholder. name = "x' OR '1'='1" injects. NOT parameterized. (Same trap as .format() and f-strings.) (d) cur.execute("... = %s", (name,)) SAFE (parameterized). Here %s is the DB-API PLACEHOLDER, and the value is passed as a SEPARATE tuple argument (name,). The driver does the binding — name is sent as data, parsed-query already fixed. Note the ONLY difference from (c) is a COMMA vs a PERCENT: "%s" % name (format -> vulnerable) vs "%s", (name,) (bound -> safe). This tiny syntactic difference is the whole security boundary. THE RULE THAT DISTINGUISHES THEM: - PARAMETERIZED: the query string contains only PLACEHOLDERS (?, $1, :name, %s) and the VALUE IS PASSED AS A SEPARATE ARGUMENT to the driver, which performs the binding. (b), (d). - VULNERABLE: the value is PUT INTO THE QUERY STRING by the language's own string mechanisms — interpolation/template literals (${...}), %-format, .format(), f-strings, sprintf, or + concatenation — so the driver receives a finished string. (a), (c). - TEST: "Is the user's value INSIDE the query string when the driver gets it, or passed SEPARATELY?" Inside the string = vulnerable; separate argument = parameterized. The presence of a placeholder character (%s, ?) is NOT enough — what matters is whether the DRIVER does the substitution (safe) or the LANGUAGE already did it (unsafe). ONE-LINE TAKEAWAY: Placeholder + separate value argument = parameterized (safe); value baked into the string via interpolation/format/concat = still vulnerable — the comma in (d) vs the percent in (c) is the entire difference.