Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
ca0e7f7
feat!: default-deny permissions across all SDK languages
SteveSandersonMS Feb 18, 2026
07bdcd1
fix: add onPermissionRequest handlers to E2E tests for default-deny b…
SteveSandersonMS Feb 18, 2026
7ba9d36
fix: add OnPermissionRequest handlers to Go hooks tests
SteveSandersonMS Feb 18, 2026
64256b9
refactor: add PermissionHandlers.approveAll helper, remove verbose RE…
SteveSandersonMS Feb 18, 2026
a7bee9d
Rename PermissionHandlers to PermissionHandler across all SDK languages
SteveSandersonMS Feb 18, 2026
ae9a038
Fix ruff import sort in session.py
SteveSandersonMS Feb 18, 2026
1135709
Node.js: export approveAll directly instead of PermissionHandler name…
SteveSandersonMS Feb 18, 2026
2ade8c3
go fmt
SteveSandersonMS Feb 18, 2026
f124485
debug: log all events in default-deny test to inspect event structure
SteveSandersonMS Feb 18, 2026
9941b3a
Fix default-deny e2e tests: check Permission denied in tool.execution…
SteveSandersonMS Feb 18, 2026
6091fd7
fix: check error.message not result.content for permission denied det…
SteveSandersonMS Feb 18, 2026
9ca4554
fix: move requestPermission outside config nil-guard in Go; add resum…
SteveSandersonMS Feb 18, 2026
a5cc0b7
fix: import PermissionHandler in Python e2e test
SteveSandersonMS Feb 18, 2026
594196b
fix: remove redundant inline asyncio imports in Python e2e tests
SteveSandersonMS Feb 18, 2026
63852b5
fix: add approveAll to invokes_built-in_tools test and regenerate sna…
SteveSandersonMS Feb 18, 2026
8aa82f4
fix(go): add permission handler to abort test and regenerate snapshot
SteveSandersonMS Feb 18, 2026
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
12 changes: 7 additions & 5 deletions docs/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,19 +124,21 @@ The `--share` option is not available via SDK. Workarounds:

### Permission Control

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.

Instead of `--allow-all-paths` or `--yolo`, use the permission handler:

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

// Or implement custom logic
if (request.kind === "shell") {
return { approved: request.command.startsWith("git") };
}
return { approved: true };
// if (request.kind === "shell") {
// return { kind: "denied-interactively-by-user" };
// }
// return { kind: "approved" };
},
});
```
Expand Down
37 changes: 37 additions & 0 deletions dotnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,43 @@ var session = await client.CreateSessionAsync(new SessionConfig
});
```

## Permission Requests

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**.

To allow operations, provide an `OnPermissionRequest` handler when creating a session:

```csharp
var session = await client.CreateSessionAsync(new SessionConfig
{
OnPermissionRequest = async (request, invocation) =>
{
// request.Kind - The type of operation: "shell", "write", "read", "url", or "mcp"

// Approve everything (equivalent to --yolo mode in the CLI)
return new PermissionRequestResult { Kind = "approved" };

// Or implement fine-grained policy:
// if (request.Kind == "shell")
// return new PermissionRequestResult { Kind = "denied-interactively-by-user" };
// return new PermissionRequestResult { Kind = "approved" };
}
});
```

**Permission request kinds:**
- `"shell"` — Execute a shell command
- `"write"` — Write to a file
- `"read"` — Read a file
- `"url"` — Fetch a URL
- `"mcp"` — Call an MCP server tool

**Permission result kinds:**
- `"approved"` — Allow the operation
- `"denied-interactively-by-user"` — User explicitly denied
- `"denied-by-rules"` — Denied by policy rules
- `"denied-no-approval-rule-and-could-not-request-from-user"` — Default deny (no handler)

## User Input Requests

Enable the agent to ask questions to the user using the `ask_user` tool by providing an `OnUserInputRequest` handler:
Expand Down
10 changes: 9 additions & 1 deletion dotnet/samples/Chat.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
using GitHub.Copilot.SDK;

await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync();
await using var session = await client.CreateSessionAsync(new SessionConfig
{
// Permission requests are denied by default. Provide a handler to approve operations.
OnPermissionRequest = (request, invocation) =>
{
// Approve all permission requests. Customize this to implement your own policy.
return Task.FromResult(new PermissionRequestResult { Kind = "approved" });
}
});

using var _ = session.On(evt =>
{
Expand Down
4 changes: 2 additions & 2 deletions dotnet/src/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig? config = nul
config?.AvailableTools,
config?.ExcludedTools,
config?.Provider,
config?.OnPermissionRequest != null ? true : null,
(bool?)true,
config?.OnUserInputRequest != null ? true : null,
hasHooks ? true : null,
config?.WorkingDirectory,
Expand Down Expand Up @@ -461,7 +461,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
config?.AvailableTools,
config?.ExcludedTools,
config?.Provider,
config?.OnPermissionRequest != null ? true : null,
(bool?)true,
config?.OnUserInputRequest != null ? true : null,
hasHooks ? true : null,
config?.WorkingDirectory,
Expand Down
36 changes: 36 additions & 0 deletions go/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,42 @@ session, err := client.CreateSession(context.Background(), &copilot.SessionConfi
> - For Azure OpenAI endpoints (`*.openai.azure.com`), you **must** use `Type: "azure"`, not `Type: "openai"`.
> - 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.

## Permission Requests

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**.

To allow operations, provide an `OnPermissionRequest` handler when creating a session:

```go
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
OnPermissionRequest: func(request copilot.PermissionRequest, invocation copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) {
// request.Kind - The type of operation: "shell", "write", "read", "url", or "mcp"

// Approve everything (equivalent to --yolo mode in the CLI)
return copilot.PermissionRequestResult{Kind: "approved"}, nil

// Or implement fine-grained policy:
// if request.Kind == "shell" {
// return copilot.PermissionRequestResult{Kind: "denied-interactively-by-user"}, nil
// }
// return copilot.PermissionRequestResult{Kind: "approved"}, nil
},
})
```

**Permission request kinds:**
- `"shell"` — Execute a shell command
- `"write"` — Write to a file
- `"read"` — Read a file
- `"url"` — Fetch a URL
- `"mcp"` — Call an MCP server tool

**Permission result kinds:**
- `"approved"` — Allow the operation
- `"denied-interactively-by-user"` — User explicitly denied
- `"denied-by-rules"` — Denied by policy rules
- `"denied-no-approval-rule-and-could-not-request-from-user"` — Default deny (no handler)

## User Input Requests

Enable the agent to ask questions to the user using the `ask_user` tool by providing an `OnUserInputRequest` handler:
Expand Down
8 changes: 2 additions & 6 deletions go/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,9 +473,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
if config.Streaming {
req.Streaming = Bool(true)
}
if config.OnPermissionRequest != nil {
req.RequestPermission = Bool(true)
}
req.RequestPermission = Bool(true)
if config.OnUserInputRequest != nil {
req.RequestUserInput = Bool(true)
}
Expand Down Expand Up @@ -562,9 +560,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
if config.Streaming {
req.Streaming = Bool(true)
}
if config.OnPermissionRequest != nil {
req.RequestPermission = Bool(true)
}
req.RequestPermission = Bool(true)
if config.OnUserInputRequest != nil {
req.RequestUserInput = Bool(true)
}
Expand Down
9 changes: 8 additions & 1 deletion go/samples/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@ func main() {
}
defer client.Stop()

session, err := client.CreateSession(ctx, nil)
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
CLIPath: cliPath,
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

The CLIPath field does not exist in SessionConfig. This line should be removed. The CLI path is already specified in the ClientOptions on line 20, which is the correct location for it.

Suggested change
CLIPath: cliPath,

Copilot uses AI. Check for mistakes.
// Permission requests are denied by default. Provide a handler to approve operations.
OnPermissionRequest: func(_ copilot.PermissionRequest, _ copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) {
// Approve all permission requests. Customize this to implement your own policy.
return copilot.PermissionRequestResult{Kind: "approved"}, nil
},
})
if err != nil {
panic(err)
}
Expand Down
8 changes: 6 additions & 2 deletions go/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,9 @@ type SessionConfig struct {
// ExcludedTools is a list of tool names to disable. All other tools remain available.
// Ignored if AvailableTools is specified.
ExcludedTools []string
// OnPermissionRequest is a handler for permission requests from the server
// OnPermissionRequest is a handler for permission requests from the server.
// If nil, all permission requests are denied by default.
// Provide a handler to approve operations (file writes, shell commands, URL fetches, etc.).
OnPermissionRequest PermissionHandler
// OnUserInputRequest is a handler for user input requests from the agent (enables ask_user tool)
OnUserInputRequest UserInputHandler
Expand Down Expand Up @@ -426,7 +428,9 @@ type ResumeSessionConfig struct {
// ReasoningEffort level for models that support it.
// Valid values: "low", "medium", "high", "xhigh"
ReasoningEffort string
// OnPermissionRequest is a handler for permission requests from the server
// OnPermissionRequest is a handler for permission requests from the server.
// If nil, all permission requests are denied by default.
// Provide a handler to approve operations (file writes, shell commands, URL fetches, etc.).
OnPermissionRequest PermissionHandler
// OnUserInputRequest is a handler for user input requests from the agent (enables ask_user tool)
OnUserInputRequest UserInputHandler
Expand Down
36 changes: 36 additions & 0 deletions nodejs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,42 @@ const session = await client.createSession({
> - For Azure OpenAI endpoints (`*.openai.azure.com`), you **must** use `type: "azure"`, not `type: "openai"`.
> - 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.

## Permission Requests

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**.

To allow operations, provide an `onPermissionRequest` handler when creating a session:

```typescript
const session = await client.createSession({
onPermissionRequest: async (request, invocation) => {
// request.kind - The type of operation: "shell" | "write" | "read" | "url" | "mcp"

// Approve everything (equivalent to --yolo mode in the CLI)
return { kind: "approved" };

// Or implement fine-grained policy:
// if (request.kind === "shell") {
// return { kind: "denied-interactively-by-user" };
// }
// return { kind: "approved" };
},
});
```

**Permission request kinds:**
- `"shell"` — Execute a shell command
- `"write"` — Write to a file
- `"read"` — Read a file
- `"url"` — Fetch a URL
- `"mcp"` — Call an MCP server tool

**Permission result kinds:**
- `"approved"` — Allow the operation
- `"denied-interactively-by-user"` — User explicitly denied
- `"denied-by-rules"` — Denied by policy rules
- `"denied-no-approval-rule-and-could-not-request-from-user"` — Default deny (no handler)

## User Input Requests

Enable the agent to ask questions to the user using the `ask_user` tool by providing an `onUserInputRequest` handler:
Expand Down
8 changes: 7 additions & 1 deletion nodejs/samples/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { CopilotClient, type SessionEvent } from "@github/copilot-sdk";

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

session.on((event: SessionEvent) => {
let output: string | null = null;
Expand Down
4 changes: 2 additions & 2 deletions nodejs/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ export class CopilotClient {
availableTools: config.availableTools,
excludedTools: config.excludedTools,
provider: config.provider,
requestPermission: !!config.onPermissionRequest,
requestPermission: true,
requestUserInput: !!config.onUserInputRequest,
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
workingDirectory: config.workingDirectory,
Expand Down Expand Up @@ -605,7 +605,7 @@ export class CopilotClient {
parameters: toJsonSchema(tool.parameters),
})),
provider: config.provider,
requestPermission: !!config.onPermissionRequest,
requestPermission: true,
requestUserInput: !!config.onUserInputRequest,
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
workingDirectory: config.workingDirectory,
Expand Down
36 changes: 36 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,42 @@ session = await client.create_session({
> - For Azure OpenAI endpoints (`*.openai.azure.com`), you **must** use `type: "azure"`, not `type: "openai"`.
> - The `base_url` 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.

## Permission Requests

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 `on_permission_request` handler is registered, all such requests are **automatically denied**.

To allow operations, provide an `on_permission_request` handler when creating a session:

```python
def on_permission_request(request, invocation):
# request["kind"] - The type of operation: "shell", "write", "read", "url", or "mcp"

# Approve everything (equivalent to --yolo mode in the CLI)
return {"kind": "approved"}

# Or implement fine-grained policy:
# if request["kind"] == "shell":
# return {"kind": "denied-interactively-by-user"}
# return {"kind": "approved"}

session = await client.create_session({
"on_permission_request": on_permission_request,
})
```

**Permission request kinds:**
- `"shell"` — Execute a shell command
- `"write"` — Write to a file
- `"read"` — Read a file
- `"url"` — Fetch a URL
- `"mcp"` — Call an MCP server tool

**Permission result kinds:**
- `"approved"` — Allow the operation
- `"denied-interactively-by-user"` — User explicitly denied
- `"denied-by-rules"` — Denied by policy rules
- `"denied-no-approval-rule-and-could-not-request-from-user"` — Default deny (no handler)

## User Input Requests

Enable the agent to ask questions to the user using the `ask_user` tool by providing an `on_user_input_request` handler:
Expand Down
10 changes: 4 additions & 6 deletions python/copilot/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,10 +485,9 @@ async def create_session(self, config: Optional[SessionConfig] = None) -> Copilo
if excluded_tools:
payload["excludedTools"] = excluded_tools

# Enable permission request callback if handler provided
# Always enable permission request callback (deny by default if no handler provided)
on_permission_request = cfg.get("on_permission_request")
if on_permission_request:
payload["requestPermission"] = True
payload["requestPermission"] = True

# Enable user input request callback if handler provided
on_user_input_request = cfg.get("on_user_input_request")
Expand Down Expand Up @@ -662,10 +661,9 @@ async def resume_session(
if streaming is not None:
payload["streaming"] = streaming

# Enable permission request callback if handler provided
# Always enable permission request callback (deny by default if no handler provided)
on_permission_request = cfg.get("on_permission_request")
if on_permission_request:
payload["requestPermission"] = True
payload["requestPermission"] = True

# Enable user input request callback if handler provided
on_user_input_request = cfg.get("on_user_input_request")
Expand Down
10 changes: 9 additions & 1 deletion python/samples/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@
RESET = "\033[0m"


def on_permission_request(request, invocation):
# Permission requests are denied by default. Approve all here.
# Customize this to implement your own policy.
return {"kind": "approved"}


async def main():
client = CopilotClient()
await client.start()
session = await client.create_session()
session = await client.create_session({
"on_permission_request": on_permission_request,
})

def on_event(event):
output = None
Expand Down
Loading