Course outline

Bash / Advanced: channels, concurrency, and maintainable CLI design / Lesson 16

Keep external data out of executable syntax

Review second parsing, command selection, arithmetic, sourcing, environment, and diagnostics as separate trust boundaries.

About 4 min readContent reviewed
On this page

Lesson goals

  • Construct exact argument lists without eval.
  • Recognize non-obvious evaluation and startup boundaries.
  • Prevent format and option confusion without claiming quoting validates everything.

Quoting preserves data but does not authorize it

Correct quoting prevents ordinary splitting and globbing of a value, but the called program can still interpret that value as an option, path, expression, or configuration directive. Define allowed command selection and operand scope separately. A script accepting a destination should validate that destination according to its purpose, not merely check that it is nonempty. Do not build a command string and feed it to eval or bash -c to repair lost boundaries. That introduces a new parse where data can become executable shell syntax with the script’s authority.

Select commands from a fixed vocabulary

A mode can map to a fixed function or trusted executable while user values remain quoted arguments. A command array preserves that separation in Bash. Never treat a user-supplied variable name, array subscript, or arithmetic expression as automatically inert: these interfaces can perform additional interpretation under Bash’s rules. If indirection is necessary, constrain names and design a narrow API rather than accepting arbitrary expressions. Prefer direct keyed data lookup with tested semantics over clever variable-variable construction. No shell restriction turns an unreviewed arbitrary command string into a safe application interface.

bash Pass shell-looking text as one literal value
user_value='literal; not a command * %s'
command=(printf '%s\n' "$user_value")
"${command[@]}"
Expected output
literal; not a command * %s

The semicolon, wildcard, and percent sequence remain data. The format and command name are fixed, and no second parser evaluates the value.

Inherited environment can affect execution

PATH chooses executable lookup, and BASH_ENV can name code read by a noninteractive Bash under its documented startup rules. Other environment and shell-option settings can also change behavior. A reproducible test or automation boundary should control relevant inherited state rather than relying on a personal login session. --noprofile and --norc address particular startup paths; they do not by themselves neutralize every environment-driven hook. Do not source untrusted configuration to obtain key/value data. Treat secret values in environments and arguments as potentially exposed through diagnostics or child processes, and collect only what a failure investigation requires.

A format string is another language boundary

printf interprets formatting directives in its format operand. Keep the format literal and pass external text to %s or another deliberately chosen conversion. Quoting an external format does not make its percent directives inert. Similarly, shell quoting does not neutralize regular expressions, SQL, or another interpreter inside a downstream program. Apply validation or parameterization at that layer using its own documented interface. Debug rendering such as Bash %q can aid a human review, but it is not authorization to execute the rendered text or a universal cross-shell transport encoding.

bash Keep the format fixed while inspecting literal data
value='100% complete; $HOME is text'
printf 'value=<%s>\n' "$value"
case $value in
  *complete*) printf 'fixed-pattern=matched\n' ;;
esac
Expected output
value=<100% complete; $HOME is text>
fixed-pattern=matched

The percent sign and dollar text are printed unchanged. The case pattern is fixed program syntax, while the compared value remains data.

Review the whole chain of interpreters

List every point where input affects command selection, file access, expression evaluation, source loading, or diagnostic disclosure. Test harmless metacharacter strings, leading dashes, empty values, and unusual whitespace. A security regression should assert exact argument receipt or rejection, not execute a malicious payload against a host. Keep examples synthetic and effects isolated. When a workflow needs complex untrusted parsing, choose a language and library designed for it rather than layering shell quoting conventions. The goal is a small auditable interface whose permitted actions are explicit and whose data never receives unexpected executable meaning.

Common errors and fixes

eval is used to repair a command string

Why it happens
Argument construction was replaced by a second parse.
How to fix it
Use a fixed command and quoted argument array.
Prevent it next time
Review every interpreter boundary explicitly.

Quoted external text becomes a printf format

Why it happens
Shell quoting was mistaken for format-language escaping.
How to fix it
Use a fixed format and pass data as a conversion operand.
Prevent it next time
Test percent characters as ordinary input.

A clean startup is assumed from --norc alone

Why it happens
Other startup hooks and inherited environment were ignored.
How to fix it
Control the relevant environment and documented startup paths.
Prevent it next time
Declare automation environment assumptions.

Check your understanding

1. Why is "$format" not safe when arbitrary external text occupies printf’s format position?

Show worked solution

Quoting preserves one argument but printf still interprets percent directives in that argument. Use a literal format such as %s and supply the text separately. This illustrates the need to validate each downstream language independently.

2. What should a harmless injection regression verify?

Show worked solution

Verify exact argument count and values for text containing semicolons, dollar signs, wildcards, and spaces, and assert rejection of disallowed command choices. Do not execute an attack string; the contract test should prove data remains data.

Key takeaways

  • Keep command selection fixed and data in exact arguments.
  • Review environment, formats, arithmetic, and sourcing separately.

Official references