`--deny-tool="read(...)"` blocks ALL file reads regardless of pattern; no persistent permission profiles for non-interactive mode
- 主要言語
- Shell
- スター
- 11.2k
- フォーク
- 1.9k
- 平均マージ
- 14時間 16分
- マージ済み PR(30日)
- 6
説明
### Describe the bug
Any `--deny-tool` pattern targeting `read()` blocks ALL `read()` tool calls, not just files matching the pattern. This makes it impossible to selectively deny reads to sensitive files (e.g., `.env`) while allowing reads to the rest of the project.
Even when the deny pattern is fixed, `read()` deny is bypassed trivially via `shell(cat .env)` or `shell(sed .env)` - there is no way to deny file access at the filesystem level.
Finally, without persistent permission profiles (#307), every non-interactive invocation requires 10+ CLI flags. All three competing AI coding CLIs (Claude Code, Gemini CLI, Codex CLI) provide file-based permission configuration.
### Affected version
GitHub Copilot CLI 1.0.27.
### Steps to reproduce the behavior
### Bug 1: `read()` deny blocks ALL reads
1. Run copilot in non-interactive mode, allowing reads to the entire project but denying reads to `.env` files only:
```bash
copilot -p "Read AGENTS.md and tell me the first heading" \
--available-tools="bash,view,rg,glob" \
--allow-tool="read($(pwd)/**)" \
--deny-tool="read($(pwd)/.env*)" \
--deny-tool=write
```
2. Observe that ALL `read()` calls are denied, not just `.env`:
```
Read AGENTS.md
Permission to run this tool was denied due the following rules:
`read(/home/user/my-project/.env*)`
```
3. Copilot falls back to `shell(sed ...)` to read files, wasting a turn.
We tested four patterns (both `$(pwd)` and absolute paths) - all produce the same result:
| Pattern | Result |
| ----------------------------------------------------- | ---------------- |
| `--deny-tool="read($(pwd)/.env*)"` | Blocks all reads |
| `--deny-tool="read($(pwd)/**/.env*)"` | Blocks all reads |
| `--deny-tool="read(.env*)"` | Blocks all reads |
| `--deny-tool="read(/absolute/path/to/project/.env*)"` | Blocks all reads |
### Bug 2: `read()` deny is trivially bypassed via shell
1. Run copilot with `read()` deny for `.env`:
```bash
copilot -p "What keys are in the .env file?" \
--available-tools="bash,view,rg,glob" \
--allow-tool="read($(pwd)/**)" \
--allow-tool="shell(git diff:*)" \
--deny-tool="read($(pwd)/.env*)" \
--deny-tool=write
```
2. Copilot's `read()` is denied, but it routes around via shell:
```
Read .env
Permission denied due to: read(.env*)
Read .env contents via shell
sed -n '1,20p' .env
1 line...
The .env file contains: [contents displayed]
```
The `read()` deny only blocks the `read()` tool. `shell(cat .env)`, `shell(sed .env)` etc. all work.
### Expected behavior
1. `--deny-tool="read(.env*)"` should deny reads to `.env` files only, not ALL files.
2. File-level deny should apply across all tools - if `.env` is denied, `shell(cat .env)` should also be denied.
3. A persistent settings file (e.g., `~/.copilot/settings.json` or `.copilot/config.toml`) should allow storing permission profiles so that 10+ flags per invocation are not needed.
### Additional context
### Competing CLI tools all solve this with settings files
**Claude Code** ([permissions docs](https://code.claude.com/docs/en/permissions)):
- `~/.claude/settings.json` + `.claude/settings.json` (project-level)
- JSON-based allow/deny/ask rules with glob patterns
- 4-level hierarchy: admin > user > project-local > project-shared
- deny > ask > allow evaluation order
**Gemini CLI** ([policy engine docs](https://geminicli.com/docs/reference/policy-engine/)):
- `~/.gemini/policies/*.toml` (TOML-based policy files)
- Priority system (0-999), `argsPattern` regex matching on tool args
- Workspace + user + admin tiers with deterministic precedence
**Codex CLI** ([config reference docs](https://developers.openai.com/codex/config-reference)):
- `.codex/config.toml` (TOML-based per-project config)
- Per-path filesystem permissions: `"/path" = "read"`, `"/path" = "none"`
- Named permission profiles, sandbox modes (`read-only`, `workspace-write`)
- `:project_roots` token for portable project-scoped access
### Host tool collision with deny-tool strings
When copilot is invoked from another AI coding tool (e.g., Claude Code), `--deny-tool="shell(rm:*)"` causes the host tool's own deny-list to block the entire command, because it pattern-matches the literal text `rm` in the Bash command string. This forces users to build wrapper scripts that hide the deny flags from the host's permission system.
### Workaround
We built a wrapper script that encapsulates all permission flags and is enforced by a validation hook. This works but is a band-aid.
### Related
- #307 (Comprehensive Permissions System Improvements Proposal, closed)
コントリビューションガイド
調査の方向性
Start by reproducing the three command-line cases in the issue, comparing read() pattern matching with shell(cat/sed) access and the repeated permission flags. Review the related permissions proposal in #307 and the existing wrapper-script workaround before determining the scope. Done means path patterns selectively deny matching files, file restrictions apply across tools, and permission profiles can be reused in non-interactive invocations.
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- shell
- 領域
- cli, security
- issue の種類
- バグ
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 活発さ
- 静か
- 明瞭さ
- おおむね明確
- 初心者へのやさしさ
- 30/100