You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When an SDK host supplies an Open Plugin through pluginDirectories and its
rules through instructionDirectories, the plugin is discovered successfully,
but reading an applicable plugin instruction triggers an out-of-workspace path
permission request.
The plugin directory is host-provided, session-scoped, and explicitly selected
for that session. Its readable customization content should not require a user
confirmation merely because the host materialized it outside the workspace.
This was originally observed in the VS Code Agent Host:
An embedding host must either show a redundant out-of-workspace confirmation
or implement its own plugin-path exception.
Expected behavior
The runtime/SDK should provide a safe way for a host to make the readable
contents of an explicitly supplied plugin available without path confirmation.
The trust should be:
scoped to the exact plugin roots supplied to that session;
read-only;
applicable to instructions, agents, skills and their supporting assets, hook
definitions, MCP/plugin metadata, and future plugin customization types;
unavailable to writes, shell execution, URL access, or paths outside those
plugin roots;
fail-closed for symlinks that resolve outside a plugin root;
subordinate to managed approval, content exclusion, and sandbox restrictions;
applied consistently on create and resume.
Treating plugin roots as general additionalDirectories would be too broad
because that field grants a standing path capability rather than read-only
access to host-attested plugin content.
Minimal reproduction
Prerequisites
Node.js 20 or newer
GitHub Copilot CLI installed as copilot
GitHub CLI authenticated with gh auth login
Create a clean directory:
mkdir copilot-plugin-permission-repro
cd copilot-plugin-permission-repro
npm init -y
npm install @github/copilot-sdk@1.0.11
Save the following as repro.mjs:
import{CopilotClient}from'@github/copilot-sdk';import{mkdir,mkdtemp,rm,writeFile}from'node:fs/promises';import{isAbsolute,join,relative,resolve}from'node:path';importprocessfrom'node:process';constcliPath=process.env.COPILOT_CLI_PATH;constgitHubToken=process.env.COPILOT_GITHUB_TOKEN;if(!cliPath){thrownewError('COPILOT_CLI_PATH is required');}if(!gitHubToken){thrownewError('COPILOT_GITHUB_TOKEN is required');}// Do not use os.tmpdir(): the runtime may pre-authorize the system temp// directory, which would hide the unauthorized-path behavior under test.constroot=awaitmkdtemp(join(process.cwd(),'.copilot-plugin-permission-'));consthome=join(root,'home');constworkspace=join(root,'workspace');constplugin=join(root,'plugin');construles=join(plugin,'rules');constsample=join(workspace,'sample.ts');constinstruction=join(rules,'typescript.instructions.md');awaitPromise.all([mkdir(home),mkdir(workspace),mkdir(rules,{recursive: true}),]);awaitPromise.all([writeFile(join(plugin,'plugin.json'),JSON.stringify({name: 'plugin-permission-repro',version: '1.0.0',})),writeFile(sample,['export function add(left, right) {','\treturn left + right;','}',].join('\n')),writeFile(instruction,['---','applyTo: "**/*.ts"','description: "Before reviewing TypeScript, acquire this instruction file with the view tool."','---','','When reviewing TypeScript, answer exactly:','PLUGIN_INSTRUCTION_PERMISSION_REPRO',].join('\n')),]);process.env.COPILOT_HOME=home;functionisWithin(path,parent){constvalue=relative(parent,path);returnvalue===''||(!value.startsWith('..')&&!isAbsolute(value));}letresolvePermissionEvent;constpermissionEvent=newPromise(resolveEvent=>{resolvePermissionEvent=resolveEvent;});constclient=newCopilotClient({
cliPath,
gitHubToken,logLevel: 'all',});letsession;lettimeoutHandle;try{awaitclient.start();session=awaitclient.createSession({clientName: 'plugin-permission-repro',workingDirectory: workspace,pluginDirectories: [plugin],instructionDirectories: [rules],enableConfigDiscovery: false,streaming: true,onPermissionRequest: request=>{if(request.kind==='read'&&isWithin(resolve(request.path),workspace)){return{kind: 'approve-once'};}return{kind: 'reject',feedback: 'Permission event captured by reproduction.',};},});session.on('permission.requested',event=>{constrequest=event.data.permissionRequest;if(request.kind!=='read'||resolve(request.path)!==instruction){return;}resolvePermissionEvent({permissionRequest: {kind: request.kind,path: request.path,},promptRequest: event.data.promptRequest,});});voidsession.sendAndWait({prompt: ['Use the view tool to review sample.ts.','Inspect and follow every applicable instruction listed in the session context.','Do not modify files or run shell commands.',].join(' '),}).catch(()=>undefined);constcaptured=awaitPromise.race([permissionEvent,newPromise((_,rejectTimeout)=>{timeoutHandle=setTimeout(()=>rejectTimeout(newError('Timed out waiting for permission event')),90_000,);}),]);if(captured.promptRequest?.kind!=='path'||captured.promptRequest.accessKind!=='read'||!captured.promptRequest.paths.includes(instruction)){thrownewError(`Expected an unauthorized path/read prompt, received: ${JSON.stringify(captured)}`);}console.log(JSON.stringify(captured,undefined,2));}finally{clearTimeout(timeoutHandle);if(session){awaitsession.disconnect().catch(()=>undefined);}awaitclient.stop().catch(()=>undefined);awaitrm(root,{recursive: true,force: true});}
Run it using the installed CLI and a token from GitHub CLI:
The script prints a permission.requested event whose:
permissionRequest.kind is read;
permissionRequest.path is the exact plugin/rules/typescript.instructions.md path;
promptRequest.kind is path;
promptRequest.accessKind is read;
promptRequest.paths contains the same plugin instruction path.
The reproduction rejects the plugin read and does not grant broad filesystem
access.
Reproduction scope
This issue was reproduced through @github/copilot-sdk while driving the local
installed copilot executable.
I also tried copilot --plugin-dir <plugin> directly. Those standalone runs did
not naturally select the plugin instruction. They are not equivalent to the
embedding-host scenario above because the CLI exposes --plugin-dir but no
corresponding command-line option for the host's separate instructionDirectories input.
Root cause found by source inspection
The SDK/runtime contract treats these fields separately:
Only additionalDirectories is added to the session path manager. pluginDirectories and instructionDirectories do not grant read access.
The runtime separately auto-allows its own <copilot_home>/installed-plugins directory. That does not cover an embedding
host's arbitrary plugin cache, such as VS Code Agent Host's <user-data>/agentPlugins/... directory.
The result is that a plugin instruction can be discovered successfully but its
subsequent read is classified as an unauthorized external path.
Requested capability
Please add a typed session-level mechanism for read-only access to
host-attested plugin roots, or otherwise make explicitly supplied plugin
content readable without treating it as a writable workspace directory.
The mechanism should:
accept the exact plugin roots selected by the host;
canonicalize both root and requested path;
reject symlink escapes;
permit reads only;
remain session-scoped and non-persistent;
continue honoring managed approvals, content exclusion, and sandbox policy;
behave consistently for session creation and resume.
Closing as fixed after verification against the current public SDK sources and bundled behavior.
Public evidence
The reported behavior is resolved in the implementation bundled by current SDK main. Underlying implementation details are intentionally omitted from this public issue.
The reported behavior/request is implemented at the current SDK baseline, so this issue is being closed as completed.
Activity
patniko commentedon Sep 8, 2026
Closing as fixed after verification against the current public SDK sources and bundled behavior.
Public evidence
The reported behavior/request is implemented at the current SDK baseline, so this issue is being closed as completed.
Public SDK baseline: github/copilot-sdk
d5c9d06d8c41.