fix: don't mask Copilot CLI's ~/.copilot/config.json#6374
Conversation
There was a problem hiding this comment.
Pull request overview
Adds tool-writable credential policy support to prevent Copilot CLI startup failures in compose mode.
Changes:
- Adds and validates
toolWritable. - Excludes Copilot config from read-only compose overlays.
- Documents and tests compose/sbx behavior.
Show a summary per file
| File | Description |
|---|---|
src/config/sandbox-mount-policy.json |
Marks Copilot config tool-writable. |
src/config/mount-policy.ts |
Parses and applies the new flag. |
src/config/mount-policy.test.ts |
Tests policy filtering. |
src/services/agent-volumes/credential-hiding.test.ts |
Verifies compose omission. |
docs/mount-policy.md |
Documents tool-writable files. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Medium
| const files: string[] = []; | ||
| for (const entry of mountPolicy.credentials) { | ||
| if (entry.type === 'file') { | ||
| if (entry.toolWritable) continue; |
There was a problem hiding this comment.
Good catch — fixed in b5fdf88. Instead of skipping the mask, compose now overlays a sandbox-owned empty writable stub file over each toolWritable credential (buildToolWritableCredentialOverlays). The stub is created and chowned to the agent UID/GID under the chroot-home placeholder in prepareChrootHomeMounts, then bind-mounted :rw at both the real $HOME path and the /host$HOME path. The Copilot CLI reads/writes the throwaway stub, while the host's real ~/.copilot/config.json never reaches the agent. sbx is unchanged (still moves the path aside).
There was a problem hiding this comment.
Reworked this entirely — you were right that the writable-stub approach was wrong, but it turned out the whole entry was the mistake.
~/.copilot/config.json is not a credential file. It holds only experiment-assignment cache, first-launch timestamps, tips-shown flags, and installed-plugin metadata — no token (verified against a real config). The Copilot CLI stores its actual auth elsewhere (sqlite stores), and ~/.copilot is already bind-mounted rw as a tool subdir, so masking config.json protected nothing in the first place.
It also could never work: the CLI rewrites config.json via temp-file + atomic rename (the inode changes on write), and you cannot rename over a bind-mounted file — plus the /dev/null mask was read-only. That, not a missing fresh-config, is what crashed smoke-copilot.
So the fix is now just removing .copilot/config.json from the credentials deny list (the one bad line from #6339). No stub, no new flag. History collapsed to a single commit.
|
| Metric | Base | PR | Delta |
|---|---|---|---|
| Lines | 98.64% | 98.65% | 📈 +0.01% |
| Statements | 98.46% | 98.46% | ➡️ +0.00% |
| Functions | 99.24% | 99.24% | ➡️ +0.00% |
| Branches | 94.55% | 94.51% | 📉 -0.04% |
📁 Per-file Coverage Changes (2 files)
| File | Lines (Before → After) | Statements (Before → After) |
|---|---|---|
src/config/mount-policy.ts |
80.2% → 79.6% (-0.59%) | 74.3% → 74.3% (+0.05%) |
src/log-directory-setup.ts |
96.2% → 100.0% (+3.78%) | 96.3% → 100.0% (+3.71%) |
Coverage comparison generated by scripts/ci/compare-coverage.ts
Address review feedback on #6374. Rather than skipping the credential mask for toolWritable entries (which exposed the host's real ~/.copilot/config.json via the rw ~/.copilot bind mount), compose now overlays a sandbox-owned empty writable stub file. The Copilot CLI reads and writes the throwaway stub while the host's real file — which may hold a persisted auth token — never reaches the agent. Stubs are created (owned by the agent UID/GID) under the chroot-home placeholder in prepareChrootHomeMounts and removed with it on cleanup. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4bdbe67a-cb99-41a2-944b-49cfd949360e
|
✅ Copilot review passed with no inline comments. @lpcox Add the |
|
| Metric | Base | PR | Delta |
|---|---|---|---|
| Lines | 98.64% | 98.64% | ➡️ +0.00% |
| Statements | 98.46% | 98.46% | ➡️ +0.00% |
| Functions | 99.24% | 99.25% | 📈 +0.01% |
| Branches | 94.55% | 94.47% | 📉 -0.08% |
📁 Per-file Coverage Changes (3 files)
| File | Lines (Before → After) | Statements (Before → After) |
|---|---|---|
src/chroot-home-setup.ts |
100.0% → 98.5% (-1.54%) | 100.0% → 98.5% (-1.54%) |
src/config/mount-policy.ts |
80.2% → 80.4% (+0.17%) | 74.3% → 75.2% (+0.93%) |
src/log-directory-setup.ts |
96.2% → 100.0% (+3.78%) | 96.3% → 100.0% (+3.71%) |
Coverage comparison generated by scripts/ci/compare-coverage.ts
|
@copilot fix failed codeql check |
Fixed in the latest commit. The two CodeQL alerts on
CodeQL re-scan returned 0 alerts. |
|
| Metric | Base | PR | Delta |
|---|---|---|---|
| Lines | 98.64% | 98.62% | 📉 -0.02% |
| Statements | 98.46% | 98.44% | 📉 -0.02% |
| Functions | 99.24% | 99.25% | 📈 +0.01% |
| Branches | 94.55% | 94.44% | 📉 -0.11% |
📁 Per-file Coverage Changes (3 files)
| File | Lines (Before → After) | Statements (Before → After) |
|---|---|---|
src/chroot-home-setup.ts |
100.0% → 97.0% (-3.04%) | 100.0% → 97.0% (-3.04%) |
src/config/mount-policy.ts |
80.2% → 80.4% (+0.17%) | 74.3% → 75.2% (+0.93%) |
src/log-directory-setup.ts |
96.2% → 100.0% (+3.78%) | 96.3% → 100.0% (+3.71%) |
Coverage comparison generated by scripts/ci/compare-coverage.ts
|
❌ Smoke Copilot BYOK AOAI (api-key) reports failed. AOAI BYOK (api-key) mode investigation needed... |
|
🔌 Smoke Services — Service connectivity failed |
|
❌ Smoke Copilot BYOK AOAI (Entra) reports failed. AOAI BYOK (Entra) mode investigation needed... |
|
Chroot tests failed Smoke Chroot failed - See logs for details. |
|
✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟 |
|
✅ Smoke Claude passed |
|
❌ Smoke Copilot BYOK reports failed. BYOK mode investigation needed... |
|
✅ Smoke Gemini completed. All facets verified. 💎 |
|
❌ Contribution Check failed. Please review the logs for details. |
|
📰 DEVELOPING STORY: Smoke Copilot reports failed. Our correspondents are investigating the incident... |
|
Build Test Failed Build Test Suite - See logs for details |
|
📡 Smoke OTel Tracing reports failed. OTel tracing regression detected. |
|
🔑 Smoke Copilot PAT reports failed. PAT auth path may have issues... |
|
🛡️ Smoke Copilot Network Isolation reports failed while checking network isolation. Investigate the egress model. |
Smoke Test: Copilot PAT Auth
Overall: PASS Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
Smoke Test Results
Overall: CC @lpcox Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
Smoke Test: Services Connectivity
Overall: FAIL — Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
|
Smoke test: network-isolation egress enforcement
✅ Allowed domain ( Overall: PASS — @lpcox Warning Firewall blocked 2 domainsThe following domains were blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"
- "example.com"See Network Configuration for more information.
|
Smoke Test: Copilot BYOK (Direct) Mode✅ GitHub MCP connectivity Mode: Direct BYOK (COPILOT_PROVIDER_API_KEY) via api-proxy → api.githubcopilot.com Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
🔥 AWF API Proxy OTEL Smoke Test Results
All 5 scenarios pass. OTEL tracing integration is functional with graceful degradation. Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
Chroot Version Comparison
Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
Smoke Test\n- Reviewed PR: fix(smoke-copilot): revert agent job to read-only perms to unblock CI\n- Reviewed PR: Smoke Copilot: grant PR write permissions required by safe outputs\n- GitHub PR read: ✅\n- Discussion comment: ✅\n- File write/read: ✅\n- Build: ✅\n- Playwright title check: ❌\n- Overall status: FAILWarning Firewall blocked 2 domainsThe following domains were blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"
- "registry.npmjs.org"See Network Configuration for more information.
|
|
Smoke test result: PASS/FAIL analysis in progress. Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "localhost"See Network Configuration for more information.
|
|
Smoke Test direct BYOK (AzureOpenAI, o4-mini-aw):
Running in direct BYOK mode via api-proxy → AzureOpenAI (Foundry, o4-mini-aw) Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
|
Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
🧪 Smoke Test: Docker Sbx — PASS
Overall: PASS 🟢
|
🏗️ Build Test Suite Results
Overall: 8/8 ecosystems passed — ✅ PASS Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
Problem
smoke-copilotwas failing consistently: the Copilot CLI crashed silently at startup (exit 1, zero stdout/stderr, ~0.5s).Root cause: the centralize-mount-policy change (#6339) added
.copilot/config.jsonto the credential deny list with the reason "Copilot CLI persisted auth token". That reason is incorrect:~/.copilot/config.jsoncontains only experiment-assignment cache, first-launch timestamps, tips-shown flags, and installed-plugin metadata — no token. The Copilot CLI's real credentials live elsewhere (its sqlite stores), and~/.copilotis already bind-mountedrwas a tool subdir, so maskingconfig.jsonprotected nothing./dev/nulloverlay. The Copilot CLI reads and rewritesconfig.jsonat startup via temp-file + atomic rename (verified: the inode changes on write). A rename can't target a bind-mounted file, and the mask is read-only regardless — so the CLI crashed. sbx was unaffected because it moves the file aside, letting the CLI create a fresh one.Fix
Remove
.copilot/config.jsonfrom the credentials deny list — reverting the one problematic line from #6339. The CLI now reads/writes its config normally (pre-#6339 behavior).This supersedes the earlier
toolWritable/ writable-stub approach on this branch: that added machinery couldn't work anyway (you can't rename over a bind-mounted file), and it's unnecessary once we recognizeconfig.jsonisn't a credential file. History was collapsed to a single commit.Also updated the sbx/compose tests, the
sbx-manager.tscomment, anddocs/sbx-integration.mdthat usedconfig.jsonas their example credential path.Validation
npm run build✅npm run lint: 0 errors ✅