Write three Semgrep taint rules for the three sinks
Injection classes just walked — Java deserialization,
JDBC SQL injection and OS command injection — each graded against a true-positive fixture it
must catch and a false-positive twin it must ignore. This mirrors the actual job task: a
security engineer's rule is only useful once it stops flagging the codebase's safe patterns.
It runs on your Mac, not in the browser — Pyodide has no semgrep binary.
src/sec_semgrep/rules/deserialization.yaml — flag new
ObjectInputStream(...) when the stream comes from a socket or servlet request;
ignore one built from a local file.src/sec_semgrep/rules/sqli.yaml — flag a request parameter reaching a SQL
string via concatenation, whether it's handed to Statement.executeQuery() or to
Connection.prepareStatement() before any placeholder binding; ignore a
literal query bound with setString.src/sec_semgrep/rules/exec_taint.yaml — flag Runtime.exec() when
the command comes from a request parameter; ignore a hardcoded command.The tests are ordinary pytest and ship in the public folder with the starter — read them first; the names below are the check list. Solutions are not published.
Needs git. uv installs the right Python itself, so nothing else is required.
# once, anywhere on your machine
git clone https://github.com/theDocWho/ai-ml-roadmap.git
cd ai-ml-roadmap
No git? Download the ZIP, unzip it, and cd into the unzipped folder instead.
From the repo root:
# one-time: uv (https://docs.astral.sh/uv/) manages the venv and pins Python ≥ 3.12
cd exercises/sec-semgrep && uv sync && uv run pytest -q
Done when uv run pytest -q prints 6 passed. Rerun
after every edit — a Semgrep rule change is instant, no rebuild needed.
test_deserialization_flags_untrusted_stream / test_deserialization_ignores_local_filetest_sqli_flags_concatenated_prepared_statement / test_sqli_ignores_parameterized_querytest_exec_flags_tainted_command / test_exec_ignores_hardcoded_commandEach pair runs the same rule against its true-positive and false-positive fixture: exactly one finding on the first, exactly zero on the second.
This is self-attestation — the site cannot see your terminal, so the box and the button are you telling The Path the suite went green on your machine.
pattern-sources: [{pattern: $SOCK.getInputStream()},
{pattern: $REQ.getInputStream()}], pattern-sinks: [{pattern: "new
ObjectInputStream($X)"}].executeQuery($SQL). PreparedStatement.executeQuery() takes
no argument — the tainted string was already consumed by
prepareStatement($SQL) a line earlier, so that call is its own sink too.$REQ.getParameter(...), sink is
$RT.exec($CMD). Reaching for a bare pattern: instead of
mode: taint is what makes a rule fire on the hardcoded command fixture too.uv run semgrep --config
src/sec_semgrep/rules/<name>.yaml fixtures/<tp|fp>/<File>.java --json
shows exactly what a rule matched, without pytest's summary in the way.