Security and Safe APIs
Unsafe defaults inside ordinary standard-library calls, grouped by where the risk enters: untrusted data, dynamic execution, the host, the crypto primitive, and what leaks back out.
part ofPythonoverview, primer and where to startread it →on this pageshowhide
explore
- Untrusted Input Handling28 questions
- Unsafe Loader Defaults4 questions
- XML Entity Attacks4 questions
- Archive Extraction Filters4 questions
- Parser and Integer Limits4 questions
- Format String Attribute Leaks4 questions
- Parameterized Queries4 questions
- Unicode Normalization Bypasses4 questions
- Dynamic Execution Risks16 questions
- eval, exec and compile4 questions
- Parsing Literals Not Code4 questions
- Sandboxing Untrusted Code4 questions
- getattr and Dynamic Imports4 questions
- Host Attack Surfaces19 questions
- shell=True and Argument Lists3 questions
- Directory Traversal Defenses4 questions
- Temp File Race Conditions4 questions
- Inherited Environment Trust4 questions
- sys.path Hijacking4 questions
- Cryptographic Primitives22 questions
- secrets versus random4 questions
- Constant-Time Comparison3 questions
- Password Hashing with hashlib4 questions
- Digests and file_digest4 questions
- TLS Verification and Trust4 questions
- Signing with hmac3 questions
- Information Disclosure12 questions
- Credential Handling4 questions
- Traceback and Debug Leaks4 questions
- Logging Without Leaking4 questions
questions
97 · 5 sectionsWhy is calling pickle.loads() on untrusted bytes the same as running that data?
basics
~20 sUnpickling is not parsing. A pickle stream is a tiny program whose opcodes import names and call them, so pickle.loads on attacker-controlled bytes can invoke any importable callable with attacker-chosen arguments before it returns anything.
Why pass values to sqlite3.Cursor.execute as a parameter tuple instead of formatting them into the SQL text?
basics
~20 sPlaceholders keep the statement and the data on separate channels. sqlite3 compiles the SQL text first, then binds each parameter into a slot of the compiled statement, so a value can never become syntax. String formatting merges the two.
What does tarfile's 'data' extraction filter block, and why is it the 3.14 default?
basics
~20 sThe data filter refuses tar members that would land outside the destination directory, refuses links whose target is absolute or escapes, and rejects device and FIFO members, while clearing ownership and risky permission bits. Python 3.14 makes it the default for extraction.
Why is str.format with an attacker-supplied template string a data-leak risk?
basics
~20 sPython's format mini-language allows attribute access and indexing inside a placeholder, so whoever writes the template chooses what is read out of the objects you pass. A chain through a method's globals reaches module-level secrets.
Why does Python's re pattern ^(\d+)+$ hang on a crafted 30-character string?
basics
~20 sPython's re module uses a backtracking engine. Nesting one quantifier inside another over the same characters gives it exponentially many ways to split the input, so a 30-character string that ultimately fails to match costs about a billion steps.
Why is Python's eval() a code-execution risk on a string that came from a user?
basics
~20 seval() compiles and runs its text as a Python expression, so one expression can import a module and touch files, processes or sockets with the privileges of your process. Parse untrusted text; never evaluate it.
Why is there no reliable way to sandbox untrusted Python inside the same interpreter process?
basics
~20 sThe Python object graph is fully connected: from any value you can follow attributes back to the type system, the builtins, and every loaded module. No matter which names you hide, code can walk to them. Real isolation is an OS-level boundary — a separate process, container, or VM.
Why does importlib.import_module on a user-supplied name amount to arbitrary code execution?
basics
~20 sImporting a module executes that module's top-level code. If the caller names the module, the caller chooses which code runs, and search order plus every installed distribution decide what that name resolves to. Import from a fixed allowlist instead.
Which expressions does ast.literal_eval accept, and which does it refuse?
basics
~10 sast.literal_eval evaluates only literal structures: strings, bytes, numbers, booleans, None, Ellipsis, and the tuple, list, dict and set displays built from them. Any name, attribute, operator or function call raises ValueError instead of running.
How can Python code with no builtins reach os.system by walking __class__ and __subclasses__?
basics
~20 sEvery object exposes class, and from there bases reaches object, whose subclasses() lists every class the interpreter has already loaded. An attacker scans that list for a gadget class whose method or globals reaches os or subprocess, so stripping builtins never removes the path back to dangerous code.
Where do the entries in Python's os.environ come from, and who controls them?
basics
~20 sThe process that launched yours supplies the environment block, and Python copies it into os.environ when the os module is first imported. Whoever controls the launch - a shell, a container spec, a job scheduler - controls the values.
In subprocess.run, what changes between passing a list and passing shell=True?
basics
~20 sWith a list, CPython execs the program directly and each element becomes exactly one argv entry, so nothing tokenises it. With shell=True the whole string goes to /bin/sh -c, where ; | $() and word splitting are live syntax.
Why is it risky to run a Python script from a world-writable directory?
basics
~20 sPython puts the script's own directory first on sys.path, ahead of the standard library. Anyone who can write there can drop a file named like a module the script imports, and their code runs at import time.
Why does os.path.join('/srv/uploads', name) not guarantee a path inside /srv/uploads?
basics
~20 sos.path.join throws away everything to the left of a component that is already absolute, so joining '/etc/passwd' onto a base returns '/etc/passwd'. A relative name holding '..' also climbs out. Joining concatenates; it never confines.
Why is tempfile.mktemp unsafe where tempfile.mkstemp is not?
basics
~20 stempfile.mktemp only returns a name. Between that return and your own open() an attacker can create that path, usually as a symlink. tempfile.mkstemp creates and opens the file itself in one exclusive step, so there is no window to exploit.
How does hashlib's update, digest and hexdigest cycle hash data incrementally?
basics
~20 sA hashlib hash object accumulates bytes across repeated update() calls, so feeding data in chunks matches hashing it in one call. digest() returns the raw bytes, hexdigest() the same value as a hex string; reading either does not reset the object.
Which hashlib functions are built for password storage, and why not hashlib.sha256?
basics
~20 shashlib offers two password functions: pbkdf2_hmac and scrypt. Both take a per-user salt and a work factor you choose, so one guess costs real time. hashlib.sha256 is built to be fast, which is exactly wrong for passwords.
Why must a password-reset token come from `secrets`, not `random`?
basics
~20 sThe random module's default generator is a Mersenne Twister whose entire internal state can be reconstructed from a few hundred observed outputs, so later tokens become predictable. secrets draws each token from the operating system's cryptographic source instead.
Why does `hmac.compare_digest` exist when `==` already compares two bytes objects?
basics
~20 s== on two bytes objects stops at the first differing byte, so the time it takes reveals how much of a secret an attacker guessed right. hmac.compare_digest always does the same work; secrets.compare_digest is the same function.
What does setting ssl.SSLContext.verify_mode to ssl.CERT_NONE actually give up?
basics
~10 sIt turns off certificate verification, so a Python client accepts any certificate at all, including one an attacker generated. The connection stays encrypted but is no longer authenticated, which defeats the point of TLS.
Why must traceback.format_exc() output never be sent in an HTTP response body?
basics
~20 sA formatted traceback exposes absolute file paths, the module layout, the failing source lines and sometimes the repr of local values such as connection strings. Log it server side under an opaque reference id and return only that id with a generic message.
Why pass %s arguments to logging.Logger.info instead of building an f-string?
basics
~20 sThe logging module interpolates %s arguments only when a record is actually emitted, so a filtered-out call costs nothing and the raw values stay on LogRecord.args, where a redaction filter can still rewrite them. An f-string bakes them in first.
How do you keep an API key field out of a Python dataclass's repr()?
basics
~20 sDeclare the attribute with dataclasses.field(repr=False). The generated repr then omits it, and str() omits it too because it falls back to repr. The value is still an ordinary attribute, so dataclasses.asdict(), vars() and pickling still carry it.
What happens to assert statements and __debug__ when Python runs with -O?
basics
~20 sThe -O flag sets the builtin __debug__ to False and makes the compiler omit every assert statement from the bytecode entirely. A check written as an assert then guards nothing, so an assert must never carry input validation or a security decision.
Why read a password with getpass.getpass() instead of input() in a Python CLI?
basics
~20 sgetpass.getpass() turns terminal echo off while the user types, so the password never appears on screen or in a terminal recording, and it reads from the controlling terminal rather than a redirected stdin. input() echoes every character.