Skip to content

Commit ca0e7f7

Browse files
feat!: default-deny permissions across all SDK languages
Previously, the SDKs only set requestPermission=true in the JSON-RPC session.create/session.resume calls when an onPermissionRequest handler was provided. When no handler was registered, requestPermission was omitted/false, allowing the CLI to handle permissions itself (permissive default). This change makes requestPermission always true so that all permission requests are routed through the SDK. The SDK's existing deny-by-default behavior (returning 'denied-no-approval-rule-and-could-not-request-from-user' when no handler is registered) now takes effect for all sessions. BREAKING CHANGE: Apps that do not provide an onPermissionRequest handler will now have all privileged tool operations (file writes, shell commands, URL fetches, MCP calls) denied by default. Register a handler to approve operations: Node.js: onPermissionRequest: async (request) => ({ kind: 'approved' }) Python: 'on_permission_request': lambda req, inv: {'kind': 'approved'} Go: OnPermissionRequest: func(r PermissionRequest, i PermissionInvocation) (PermissionRequestResult, error) { return PermissionRequestResult{Kind: 'approved'}, nil } .NET: OnPermissionRequest = (req, inv) => Task.FromResult(new PermissionRequestResult { Kind = 'approved' }) Changes: - nodejs/src/client.ts: always send requestPermission:true - python/copilot/client.py: always set requestPermission=True - go/client.go: always set RequestPermission=true - dotnet/src/Client.cs: always pass true for RequestPermission - All samples updated to include onPermissionRequest approve-all handler - All language READMEs updated with Permission Requests documentation section - docs/compatibility.md: fix incorrect PermissionRequestResult format and add default-deny description - go/types.go: update OnPermissionRequest doc comment Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8a9f992 commit ca0e7f7

14 files changed

Lines changed: 201 additions & 27 deletions

File tree

docs/compatibility.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,19 +124,21 @@ The `--share` option is not available via SDK. Workarounds:
124124

125125
### Permission Control
126126

127+
The SDK uses a **deny-by-default** permission model. All permission requests (file writes, shell commands, URL fetches, etc.) are denied unless your app provides an `onPermissionRequest` handler.
128+
127129
Instead of `--allow-all-paths` or `--yolo`, use the permission handler:
128130

129131
```typescript
130132
const session = await client.createSession({
131133
onPermissionRequest: async (request) => {
132134
// Auto-approve everything (equivalent to --yolo)
133-
return { approved: true };
135+
return { kind: "approved" };
134136

135137
// Or implement custom logic
136-
if (request.kind === "shell") {
137-
return { approved: request.command.startsWith("git") };
138-
}
139-
return { approved: true };
138+
// if (request.kind === "shell") {
139+
// return { kind: "denied-interactively-by-user" };
140+
// }
141+
// return { kind: "approved" };
140142
},
141143
});
142144
```

dotnet/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,43 @@ var session = await client.CreateSessionAsync(new SessionConfig
495495
});
496496
```
497497

498+
## Permission Requests
499+
500+
The SDK uses a **deny-by-default** permission model. When the Copilot agent needs to perform privileged operations (file writes, shell commands, URL fetches, etc.), it sends a permission request to the SDK. If no `OnPermissionRequest` handler is registered, all such requests are **automatically denied**.
501+
502+
To allow operations, provide an `OnPermissionRequest` handler when creating a session:
503+
504+
```csharp
505+
var session = await client.CreateSessionAsync(new SessionConfig
506+
{
507+
OnPermissionRequest = async (request, invocation) =>
508+
{
509+
// request.Kind - The type of operation: "shell", "write", "read", "url", or "mcp"
510+
511+
// Approve everything (equivalent to --yolo mode in the CLI)
512+
return new PermissionRequestResult { Kind = "approved" };
513+
514+
// Or implement fine-grained policy:
515+
// if (request.Kind == "shell")
516+
// return new PermissionRequestResult { Kind = "denied-interactively-by-user" };
517+
// return new PermissionRequestResult { Kind = "approved" };
518+
}
519+
});
520+
```
521+
522+
**Permission request kinds:**
523+
- `"shell"` — Execute a shell command
524+
- `"write"` — Write to a file
525+
- `"read"` — Read a file
526+
- `"url"` — Fetch a URL
527+
- `"mcp"` — Call an MCP server tool
528+
529+
**Permission result kinds:**
530+
- `"approved"` — Allow the operation
531+
- `"denied-interactively-by-user"` — User explicitly denied
532+
- `"denied-by-rules"` — Denied by policy rules
533+
- `"denied-no-approval-rule-and-could-not-request-from-user"` — Default deny (no handler)
534+
498535
## User Input Requests
499536

500537
Enable the agent to ask questions to the user using the `ask_user` tool by providing an `OnUserInputRequest` handler:

dotnet/samples/Chat.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
using GitHub.Copilot.SDK;
22

33
await using var client = new CopilotClient();
4-
await using var session = await client.CreateSessionAsync();
4+
await using var session = await client.CreateSessionAsync(new SessionConfig
5+
{
6+
// Permission requests are denied by default. Provide a handler to approve operations.
7+
OnPermissionRequest = (request, invocation) =>
8+
{
9+
// Approve all permission requests. Customize this to implement your own policy.
10+
return Task.FromResult(new PermissionRequestResult { Kind = "approved" });
11+
}
12+
});
513

614
using var _ = session.On(evt =>
715
{

dotnet/src/Client.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig? config = nul
377377
config?.AvailableTools,
378378
config?.ExcludedTools,
379379
config?.Provider,
380-
config?.OnPermissionRequest != null ? true : null,
380+
(bool?)true,
381381
config?.OnUserInputRequest != null ? true : null,
382382
hasHooks ? true : null,
383383
config?.WorkingDirectory,
@@ -461,7 +461,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
461461
config?.AvailableTools,
462462
config?.ExcludedTools,
463463
config?.Provider,
464-
config?.OnPermissionRequest != null ? true : null,
464+
(bool?)true,
465465
config?.OnUserInputRequest != null ? true : null,
466466
hasHooks ? true : null,
467467
config?.WorkingDirectory,

go/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,42 @@ session, err := client.CreateSession(context.Background(), &copilot.SessionConfi
445445
> - For Azure OpenAI endpoints (`*.openai.azure.com`), you **must** use `Type: "azure"`, not `Type: "openai"`.
446446
> - The `BaseURL` should be just the host (e.g., `https://my-resource.openai.azure.com`). Do **not** include `/openai/v1` in the URL - the SDK handles path construction automatically.
447447
448+
## Permission Requests
449+
450+
The SDK uses a **deny-by-default** permission model. When the Copilot agent needs to perform privileged operations (file writes, shell commands, URL fetches, etc.), it sends a permission request to the SDK. If no `OnPermissionRequest` handler is registered, all such requests are **automatically denied**.
451+
452+
To allow operations, provide an `OnPermissionRequest` handler when creating a session:
453+
454+
```go
455+
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
456+
OnPermissionRequest: func(request copilot.PermissionRequest, invocation copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) {
457+
// request.Kind - The type of operation: "shell", "write", "read", "url", or "mcp"
458+
459+
// Approve everything (equivalent to --yolo mode in the CLI)
460+
return copilot.PermissionRequestResult{Kind: "approved"}, nil
461+
462+
// Or implement fine-grained policy:
463+
// if request.Kind == "shell" {
464+
// return copilot.PermissionRequestResult{Kind: "denied-interactively-by-user"}, nil
465+
// }
466+
// return copilot.PermissionRequestResult{Kind: "approved"}, nil
467+
},
468+
})
469+
```
470+
471+
**Permission request kinds:**
472+
- `"shell"` — Execute a shell command
473+
- `"write"` — Write to a file
474+
- `"read"` — Read a file
475+
- `"url"` — Fetch a URL
476+
- `"mcp"` — Call an MCP server tool
477+
478+
**Permission result kinds:**
479+
- `"approved"` — Allow the operation
480+
- `"denied-interactively-by-user"` — User explicitly denied
481+
- `"denied-by-rules"` — Denied by policy rules
482+
- `"denied-no-approval-rule-and-could-not-request-from-user"` — Default deny (no handler)
483+
448484
## User Input Requests
449485

450486
Enable the agent to ask questions to the user using the `ask_user` tool by providing an `OnUserInputRequest` handler:

go/client.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -473,9 +473,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
473473
if config.Streaming {
474474
req.Streaming = Bool(true)
475475
}
476-
if config.OnPermissionRequest != nil {
477-
req.RequestPermission = Bool(true)
478-
}
476+
req.RequestPermission = Bool(true)
479477
if config.OnUserInputRequest != nil {
480478
req.RequestUserInput = Bool(true)
481479
}
@@ -562,9 +560,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
562560
if config.Streaming {
563561
req.Streaming = Bool(true)
564562
}
565-
if config.OnPermissionRequest != nil {
566-
req.RequestPermission = Bool(true)
567-
}
563+
req.RequestPermission = Bool(true)
568564
if config.OnUserInputRequest != nil {
569565
req.RequestUserInput = Bool(true)
570566
}

go/samples/chat.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,14 @@ func main() {
2323
}
2424
defer client.Stop()
2525

26-
session, err := client.CreateSession(ctx, nil)
26+
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
27+
CLIPath: cliPath,
28+
// Permission requests are denied by default. Provide a handler to approve operations.
29+
OnPermissionRequest: func(_ copilot.PermissionRequest, _ copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) {
30+
// Approve all permission requests. Customize this to implement your own policy.
31+
return copilot.PermissionRequestResult{Kind: "approved"}, nil
32+
},
33+
})
2734
if err != nil {
2835
panic(err)
2936
}

go/types.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,9 @@ type SessionConfig struct {
349349
// ExcludedTools is a list of tool names to disable. All other tools remain available.
350350
// Ignored if AvailableTools is specified.
351351
ExcludedTools []string
352-
// OnPermissionRequest is a handler for permission requests from the server
352+
// OnPermissionRequest is a handler for permission requests from the server.
353+
// If nil, all permission requests are denied by default.
354+
// Provide a handler to approve operations (file writes, shell commands, URL fetches, etc.).
353355
OnPermissionRequest PermissionHandler
354356
// OnUserInputRequest is a handler for user input requests from the agent (enables ask_user tool)
355357
OnUserInputRequest UserInputHandler
@@ -426,7 +428,9 @@ type ResumeSessionConfig struct {
426428
// ReasoningEffort level for models that support it.
427429
// Valid values: "low", "medium", "high", "xhigh"
428430
ReasoningEffort string
429-
// OnPermissionRequest is a handler for permission requests from the server
431+
// OnPermissionRequest is a handler for permission requests from the server.
432+
// If nil, all permission requests are denied by default.
433+
// Provide a handler to approve operations (file writes, shell commands, URL fetches, etc.).
430434
OnPermissionRequest PermissionHandler
431435
// OnUserInputRequest is a handler for user input requests from the agent (enables ask_user tool)
432436
OnUserInputRequest UserInputHandler

nodejs/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,42 @@ const session = await client.createSession({
565565
> - For Azure OpenAI endpoints (`*.openai.azure.com`), you **must** use `type: "azure"`, not `type: "openai"`.
566566
> - The `baseUrl` should be just the host (e.g., `https://my-resource.openai.azure.com`). Do **not** include `/openai/v1` in the URL - the SDK handles path construction automatically.
567567
568+
## Permission Requests
569+
570+
The SDK uses a **deny-by-default** permission model. When the Copilot agent needs to perform privileged operations (file writes, shell commands, URL fetches, etc.), it sends a permission request to the SDK. If no `onPermissionRequest` handler is registered, all such requests are **automatically denied**.
571+
572+
To allow operations, provide an `onPermissionRequest` handler when creating a session:
573+
574+
```typescript
575+
const session = await client.createSession({
576+
onPermissionRequest: async (request, invocation) => {
577+
// request.kind - The type of operation: "shell" | "write" | "read" | "url" | "mcp"
578+
579+
// Approve everything (equivalent to --yolo mode in the CLI)
580+
return { kind: "approved" };
581+
582+
// Or implement fine-grained policy:
583+
// if (request.kind === "shell") {
584+
// return { kind: "denied-interactively-by-user" };
585+
// }
586+
// return { kind: "approved" };
587+
},
588+
});
589+
```
590+
591+
**Permission request kinds:**
592+
- `"shell"` — Execute a shell command
593+
- `"write"` — Write to a file
594+
- `"read"` — Read a file
595+
- `"url"` — Fetch a URL
596+
- `"mcp"` — Call an MCP server tool
597+
598+
**Permission result kinds:**
599+
- `"approved"` — Allow the operation
600+
- `"denied-interactively-by-user"` — User explicitly denied
601+
- `"denied-by-rules"` — Denied by policy rules
602+
- `"denied-no-approval-rule-and-could-not-request-from-user"` — Default deny (no handler)
603+
568604
## User Input Requests
569605

570606
Enable the agent to ask questions to the user using the `ask_user` tool by providing an `onUserInputRequest` handler:

nodejs/samples/chat.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import { CopilotClient, type SessionEvent } from "@github/copilot-sdk";
33

44
async function main() {
55
const client = new CopilotClient();
6-
const session = await client.createSession();
6+
const session = await client.createSession({
7+
// Permission requests are denied by default. Provide a handler to approve operations.
8+
onPermissionRequest: async (_request) => {
9+
// Approve all permission requests. Customize this to implement your own policy.
10+
return { kind: "approved" };
11+
},
12+
});
713

814
session.on((event: SessionEvent) => {
915
let output: string | null = null;

0 commit comments

Comments
 (0)