Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/enclave/tool_imports.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ Common settings paths:
|------|--------------------|
| claude | `settings.json` |
| codex | `config.toml` |
| copilot | `settings.json` |
| mistral-vibe | `config.toml` |
| opencode | `opencode.json` |
| pi | `agent/settings.json` |
Expand Down
1 change: 1 addition & 0 deletions docs/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Built-in tool profiles:
|------|-------------|
| `claude` | [Claude Code](https://www.anthropic.com/claude-code) (Anthropic) |
| `codex` | [Codex CLI](https://github.com/openai/codex) (OpenAI) |
| `copilot` | [GitHub Copilot CLI](https://github.com/features/copilot/cli) (GitHub) |
| `mistral-vibe` | [Mistral Vibe CLI](https://github.com/mistralai/mistral-vibe) (opt-in/experimental) |
| `opencode` | [OpenCode](https://opencode.ai/) |
| `pi` | [Pi](https://github.com/earendil-works/pi) |
Expand Down
112 changes: 112 additions & 0 deletions extensions/tools/copilot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Copilot CLI

GitHub Copilot CLI is the command-line coding assistant for GitHub Copilot.

## Configuration

- **Command**: `copilot --disable-builtin-mcps`
- **YOLO flag**: `--yolo` (enabled by default)
- **Config directory**: `~/.copilot`
- **Settings file**: `~/.copilot/settings.json`
- **Skills directory**: `~/.copilot/skills`

The extension also passes through selected project-level Copilot configuration:
`agents/`, `copilot-instructions.md`, `extensions/`, `hooks/`, `instructions/`,
`lsp-config.json`, `mcp-config.json`, `providers.json`, `settings.json`, and
`skills/`.

## Authentication

| Variable | Purpose |
|----------|---------|
| `COPILOT_GITHUB_TOKEN` | GitHub token injected by the network gateway |

The provider declares `~/.copilot/config.json` as its auth file. Enclave uses
its presence to detect an existing auth session and includes it in auth
import/export. See [Authentication & Secrets](../../../docs/auth.md).
Comment on lines +24 to +26

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't mention the OAuth scope problem from the PR discussion: the device/browser login token comes back with repo (read+write) and write:plugin_gateway_connections, way more than Copilot needs (see the finding). Worth telling users here to prefer COPILOT_GITHUB_TOKEN with a fine-grained PAT and flagging the OAuth risk explicitly, since that's the actual guidance that came out of testing this PR.


The gateway sends the declared token as a Bearer authorization header to
`api.github.com` and `*.githubcopilot.com`.

### Recommended: fine-grained PAT via `COPILOT_GITHUB_TOKEN`

Prefer `COPILOT_GITHUB_TOKEN` with a fine-grained personal access token that
only grants the **Copilot Requests** permission. This is the minimum Copilot CLI
needs and the approach recommended by the Copilot CLI maintainers
([github/copilot-cli#953](https://github.com/github/copilot-cli/issues/953)).

### OAuth login (`/login`) grants broad scopes

> **Warning:** The device-code and browser OAuth login (`/login`) issues a token
> with far more scopes than Copilot needs. Observed token scopes:
> `codespace`, `gist`, `read:org`, `read:user`, `repo` (read and write), and
> `write:plugin_gateway_connections`.

A token with `repo` write access can read and modify private repositories, so
anyone who obtains the stored OAuth credentials (under
`~/.local/state/enclave/tools/copilot/`) can act on your GitHub account beyond
Copilot. Use OAuth only when you accept this risk; otherwise use a fine-grained
PAT as described above.

## Memory and Skills

Copilot Memory is **disabled by default** (`"memory": false` in the settings
template). Copilot stores memories on GitHub's servers, not in a local memory
directory. Account-scoped memories can be retrieved by later sessions in other
projects, other Enclave state roots, or Copilot outside Enclave. This can expose
project information across those boundaries and let one project's agent
influence another through shared memories. In YOLO mode, an agent can store
memories without a confirmation prompt.

Enclave's per-project memory isolation applies to local files and cannot isolate
this server-side memory. The extension therefore does not declare a native
memory directory or memory-disable arguments; `--no-memory` is not a control for
Copilot's server-side memory.

If you accept this cross-project sharing, run `/memory on` inside a Copilot
session to enable it, or `/memory off` to disable it again. When an Enclave config
override or patch is active, the changed setting carries forward in that
project's config store. See [Tool Config Patches](../../../docs/configuration.md#tool-config-patches)
for settings persistence and precedence.

For a persistent opt-in, add `"memory": true` to a
[settings patch](../../../docs/configuration.md#tool-config-patches), for example
`~/.config/enclave/patches/copilot/settings.json` on Linux:

```json
{
"memory": true
}
```

This global patch enables memory for all projects unless overridden by a
project-specific setting. It does not make the stored memories project-scoped.

The extension's `enclave-help` skill is installed alongside managed skills in
`~/.copilot/skills`.

## Network Access

The Copilot service domains are `api.github.com` and `*.githubcopilot.com`.
The DNS allowlist also includes the common GitHub, npm, PyPI, Go, CDN, and
TLS/OCSP infrastructure fragments used by Enclave.

## Settings

The default settings template sets `remoteExport` and `autoUpdate` to `false`,
`includeCoAuthoredBy`, `storeTokenPlaintext`, and `memory` to `false`,
`logLevel` to `default`, and `model` to `auto`. See [Memory and Skills](#memory-and-skills)
before enabling memory.

## Files

| File | Purpose |
|------|---------|
| `spec.yaml` | Extension manifest (launch behavior, settings, credentials, network, and provider auth) |
| `install.sh` | Installs `@github/copilot` and its `copilot` command |
| `check-update.sh` | Returns the latest npm package version for update probes |
| `gateway-allowlist.conf` | DNS allowlist for network isolation |
| `templates/settings.json` | Default Copilot settings template |
| `entrypoint.d/setup.sh` | Creates `~/.copilot` and sets `COPILOT_HOME` |
| `skills/enclave-help/SKILL.md` | Bundled skill for answering questions from Enclave's documentation |
| `go/handler.go` | Registers the Copilot tool handler |
11 changes: 11 additions & 0 deletions extensions/tools/copilot/check-update.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Copyright (C) 2026 Dirk Fauth and others.
#
# This program and the accompanying materials are made available under the
# terms of the MIT License, which is available in the project root.
#
# SPDX-License-Identifier: MIT

set -euo pipefail

npm view @github/copilot@latest version
26 changes: 26 additions & 0 deletions extensions/tools/copilot/entrypoint.d/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/bash
# Copyright (C) 2026 Dirk Fauth and others.
#
# This program and the accompanying materials are made available under the
# terms of the MIT License, which is available in the project root.
#
# SPDX-License-Identifier: MIT
# shellcheck shell=bash

# Copilot extension setup
export COPILOT_HOME="$HOME/.copilot"
mkdir -p "$COPILOT_HOME"

# In yolo mode, remember the project workspace as trusted for future sessions.
if [ "${ENCLAVE_YOLO:-}" = "1" ] && [ -n "${PROJECT_DIR:-}" ] && command -v jq >/dev/null 2>&1; then
_config="$COPILOT_HOME/config.json"
if [ -f "$_config" ]; then
# Copilot rewrites config.json as JSONC (leading // comments); strip them for jq.
_updated="$(grep -v '^[[:space:]]*//' "$_config" | jq --arg dir "$PROJECT_DIR" \
'.trustedFolders = (((.trustedFolders // []) + [$dir]) | unique)' \
2>/dev/null)" && printf '%s\n' "$_updated" > "$_config"
else
jq -n --arg dir "$PROJECT_DIR" '{trustedFolders:[$dir]}' > "$_config"
fi
unset _config _updated
fi
19 changes: 19 additions & 0 deletions extensions/tools/copilot/gateway-allowlist.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright (C) 2026 Dirk Fauth and others.
#
# This program and the accompanying materials are made available under the
# terms of the MIT License, which is available in the project root.
#
# SPDX-License-Identifier: MIT

# Enclave DNS Allowlist Configuration - Copilot
no-resolv
no-poll
address=/#/

# Common Infrastructure
conf-file=/etc/dnsmasq.allowlists/fragments/github.conf
conf-file=/etc/dnsmasq.allowlists/fragments/npm.conf
conf-file=/etc/dnsmasq.allowlists/fragments/pypi.conf
conf-file=/etc/dnsmasq.allowlists/fragments/go.conf
conf-file=/etc/dnsmasq.allowlists/fragments/cdn.conf
conf-file=/etc/dnsmasq.allowlists/fragments/tls.conf
18 changes: 18 additions & 0 deletions extensions/tools/copilot/go/handler.go

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only embeds BaseHandler, which tools.Resolve already returns for unregistered tools (registry). Drop go/ like mistral-vibe and pi. As is, cmd/enclave/tool_imports.go wasn't regenerated, so the package isn't linked anyway.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cmd/enclave/tool_imports.go was regenerated. it is part of this PR or what is missing here?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's ok. Nothing is missing, tool_imports.go was regenerated and looks good. The bot's point was that the handler currently does nothing, since the registry falls back to the base handler anyway. Please keep the file as it is. I can look into that as a follow-up issue after the PR is merged.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (C) 2026 Dirk Fauth and others.
//
// This program and the accompanying materials are made available under the
// terms of the MIT License, which is available in the project root.
//
// SPDX-License-Identifier: MIT

package copilot

import "enclave/internal/tools"

func init() {
tools.RegisterHandler("copilot", Handler{})
}

type Handler struct {
tools.BaseHandler
}
11 changes: 11 additions & 0 deletions extensions/tools/copilot/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash
# Copyright (C) 2026 Dirk Fauth and others.
#
# This program and the accompanying materials are made available under the
# terms of the MIT License, which is available in the project root.
#
# SPDX-License-Identifier: MIT

set -e

enclave-install-npm-tool @github/copilot@latest copilot Copilot
44 changes: 44 additions & 0 deletions extensions/tools/copilot/skills/enclave-help/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
name: enclave-help
description: Answer questions about Enclave by reading the bundled documentation
---

# Enclave Help

Answer the user's question about Enclave by reading the relevant documentation
files bundled at `/usr/share/doc/enclave/`.

## Important context

You are running inside an Enclave container. Host-global configuration
(e.g. `~/.config/enclave/config.json`, network policies, secrets) lives on the
host filesystem and is not directly editable from inside the container.
When a question involves host-side configuration changes, explain what the
user needs to do on the host or via the `enclave` CLI rather than
attempting in-container file edits.

## Documentation index

| Path | Topic |
|------|-------|
| `docs/README.md` | Documentation index |
| `docs/ARCHITECTURE.md` | Detailed architecture with diagrams |
| `docs/cli-reference.md` | CLI flags and options |
| `docs/configuration.md` | Config files and settings |
| `docs/networking.md` | Network isolation and gateway |
| `docs/auth.md` | Authentication and API keys |
| `docs/persistence.md` | Persistent stores and session data |
| `docs/tools.md` | Supported agent tools |
| `docs/extensions/` | Extension system (adding tools, features) |
| `docs/runtime/` | Runtime behavior (stores, auth sync, network flow) |
| `docs/security/` | Security model and host hardening |
| `extensions/tools/<tool>/README.md` | Per-tool documentation |
| `extensions/features/<feature>/README.md` | Per-feature documentation |

All paths are relative to `/usr/share/doc/enclave/`.

## Steps

1. Read the doc(s) most relevant to the user's question.
2. Synthesize a clear, concise answer from what you find.
3. If the docs don't cover the topic, say so explicitly.
71 changes: 71 additions & 0 deletions extensions/tools/copilot/spec.yaml

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing from the tool checklist: extensions/tools/copilot/README.md (required per AGENTS.md), a row in docs/tools.md, and the golden snapshots. go test ./internal/config/ currently fails with read golden tool-copilot ... no such file or directory, regenerate with -update-golden.

Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright (C) 2026 Dirk Fauth and others.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things from the PR discussion are still unresolved: the OAuth token comes back overly broad in scope, and the browser OAuth flow can't be pinned to a fixed callback port (no oauthPorts entry here, unlike codex). Given that, should this ship with defaultIncluded: false for now, same as mistral-vibe (

schemaVersion: "1"
kind: sandbox
name: mistral-vibe
displayName: Mistral Vibe
description: Mistral Vibe CLI coding assistant
defaultIncluded: false
), until those are sorted out?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xai To be honest I am not sure what to do here. Any hints or suggestions?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need defaultIncluded: false. Keep this as it is.

#
# This program and the accompanying materials are made available under the
# terms of the MIT License, which is available in the project root.
#
# SPDX-License-Identifier: MIT

schemaVersion: "1"
kind: sandbox
name: copilot
displayName: Copilot CLI
description: Copilot CLI

sandbox:
entrypoint: { run: [copilot, --disable-builtin-mcps, --disable-client-telemetry] }
configDir: .copilot

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a user has a config override or patch in their enclave config for copilot, Enclave currently rebuilds the config directory on every start and keeps only declared state. Without this line, Copilot's conversation history and session database are deleted, so --continue and --resume find nothing. Verified in real sessions, including sidebar-sessions-state/, which keeps the sidebar's list of recent sessions. Please add after the configDir line:

  statePaths: [config.json, session-state/, sidebar-sessions-state/, command-history-state*, session-store.db*, permissions-config.json, mcp-oauth-config/, mcp-secrets/, installed-plugins/, plugin-data/]

statePaths: [
config.json,
session-state/,
sidebar-sessions-state/,
command-history-state*,
session-store.db*,
permissions-config.json,
mcp-oauth-config/,
mcp-secrets/,
installed-plugins/,
plugin-data/
]
qemuMinMemoryMiB: 4096
skillsDir: .copilot/skills
settingsFile: copilot-settings.json
settingsTarget: .copilot/settings.json
yoloFlag: --yolo
yoloEnabled: true
continueArgs: [--continue]
resumeArgs: [--resume]
passthroughPaths: [
agents/,
copilot-instructions.md,
extensions/,
hooks/,
instructions/,
lsp-config.json,
mcp-config.json,
providers.json,
settings.json,
skills/]
Comment on lines +37 to +47

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: trailing whitespace on each of these lines, and most new files lack a final newline. A block list like pi/opencode use reads better here.


credentials:
sources:
copilot-github-token:
env: [COPILOT_GITHUB_TOKEN]
placeholder:
prefix: github_pat_
random: { encoding: alphanumeric, length: 82 }

network:
serviceDomains:
api.github.com: copilot-github-token
"*.githubcopilot.com": copilot-github-token
serviceAuth:
copilot-github-token: { headerName: Authorization, valueFormat: "Bearer %s" }

providers:
- name: github
credentials: [copilot-github-token]
authFiles: [config.json]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is config.json auth only? If Copilot also keeps state there (trusted folders, model, first-launch flags), file_exists passes after the first start without any login, and all of that gets shared across projects via the auth store. A json_pointer check on the token key would be accurate, and this also matters for the folder trust you want to add.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xai Not sure what to do here. any hints or suggestions?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not auth only, this is the problem I mentioned in the above inline comment. In copilot, config.json holds both configuration data and login credentials. It is even worse, because it also stores project-specific stuff there, such as folder trust, so it is basically leaks trust information between projects.

If we decide to break /login as I suggested on line 15, we can remove the authFiles stuff here completely, i.e., the provider block becomes:

providers:
  - name: github
    credentials: [copilot-github-token]

authSession:
mode: any
checks:
- { file: config.json, type: file_exists }
9 changes: 9 additions & 0 deletions extensions/tools/copilot/templates/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"remoteExport": false,
"autoUpdate": false,
"logLevel": "default",
"includeCoAuthoredBy": false,
"storeTokenPlaintext": false,
"model": "auto",
"memory": false
}
Comment on lines +1 to +9

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot Memory stores memories/facts on GitHub's servers and serves them to every later session of that account: other projects, other enclave state roots, copilot outside Enclave, anything. In my test sessions, a prompt asking copilot to remember a word made the agent store it with user scope under --yolo without any notice, and every later project's session fetched it at start. That is also a nice way for agents in different projects and sessions to collaborate with each other by using the memory feature as a message board. I think the huggingface incident has shown us what can happen with this. So I would argue to disable copilot's memory feature by default.

We could use

{
"remoteExport": false,
"autoUpdate": false,
"logLevel": "default",
"includeCoAuthoredBy": false,
"storeTokenPlaintext": false,
"model": "auto",
"memory": false
}

Here. Users who want the feature nevertheless can run /memory on in a session, and the setting then persists in that project's config store.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added that setting. As users are able to change this, it is a better default for enclave to disable memory. I extended the README to cover this aspect.

Loading
Loading