Quoted strings that happen to start with / are misinterpreted as paths the agent intends to read or write
- Dominant language
- Shell
- Stars
- 11.2k
- Forks
- 1.9k
- Avg merge
- 14h 16m
- Merged PRs (30d)
- 6
Description
### Describe the bug
## Description
The GitHub Copilot CLI's path-access scanner misclassifies quoted shell-command arguments that happen to start with `/` as filesystem paths the action intends to read or write, even when the receiving command is a no-op or treats the argument as literal data.
The scanner inspects bash-tool command text before execution. In the tested forms, a quoted argument whose first character is `/` triggers an interactive "Allow directory access" permission prompt, presenting the argument as a candidate directory the action may read or write.
The trigger appears to depend on the token's leading character (a forward slash) rather than on whether the receiving command opens the token as a filesystem path, and rather than on whether the token names an existing directory. Strings such as `/))` (which does not name an existing directory and is not accessed by the receiving command) and `/tmp is a sentence` (whose `/tmp` prefix is allowlisted in the bare unquoted form but is not protected once the quoted argument is extended with non-path characters) both trigger the prompt.
The scanner appears to treat `echo` and the tested `awk` program-argument form differently from other tested commands: those pass, while most other tested commands trigger the prompt, including no-ops such as `true` and `:`, and data-only commands such as `printf`.
This is closely related to https://github.com/github/copilot-cli/issues/774, which describes a similar lexical-scanning failure mode rejecting command-like words in text arguments passed to non-bash tools. The present report covers a complementary case within the bash-tool path: arguments are scanned by token shape rather than by whether the receiving command would actually open them.
## Steps to Reproduce
1. Invoke one of the tested non-exempt commands, such as `true`, `:`, or `printf`, with a quoted argument whose first character is a forward slash (`/`). In the tested cases, the remainder of the string varied widely — from an allowlisted-prefix extension like `/tmp is a sentence`, to a path-shaped sensitive substring like `/etc/passwd`, to a string that does not name any directory in the tested environment like `/))` — and the prompt fired in every case.
2. The string need not be a valid path; it need not exist on disk; the command need not access files.
3. The scanner intercepts the call before bash runs with an "Allow directory access" permission prompt that defaults to "Yes, and add these directories to the allowed list".
## Example
### Quoted string starting with `/` passed to `true`
```bash
true '/))'
```
The CLI displays this interactive prompt before executing:
```
╭─ Allow directory access ─────────────────────────────────────────╮
│ │
│ This action may read or write the following path outside your │
│ allowed directory list. │
│ │
│ ╭──────────────────────────────────────────────────────────────╮ │
│ │ /)) │ │
│ ╰──────────────────────────────────────────────────────────────╯ │
│ │
│ Do you want to allow this? │
│ │
│ 1. Yes │
│ ❯ 2. Yes, and add these directories to the allowed list │
│ 3. No (Esc) │
│ │
│ ↑↓ to navigate · Enter to select · Esc to cancel │
╰──────────────────────────────────────────────────────────────────╯
```
`true` is the universal POSIX no-op: it ignores every argument and returns success. The argument `/))` does not name an existing directory in the tested environment, is not sensitive-looking, and is not accessed by `true` — it is simply a string that happens to start with `/`. Yet the prompt's title — "Allow directory access" — and the candidate text shown in its own bordered box indicate the CLI is treating `/))` as a filesystem path the action may read or write. The default option ("Yes, and add these directories to the allowed list") would proceed with the call; based on that option's label, I would expect accepting it to also persist `/))` in the allowed-directory list.
### Quoted argument that begins with an allowlisted bare token (`/tmp`)
```bash
true '/tmp is a sentence'
```
Same prompt, with `/tmp is a sentence` shown as the candidate directory. This is striking because the unquoted variant `true /tmp` passes (see Contrast table below): the allowlist that protects `/tmp` does not carry through to a quoted argument whose first character is `/`, even when the quoted content starts with the same allowlisted prefix.
### Quoted path-shaped string passed to the null builtin
```bash
: '/tmp is a noop arg'
```
Same prompt, with `/tmp is a noop arg` shown as the candidate directory. `:` is the shell null command and never inspects its arguments.
### Argument to printf
```bash
printf '%s\n' '/tmp is a noop arg'
```
Same prompt, with the quoted argument shown as the candidate directory. `printf` treats this argument as data substituted into the format string, not as a path to read. A separately tested format-string case, `printf '/tmp is a noop arg\n'`, also triggers the same prompt, so the observed scan is not limited to printf's data arguments; both tested positions are subject to it.
### `command` builtin does not change the outcome
```bash
command true '/tmp is a sentence'
```
Same prompt, with the quoted argument shown as the candidate directory. Wrapping the command with the `command` builtin does not change the scanner's classification.
### Contrast: tested forms that the scanner passes
| Form | Result |
|---|---|
| `echo '/etc/passwd is a sentence'` | passes |
| `echo /etc/passwd` (unquoted) | passes |
| `echo '/var/log was the locus'` | passes |
| `awk '/error/ {print}'` (the awk regex starts with `/`) | passes |
| `true /foo` | passes |
| `true /tmp` | passes |
| `true /xyz` | passes |
The scanner clearly recognizes single-quoting (it extracts the full quoted content as a single candidate path) and appears to treat `echo` and the tested `awk` form differently from other commands: `echo` arguments pass in both quoted and unquoted forms, and the tested `awk` program-argument form passes even when its first character is `/`. Comparable arguments to `true`, `:`, and `printf` trigger the permission prompt. Additionally, the tested forms `true /foo`, `true /tmp`, and `true /xyz` pass even though the receiving command is `true` — which does trigger the prompt for other tested arguments. What is not modeled in the tested forms is the receiving command's semantics: `true`, `:`, and `printf` never open files at the named path in the tested forms, yet their arguments are still subject to the path-access rule. Conversely, a quoted argument whose first character is not `/` does not trigger the prompt even when `/` appears later in the token: `true '(/ 10 2)'` passes, supporting the inference that the trigger condition is the token's leading character rather than the presence of `/` anywhere in the token.
### Asymmetry: commands that pass vs. `true`
`echo '/etc/passwd is a sentence'` passes but `true '/etc/passwd is a sentence'` triggers the permission prompt. Similarly, `awk '/error/ {print}'` passes — the awk regex argument's leading `/` is not flagged — but the same content passed to `true` (`true '/error/ {print}'`) does trigger the permission prompt. The tested commands have the same filesystem-access semantics with respect to these arguments: none of them opens the argument as a file. The asymmetry suggests command-specific treatment for `echo` and the tested `awk` form rather than a general rule about whether the argument can be opened as a path.
I have not exhaustively tested every command, every leading-`/` argument shape, every allowlisted path, or every argument position. The claims here are limited to the tested contrast cases above and to the reproducible permission prompts.
## Expected Behavior
A semantics-aware path-access guard would:
* Distinguish between commands that use arguments as filesystem paths (e.g., `cat`, `mv`, `cp`, `chmod`) and commands that do not open the tested argument positions as paths (e.g., `true`, `:`, `printf`, and `echo`).
* Apply the path-access rule only to arguments at positions where the receiving command will treat the argument as a filesystem path.
* Treat literal text data passed to data-only commands as data, not as path-access claims.
## Actual Behavior
The observed behavior is consistent with the scanner inspecting quoted leading-`/` arguments for the tested non-exempt commands and treating them as candidate directory-access strings without distinguishing:
* Whether the receiving command opens arguments as filesystem paths.
* Whether any argument position of the receiving command can open filesystem paths; in the tested `printf` cases, both format-string and data positions are scanned even though neither is opened as a path.
* Whether the bytes name an existing directory or just happen to have a leading `/`.
Within the tested forms, the scanner appears to recognize quoting (it extracts the entire quoted content as a single candidate path) and to pass arguments to `echo` and the tested `awk` form, but it does not pass comparable arguments to other tested commands or argument positions.
## Impact
* **Requires interactive approval for string operations that do not access files**: print operations such as `printf '%s' '/tmp is a noop arg'` trigger the permission prompt; no-op operations such as `true '/tmp was the locus'` trigger the permission prompt.
* **Interrupts legitimate agent actions before execution**: the agent can choose a command whose runtime behavior would not open the named path, but the pre-execution scanner still intercepts the action with a permission prompt based on the argument text alone.
* **May pollute the allowed-directory list if the default option is accepted**: the prompt's default option ("Yes, and add these directories to the allowed list") is labeled as adding the candidate string to the allowed list; based on that label, I would expect accepting it to persist non-path strings such as `/tmp is a sentence` as registered allowed directories. A user who casually accepts the default could then accumulate arbitrary English-prose fragments in their allowed-list configuration.
* **Can interrupt OS-development conversations** involving examples or prose that begin with `/`, similar to the impact described in #774 for the `session` tool.
* **Requires indirect workarounds**: routing all data through `echo` or the tested `awk` program-argument form, splitting strings to avoid a leading `/`, paraphrasing prose, or piping through unrelated transformations to obscure the leading slash.
## Environment
* GitHub Copilot CLI version: 1.0.48
* OS: Linux
* Use case: passing literal strings (English prose, JSON data, command examples) that incidentally begin with system-path-like substrings.
## Suggested Fix
One narrow fix would be to make the scanner command-semantics-aware:
1. Maintain a per-command path-argument-position table for common commands.
2. Apply the same observed behavior of passing `echo` arguments and the tested `awk` program-argument form to other known data-only / no-op commands such as `true`, `:`, and `printf`.
3. For commands that do have path-bearing argument positions, such as `cat`, `cp`, `mv`, or `chmod`, inspect only the positions that the command can actually use as filesystem paths.
A runtime filesystem-access signal, where available, could complement these lexical checks. A narrower available fix is to avoid applying path-access rules to known data-only argument positions.
Note: the scanner already passes arguments to `echo` and the tested `awk` form, and it passes the tested forms `true /foo`, `true /tmp`, and `true /xyz`. The fix is to extend comparable behavior to other data-only commands (`true`, `:`, `printf`) and to no-op builtins generally.
## Workaround
* Route data through `echo` — or through the tested `awk` program-argument form — instead of other commands when the data starts with `/`.
* Avoid any quoted argument whose first character is `/`, where possible. (Often impossible when the data is determined by the user's request.)
* Use heredoc redirection to a data-only command rather than quoted arguments. Note that heredoc-based workarounds may be affected by separate heredoc-body scanning behavior (https://github.com/github/copilot-cli/issues/3334), so this workaround is not always reliable.
## Related
* https://github.com/github/copilot-cli/issues/774 — Related report: a similar lexical-scanning failure mode rejects command-like words in text arguments passed to **non-bash tools** (e.g., the `session` tool). Both reports describe interception behavior when text containing pattern-matching tokens flows through contexts where the surrounding grammar makes clear the text is data, not the protected form the scanner pattern-matches against.
* https://github.com/github/copilot-cli/issues/3334 — Related report: heredoc-body scanning misinterprets ordinary words like `kill`, `pkill`, `killall` in heredoc payload as shell `kill` commands without arguments. Similar lexical-scanning failure mode: text that looks like a protected pattern is rejected even though the surrounding grammar makes clear it is data.
---
**Note:** Together with #774 and #3334, this report describes interception behavior across multiple contexts that follow a similar lexical-scanning failure mode. The scanner already passes `echo` arguments, the tested `awk` form, and the tested unquoted paths `/foo`, `/tmp`, and `/xyz` for `true`, so a change to extend command-semantics awareness to other commands could be scoped specifically to a per-command path-argument-position table.
### Affected version
GitHub Copilot CLI 1.0.48.
### Steps to reproduce the behavior
_No response_
### Expected behavior
_No response_
### Additional context
_No response_
Contributor guide
Assessment
This issue has not been assessed yet.