What does tempfile.NamedTemporaryFile give you that open('/tmp/report.csv','w') does not?
answer
- Who else can write in that directory?
- A guessable name is a target
- Created atomically, not merely opened
- Owner-only mode, no world bits
- Removed when the object closes
basics
~20 stempfile.NamedTemporaryFile creates a uniquely named file atomically, with owner-only 0o600 permissions, and removes it when closed. A fixed /tmp path is guessable, may already exist as somebody else's symlink, and inherits a looser mode from the umask.
solid answer
~40 s`/tmp` is world-writable, so any local account can create names in it; the sticky bit only stops others deleting *your* files, not creating theirs. `open('/tmp/report.csv', 'w')` follows a symlink an attacker planted at that path and truncates whatever it points at, and the file it does create takes mode `0o666` minus your umask — commonly world-readable. `tempfile.NamedTemporaryFile` goes through the same helper as `tempfile.mkstemp`: an unpredictable name, an `os.open` carrying `os.O_CREAT | os.O_EXCL` so creation fails rather than reusing an existing path, a requested mode of `0o600`, and a retry on collision. It also unlinks the file on close on POSIX, so a `with` block cleans up even on an exception. If you never need the name at all, `tempfile.TemporaryFile` is stronger still.
code
python · 9 linesimport os
import stat
import tempfile
with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", encoding="utf-8") as f:
f.write("id,amount\n1,83\n")
f.flush()
print(f.name)
print(stat.filemode(os.stat(f.name).st_mode))go deeper
Recall the two concrete guarantees: an unpredictable name created atomically, and mode 0o600 rather than whatever the umask allows. Be able to write the with tempfile.NamedTemporaryFile(...) form from memory.
Explain the mechanics: os.O_CREAT | os.O_EXCL makes the kernel do the check and the create in one step, and the requested 0o600 cannot be widened by a umask. Say why chmod-after-create is not equivalent.
Show the judgement call about which variant fits: an anonymous tempfile.TemporaryFile when only you touch the data, NamedTemporaryFile when a path must be handed out, tempfile.mkdtemp when a whole working set needs a private directory.
Own the standard: temp-file handling is a per-service convention, not a per-file choice. Decide where staging data may live, whether it may touch disk at all for sensitive payloads, and how the rule is enforced in review and static analysis.
### A fixed path in a shared directory is a security decision On a Unix host `/tmp` is world-writable with the sticky bit set. The sticky bit means only a file's owner may unlink or rename it — it does **not** stop any other local account from *creating* new entries in that directory. `/tmp` is therefore a namespace every local user can write into, and the name `report.csv` inside it is not yours until you have actually created it. `open('/tmp/report.csv', 'w')` then does two things nobody would choose deliberately. First, mode `'w'` means "create if absent, truncate if present", and it follows symbolic links. If a local attacker — or a compromised service account, or a second copy of your own job — has already placed a symlink at `/tmp/report.csv` pointing at a file your process is allowed to write, your open truncates that target and then fills it with your export. The attacker never needed write access to the victim file; they borrowed your process's privileges. This is the classic symlink attack, and it is why the whole `tempfile` module exists. Second, the file you do create is born with mode `0o666` reduced by the process umask. On a typical host the umask is `0o022`, leaving `0o644` — world-readable. Every row you stage there is readable by every account on the machine for as long as the file exists. ### What NamedTemporaryFile does instead `tempfile.NamedTemporaryFile` is a wrapper over the same private helper that `tempfile.mkstemp` uses, and it applies four defences at once. **An unpredictable name.** The random component comes from the module's own name generator, not from the process id, the wall clock, or a counter. That matters because a name derived from a timestamp or a PID is guessable by anyone who can watch the process table. **Atomic, exclusive creation.** The underlying `os.open` call carries `os.O_CREAT | os.O_EXCL`. `O_EXCL` makes the create fail with `FileExistsError` if *anything* already exists at that path, including a dangling symlink. The kernel performs the existence check and the creation as one indivisible operation, so there is no window between "is it free?" and "take it". On a collision the module discards that name and tries another. **Owner-only permissions.** The mode requested is `0o600`. A umask can only clear bits, never set them, so the resulting file can never be more permissive than owner read/write, whatever the caller's umask happens to be. You never need a follow-up `os.chmod` — and that matters, because chmod-after-create is itself a window during which the file is readable. **Cleanup bound to the object.** On POSIX the file is unlinked when the wrapper is closed, so a `with` block removes it even if the body raises. ### Reading the guarantees precisely The object yields the path as its `.name`, and that name is genuinely on disk — that is the whole point of *Named*TemporaryFile versus `tempfile.TemporaryFile`, which on Linux can create a file that has no directory entry at all and therefore cannot be reached by name by anyone. Prefer `TemporaryFile` whenever the file is only ever touched through the object you hold; reach for `NamedTemporaryFile` only when something else genuinely needs the path. The default mode is `'w+b'` — binary. Passing `mode='w'` gives text, and then `encoding=` is worth naming explicitly rather than inheriting the locale default. `delete=True` is the default; `delete_on_close` (added in Python 3.12) lets you close the handle while keeping the file until the context manager exits, which is what you want when another process must open the path. `tempfile.mkdtemp` is the companion for the many-files case: it creates a directory with mode `0o700`, and inside a directory only you can enter, ordinary names are safe again because no other account can create entries there. ### The failure modes people ship Two patterns keep reappearing in reviews. The first is `os.path.exists(path)` followed by `open(path, 'w')` — a check and a use with a schedulable gap between them, which is exactly the race `O_EXCL` was invented to close. The second is creating the file with a permissive mode and calling `os.chmod` right afterwards; the file is world-readable for the duration, and on a busy host that duration is long enough. A third, quieter one: relying on the umask. A library cannot know the umask of the process that imported it, and a service manager may set it to `0o000`. Requesting `0o600` at creation is the only version of that promise that holds.
- When would you reach for tempfile.TemporaryFile instead of tempfile.NamedTemporaryFile?Whenever nothing outside your process needs the path. `tempfile.TemporaryFile` hands back an open file object and, on Linux, can create a file with no directory entry at all, so no other account can reach it by name and there is nothing left behind if the process is killed. `NamedTemporaryFile` exists only for the case where some other tool or child process must be given a path.
- Does the sticky bit on /tmp not already protect you?No. The sticky bit stops other users unlinking or renaming files they do not own. It places no restriction on creating new names, so an attacker can still plant a symlink at a path you have not yet created. It also does nothing about the file mode: a `0o644` export in `/tmp` is readable by everyone on the host regardless.
- Your code creates the file and then calls os.chmod to lock it down. What is wrong with that?There is a window between the create and the chmod during which the file carries the permissive mode, and anything a competing process opens in that window keeps its access through the descriptor even after the mode changes. Request `0o600` at creation instead — `tempfile.mkstemp` and `tempfile.NamedTemporaryFile` already do, and a umask can only remove bits, never add them.
A fixed name in /tmp is like leaving a labelled envelope on a shared hallway table; anyone can swap it for their own before you fill it in. mkstemp-style creation is asking the building for a fresh locked box that it refuses to hand over if it is already taken.
saying these in an interview costs you the question
- Thinks files in /tmp are private to the creating process
- Believes a timestamp or PID makes a name unguessable
- Creates the file first, then chmods it to 0600
- Relies on the process umask to keep temp files private
- Treats deleting the file afterwards as the security control
- Says the sticky bit on /tmp prevents symlink attacks