November 2, 2026
Understanding the Linux Command Line — The Theory Behind Options, Word Splitting, and How a…
Breachlab Guide Theory • Day 2 | Why “options” are only a convention, why the shell splits your words before any program sees them, and how…

By Someone
7 min read
Breachlab Guide Theory • Day 2 | Why "options" are only a convention, why the shell splits your words before any program sees them, and how that gap turned into a real class of vulnerabilities
Disclaimer: This series is for educational purposes only. Everything discussed is studied on Breachlab's legal practice targets and through published research. Don't test these ideas on systems you don't own or don't have written permission to test. In line with Breachlab's guidelines, this article explains concepts and history; it contains no level answers or step-by-step solutions.
In the practical guide for Ghost Level 1, a file that was plainly visible in ls refused to open. A leading dash made cat treat the name as an option, a lone dash made it read the keyboard, and a space split one filename into two.
It's tempting to file that under "Linux quirks" and move on. But when you ask why it happens, you uncover one of the oldest and most persistent ideas in Unix security. A command line goes through two interpreters before anything is executed, and neither of them knows what the other intended.
This article accompanies the practical guide for Day 2 and focuses on the reasoning behind the behaviour rather than on typing commands. We'll look at where "options" actually come from, what the shell does to your text before any program sees it, why Linux allows such strange filenames at all, and how security researchers turned this ambiguity into real attacks.
The Kernel Has No Idea What an Option Is
Why the dash is a social agreement between programs, not a feature of the operating system.
When the shell starts a program, it eventually calls execve, the system call that replaces a process with a new program. One of its parameters is argv: an array of plain strings. That's it. The kernel doesn't mark any string as "option" or "filename". -v, notes.txt and --help all arrive as identical, unlabelled pieces of text.
Deciding what each string means is entirely the job of the program receiving it. Early Unix programs each parsed their own arguments by hand, and a habit formed: arguments starting with - are switches, and everything else is something to work on. That habit was later formalised by the getopt library function and then written into the POSIX standard as the Utility Syntax Guidelines.
Three of those guidelines explain almost everything you saw on Level 1:
- Guideline 4 says all options should be preceded by the - delimiter character. This is the rule that turns
-vinto a switch. - Guideline 10 says the first -- that isn't an option-argument marks the end of options. That's why
cat -- -vworks. - Guideline 13 says a - operand should mean only standard input, or a file named -. The standard itself admits the ambiguity, and leaves it to each utility to choose.
Notice the language: should. These are guidelines, and every program implements them itself. That's why -- works with cat but isn't guaranteed to work with every tool, and why the ./ prefix (which sidesteps option parsing entirely) is the more universal fix.
The Shell Splits Your Words Before Any Program Reads Them
Why a space is a delimiter until you say otherwise, and why quoting can't save a filename that starts with a dash.
Before execve is ever called, the shell has to turn your single line of text into that argv array. The POSIX Shell Command Language defines this as a series of steps: tokenising the line, expanding variables and wildcards, field splitting on whitespace (controlled by the IFS variable), and finally quote removal.
That order explains the space problem precisely. An unquoted space is a word boundary, so field notes.txt becomes two array elements. Quotes and backslashes tell the shell "don't split here", so the name reaches the program as one string.
It also explains a subtle point from the guide: quoting doesn't fix a leading dash. Quote removal is the last thing the shell does, so cat "-v" hands cat the exact same string as cat -v. The quotes protected the text from the shell, but the program never saw them, and it applies its own option rules regardless.
So there are two separate interpreters with two separate rulebooks. The shell decides where arguments begin and end, and the program decides what each argument means. Ghost Level 1 lives in the gap between them.
Filenames Can Be Almost Anything
Why Linux allows names that break its own tools.
You might wonder why Linux allows a file called - at all. The answer is that, at the filesystem level, a Unix filename is almost any sequence of bytes. Only two are forbidden: the / that separates path components, and the NUL byte that ends a string in C. Spaces, leading dashes, tabs, even newlines are all legal.
POSIX does define a portable filename character set (letters, digits, period, underscore and hyphen) and treats names starting with a hyphen as non-portable. But "non-portable" is advice, not enforcement.
The security researcher David A. Wheeler has written about this tension since 2009 in his essay Fixing Unix/Linux/POSIX Filenames. His argument is that this near-total freedom causes a steady stream of bugs and security flaws, because almost no script handles every legal name correctly. His recommendations include forbidding control characters, restricting leading dashes, and prefixing wildcards with ./. He also notes that the 2024 POSIX revision chose to keep allowing newlines in names and add tooling to cope with them instead, which he considers only a partial fix.
The lesson for a beginner is simple. The filesystem lets an attacker choose almost any name, and the tools you run assume names are well-behaved. That gap is where problems appear.
From Annoyance to Vulnerability: Argument Injection
How "my cat command is hanging" turns into "someone else's command is running my options".
On Level 1 the confusion only hurt you. Now flip it around: what if someone else controls the filename, and you (or a privileged script) run the command?
In 2014, Leon Juranic of DefenseCode published Back To The Future: Unix Wildcards Gone Wild. The core idea is elegant and a little alarming. An unquoted wildcard like * expands to every filename in a directory, and that expansion happens before the program parses its options. If an attacker can create files in a directory that another user, cron job or web application later processes with a wildcard, they can plant files whose names begin with -. The program then receives those names as options. The paper showed this affecting common tools such as rm, chown, chmod, tar and rsync, ranging from changed file ownership to, in some configurations, command execution.
Nothing in that attack is a memory-corruption bug. Every program behaves exactly as documented. The weakness is purely that data (a filename) is allowed to cross into the space reserved for instructions (options).
MITRE catalogues this pattern as CWE-88: Improper Neutralization of Argument Delimiters in a Command ("Argument Injection"). Software builds a command for another component, doesn't properly separate its intended arguments, and so input gets read as extra options.
It isn't only a problem in shell scripts, either. In 2017, CVE-2017–1000117 showed that Git could be handed an ssh:// URL whose host part began with a dash. Git passed that "hostname" to the local ssh client, which read it as a command-line option instead of a host. A crafted repository URL, including one hidden in a submodule, could therefore lead to command execution on the machine running Git. Patched versions added checks that reject hosts and paths beginning with a dash.
That's the same principle you met on Level 1, just in a different program.
The Principle Behind Every Fix
Every defence is a way of removing ambiguity about which strings are data.
Looked at through theory, the four fixes from the practical guide aren't four tricks. They're three applications of one idea:
- Make data look unlike instructions. A ./ prefix means the argument can't start with a dash. Wheeler recommends exactly this for wildcards (./* instead of *).
- Mark where the instructions end. -- is the explicit delimiter from POSIX Guideline 10, and it's the standard mitigation for CWE-88 when a program supports it.
- Don't hand the program a name at all. Input redirection (<) makes the shell open the file, so the program never parses a filename.
In application code, the same principle shows up as passing arguments as an array instead of building a shell string (for example, Python's subprocess.run([...]) without shell=True), and as allowlisting which options a user can influence.
Why These Concepts Matter
Each idea in this article answers part of one question: how does a line of text become a decision about what to execute?
execveandargvshow that the operating system passes plain strings, and that "options" are a convention every program implements for itself.- The POSIX Utility Syntax Guidelines explain why the dash means "option", why -- ends options, and why a lone - means standard input.
- Shell word splitting and quote removal explain why spaces break names, and why quotes can't rescue a leading dash.
- The near-anything filename rules, and Wheeler's critique of them, explain why attackers get to pick the strings in the first place.
- Juranic's wildcard research, CWE-88 and the 2017 Git CVE show how the same ambiguity turned into real security flaws.
For a pentester, this is a mindset as much as a fact. Whenever a system takes input and later hands it to another program, ask: could this data be read as an instruction? That question will come up again throughout Breachlab, from shell scripts to web apps to CI/CD pipelines.
The Breachlab Journey Continues
This series works through Breachlab one level at a time, pairing every practical guide with a theory companion like this one. Ghost builds the Linux foundations; later tracks build on them for post-exploitation, web, cloud and Active Directory work.
Breachlab is free and runs entirely on donations. If it's teaching you something, you can support it here: breachlab.org/donate
Read the practical companion to this article: Learning Cybersecurity from Scratch with Breachlab — Ghost Level 1
Watch the deep dive
Follow along with the complete Breachlab playlist on YouTube: Breachlab playlist on Assert True. The channel is Assert True on YouTube.
GitHub Repository
My notes and progress for every level live here: github.com/Someone-anon-coder/Breachlab
Follow the complete series
- Breachlab Ghost I list on Medium: every Breachlab guide and theory article in one place
- Cybersecurity list on Medium: my wider cybersecurity learning notes
My other learning series
Arbiter (Java) Guide · Arbiter (Java) Theory · Python Guide · Python Guide Theory · GoLang Guide · GoLang Guide Theory · My Progress — Matplotlib
What's Next?
In the next Breachlab Guide Theory article, we'll explore the theory behind Ghost Level 2: why Unix hides "dotfiles" by default (a convention that famously began as a shortcut in the code of ls), what that means for where configuration and secrets end up, and why "not shown" is never the same as "protected".
Happy hacking! 🛡️
References
- The Open Group, Base Specifications Issue 7, Chapter 12: Utility Conventions (Utility Syntax Guidelines): pubs.opengroup.org/onlinepubs/9699919799.2008edition/basedefs/V1_chap12.html
- The Open Group, Shell Command Language: Word Expansions and Quoting: pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
- GNU Bash Reference Manual, Word Splitting: gnu.org/software/bash/manual/html_node/Word-Splitting.html
- David A. Wheeler, Fixing Unix/Linux/POSIX Filenames (2009, updated 2024): dwheeler.com/essays/fixing-unix-linux-filenames
- David A. Wheeler, Filenames and Pathnames in Shell: How to do it Correctly: dwheeler.com/essays/filenames-in-shell.html
- Leon Juranic (DefenseCode), Back To The Future: Unix Wildcards Gone Wild (2014): exploit-db.com/papers/33930
- Help Net Security, Exploiting wildcards on Linux (2014): helpnetsecurity.com/2014/06/27/exploiting-wildcards-on-linux
- MITRE, CWE-88: Improper Neutralization of Argument Delimiters in a Command ("Argument Injection"): cwe.mitre.org/data/definitions/88.html
- NSFOCUS, Git SSH CVE-2017–1000117 analysis: blog.nsfocus.net/git-ssh-cve-2017–1000117