anthropics / anthropics/claude-code
sandbox credentials.files mask produces no file at all for ~/.config/gh/hosts.yml
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
## Sandbox `credentials.files` mask produces no file at all for `~/.config/gh/hosts.yml`
### Environment
- Claude Code version: `2.1.270`
- Install method: native
- Platform: Linux (remote/EC2-hosted session, launched via the Claude Code desktop app's remote client)
- `gh` version: `2.100.0`
### Config (`~/.claude/settings.json`, relevant excerpt)
```json
{
"sandbox": {
"enabled": true,
"network": {
"allowedDomains": ["github.com", "api.github.com", "..."],
"strictAllowlist": true,
"tlsTerminate": {}
},
"filesystem": {
"denyRead": ["~/"],
"allowRead": ["~/repos", "~/.gitconfig", "~/.config/git", "~/.config/gh/config.yml", "~/.cache/aws-bedrock", "~/.claude/plugins"]
},
"credentials": {
"files": [
{
"path": "~/.config/gh/hosts.yml",
"mode": "mask",
"extract": "oauth_token:\\s*(\\S+)",
"injectHosts": ["api.github.com"],
"maskDuplicates": true,
"onExtractNoMatch": "error"
}
]
}
}
}
```
### Expected behavior
Per docs (`/docs/en/sandboxing#mask-credential-files`): on Linux/WSL2, a sandboxed command reading `~/.config/gh/hosts.yml` should see a sentinel copy (real token replaced with a placeholder), and outbound requests to `api.github.com` should get the real token substituted by the sandbox proxy.
### Actual behavior
The file does not exist at all inside the sandbox — not a sentinel, not the real file, nothing:
```
$ cat ~/.config/gh/hosts.yml
cat: /home/ubuntu/.config/gh/hosts.yml: No such file or directory
$ ls -la ~/.config/gh/
total 4
drwxr-xr-x 2 ubuntu ubuntu 60 ... .
drwx------ 4 ubuntu ubuntu 80 ... ..
-rw------- 1 ubuntu ubuntu 1660 ... config.yml
```
`hosts.yml` doesn't appear in the directory listing at all — not even as a masked entry. As a consequence, `gh auth status` reports "not logged in" and `git ls-remote`/`git push`/`git pull` over HTTPS fail with `could not read Username for 'https://github.com'` inside the sandbox, even though the same commands work fine outside it.
### Verification already done (all pass)
1. **`extract` regex matches**: confirmed via `grep -qE 'oauth_token:[[:space:]]*\S+' ~/.config/gh/hosts.yml` outside the sandbox → matches (twice — `hosts.yml` has `oauth_token:` both nested under `users.` and duplicated at the top level, which is normal for current `gh` versions).
2. **File validity**: `file ~/.config/gh/hosts.yml` → `ASCII text`; `wc -c` → 216 bytes. Well under the documented 8 MiB cap, not a directory, not a glob.
3. **`network.tlsTerminate: {}`** is set, and confirmed active empirically — `HTTPS_PROXY`/`HTTP_PROXY`/`GIT_SSH_COMMAND` inside the sandbox are all wired through the sandbox's TLS-terminating proxy (`localhost:3128`).
4. **`api.github.com`** is present in both `injectHosts` and `network.allowedDomains`.
5. **Config schema** matches the documented example in `/docs/en/sandboxing#mask-credential-files` exactly, plus two optional fields (`maskDuplicates`, `onExtractNoMatch`) valid since v2.1.224 (we're on 2.1.270).
6. **Settings symlink is live and correct**: `~/.claude/settings.json` → `/dotfiles/claude_1/.claude/settings.json`, confirmed via `readlink -f`.
7. **Not a stale-session issue**: reproduced in a session started fresh after the config was last edited.
8. **`/sandbox` → Config tab** shows the base filesystem/network restrictions (`~/.config/gh` correctly shown denied except the explicit `config.yml` allowRead exception) but has no dedicated section reporting `sandbox.credentials` mask status, so it can't confirm/deny whether the mask ran. No warning banner appeared at session start. No Dependencies tab appeared (so the optional Linux seccomp package isn't the issue).
None of the documented fallback-to-`deny` conditions (directory path, glob pattern, file >8 MiB, non-UTF-8) apply, and `onExtractNoMatch: "error"` shouldn't trigger since the regex does match. Per docs, masking is supposed to be enforced independently of the filesystem layer's `denyRead`, so the broad `"denyRead": ["~/"]` shouldn't prevent the mask's sentinel from appearing — but empirically, it looks like it does (or something else silently drops this entry) for this file.
### Suspected cause (unconfirmed)
Possibly specific to this environment: a remote/EC2-hosted session launched via the desktop app's remote client, rather than a local interactive `claude` CLI session. This box has at least one other documented instance of remote/EC2-launched sessions not getting credential-handling behavior that's otherwise expected (AWS credential env-var injection via `awsCredentialExport` doesn't reach the sandboxed process env in this same launch mode, per separate investigation). If file-mode `credentials.files` masking has a similar gap for this launch path, that would explain a silent, total absence rather than a reported misconfiguration.
### Workaround in use
Route `gh`/`git` operations over HTTPS through the unsandboxed-command escalation path instead of relying on the mask.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by reproducing the issue with the shown ~/.claude/settings.json, the ~/.config/gh/hosts.yml mask, and the broad denyRead rule in a fresh remote session. Check the /sandbox Config tab and run the listed cat, gh auth status, and git commands inside and outside the sandbox. Done means the masked sentinel file appears and GitHub authentication works, or the launch-path limitation is diagnosed and reported clearly.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- git, github, linux, python
- Domain
- authentication, cli, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100