Lion Cipher Lab

Harden a subprocess wrapper against argument injection (CWE-88)

Why it matters

Every secure-coding checklist tells you the same thing about running external programs: never build a shell string, always pass a list of arguments so nothing you inject can start a new command. That advice is correct, and it is exactly what subprocess.run([...], shell=False) gives you -- no shell, so ';' and '$(...)' and backticks are just inert characters in a bad filename. The problem is that the advice is incomplete, and almost no tutorial says so. Once your user's value lands in the argument vector (argv) of a program that has its OWN rich option parser -- tar, find, sort, curl, grep,…

What you'll learn

Steps in this lab

  1. The one sentence that this whole lab exists to correct
  2. Open a terminal on a GNU/Linux system (zero-assumption setup)
  3. Create docsvc.py -- the document service (naive AND hardened wrappers)
  4. Create harness.py -- the thing that measures every claim
  5. Run it once and read the headline
  6. Section [1]: the honest baseline -- shell=False really does stop shell injection
  7. Section [2]: the break -- four injections the same wrapper cannot stop
  8. Failure mode 1: the 'no shell means safe' fallacy, quantified
  9. Failure mode 2: '--' end-of-options, and exactly where it does NOT save you
  10. Failure mode 3: GNU option permutation -- 'I put it last, so it's positional' is false
  11. Failure mode 4: the leading-dash allowlist that isn't enough (order of operations)
  12. The callee can fight back -- and why you still cannot rely on it
  13. Section [4] and [5]: the fix, measured -- and why the strongest layer is 'no subprocess'
  14. Judgment: why this fix and not that one
  15. Find it in your own code, and what a reviewer looks for

Open the full interactive lab →