Files
gh-jezweb-claude-skills-ski…/references/permissions-guide.md
2025-11-30 08:23:58 +08:00

430 lines
8.2 KiB
Markdown

# Permissions Guide
Complete guide to permission control in Claude Agent SDK.
---
## Permission Modes
### Overview
Three built-in modes:
| Mode | Behavior | Use Case |
|------|----------|----------|
| `default` | Standard checks | General use, production |
| `acceptEdits` | Auto-approve file edits | Trusted refactoring |
| `bypassPermissions` | Skip ALL checks | CI/CD, sandboxed envs |
---
## Default Mode
Standard permission checks.
```typescript
options: {
permissionMode: "default"
}
```
**Prompts user for**:
- File writes/edits
- Potentially dangerous bash commands
- Sensitive operations
**Auto-allows**:
- Read operations (Read, Grep, Glob)
- Safe bash commands
---
## Accept Edits Mode
Automatically approves file modifications.
```typescript
options: {
permissionMode: "acceptEdits"
}
```
**Auto-approves**:
- File edits (Edit)
- File writes (Write)
**Still prompts for**:
- Dangerous bash commands
- Sensitive operations
**Use when**: Refactoring, code generation workflows
---
## Bypass Permissions Mode
⚠️ **DANGER**: Skips ALL permission checks.
```typescript
options: {
permissionMode: "bypassPermissions"
}
```
**Auto-approves EVERYTHING**:
- All file operations
- All bash commands
- All tools
**ONLY use in**:
- CI/CD pipelines
- Sandboxed containers
- Docker environments
- Trusted, isolated contexts
**NEVER use in**:
- Production systems
- User-facing environments
- Untrusted inputs
---
## Custom Permission Logic
### canUseTool Callback
```typescript
type CanUseTool = (
toolName: string,
input: any
) => Promise<PermissionDecision>;
type PermissionDecision =
| { behavior: "allow" }
| { behavior: "deny"; message?: string }
| { behavior: "ask"; message?: string };
```
### Basic Example
```typescript
options: {
canUseTool: async (toolName, input) => {
// Allow read-only
if (['Read', 'Grep', 'Glob'].includes(toolName)) {
return { behavior: "allow" };
}
// Deny dangerous bash
if (toolName === 'Bash' && input.command.includes('rm -rf')) {
return {
behavior: "deny",
message: "Destructive command blocked"
};
}
// Ask for confirmation
if (toolName === 'Write') {
return {
behavior: "ask",
message: `Create ${input.file_path}?`
};
}
return { behavior: "allow" };
}
}
```
---
## Common Patterns
### Pattern 1: Block Destructive Commands
```typescript
canUseTool: async (toolName, input) => {
if (toolName === 'Bash') {
const dangerous = [
'rm -rf',
'dd if=',
'mkfs',
'> /dev/',
'shutdown',
'reboot',
'kill -9',
'pkill'
];
for (const pattern of dangerous) {
if (input.command.includes(pattern)) {
return {
behavior: "deny",
message: `Blocked dangerous command: ${pattern}`
};
}
}
}
return { behavior: "allow" };
}
```
### Pattern 2: Protect Sensitive Files
```typescript
canUseTool: async (toolName, input) => {
if (toolName === 'Write' || toolName === 'Edit') {
const sensitivePaths = [
'/etc/',
'/root/',
'.env',
'credentials',
'secrets',
'config/production',
'.ssh/',
'private_key'
];
for (const path of sensitivePaths) {
if (input.file_path?.includes(path)) {
return {
behavior: "ask",
message: `⚠️ Modify sensitive file: ${input.file_path}?`
};
}
}
}
return { behavior: "allow" };
}
```
### Pattern 3: Environment-Based Permissions
```typescript
const environment = process.env.NODE_ENV; // 'development' | 'staging' | 'production'
canUseTool: async (toolName, input) => {
// Production: require approval for everything
if (environment === 'production') {
if (toolName === 'Bash' || toolName === 'Write' || toolName === 'Edit') {
return {
behavior: "ask",
message: `PRODUCTION: Approve ${toolName}?`
};
}
}
// Staging: auto-approve edits
if (environment === 'staging') {
if (toolName === 'Write' || toolName === 'Edit') {
return { behavior: "allow" };
}
}
// Development: allow most things
if (environment === 'development') {
return { behavior: "allow" };
}
return { behavior: "allow" };
}
```
### Pattern 4: Deployment Confirmation
```typescript
canUseTool: async (toolName, input) => {
if (toolName === 'Bash') {
const deploymentPatterns = [
'deploy',
'kubectl apply',
'terraform apply',
'helm install',
'docker push',
'npm publish'
];
for (const pattern of deploymentPatterns) {
if (input.command.includes(pattern)) {
return {
behavior: "ask",
message: `🚀 DEPLOYMENT: ${input.command}\n\nProceed?`
};
}
}
}
return { behavior: "allow" };
}
```
### Pattern 5: Audit Logging
```typescript
const auditLog: Array<{
tool: string;
input: any;
decision: string;
timestamp: Date;
}> = [];
canUseTool: async (toolName, input) => {
// Log everything
console.log(`[${new Date().toISOString()}] ${toolName}`);
const decision = { behavior: "allow" as const };
// Store audit log
auditLog.push({
tool: toolName,
input,
decision: decision.behavior,
timestamp: new Date()
});
// Could also send to external service
// await logToDatadog(toolName, input, decision);
return decision;
}
```
---
## Combining Modes with Custom Logic
```typescript
options: {
permissionMode: "acceptEdits", // Auto-approve file edits
canUseTool: async (toolName, input) => {
// But still block dangerous bash
if (toolName === 'Bash' && input.command.includes('rm -rf')) {
return { behavior: "deny", message: "Blocked" };
}
// Custom logic runs AFTER permission mode
return { behavior: "allow" };
}
}
```
---
## Security Best Practices
### ✅ Do
- Start with `"default"` mode
- Use `canUseTool` for fine-grained control
- Block known dangerous patterns
- Require confirmation for sensitive ops
- Log all tool usage for auditing
- Test permission logic thoroughly
- Use environment-based rules
- Implement rate limiting if needed
### ❌ Don't
- Use `"bypassPermissions"` in production
- Skip permission checks for "trusted" inputs
- Allow arbitrary bash without filtering
- Trust file paths without validation
- Ignore audit logging
- Assume AI won't make mistakes
- Give blanket approvals
---
## Testing Permissions
```typescript
async function testPermissions() {
const tests = [
{ tool: "Read", input: { file_path: "/etc/passwd" }, expectAllow: true },
{ tool: "Bash", input: { command: "rm -rf /" }, expectDeny: true },
{ tool: "Write", input: { file_path: ".env" }, expectAsk: true }
];
for (const test of tests) {
const decision = await canUseTool(test.tool, test.input);
if (test.expectAllow && decision.behavior !== 'allow') {
console.error(`FAIL: Expected allow for ${test.tool}`);
}
if (test.expectDeny && decision.behavior !== 'deny') {
console.error(`FAIL: Expected deny for ${test.tool}`);
}
if (test.expectAsk && decision.behavior !== 'ask') {
console.error(`FAIL: Expected ask for ${test.tool}`);
}
}
}
```
---
## Common Scenarios
### Allow Read-Only
```typescript
canUseTool: async (toolName, input) => {
if (['Read', 'Grep', 'Glob'].includes(toolName)) {
return { behavior: "allow" };
}
return {
behavior: "deny",
message: "Read-only mode"
};
}
```
### Require Approval for All
```typescript
canUseTool: async (toolName, input) => {
return {
behavior: "ask",
message: `Approve ${toolName}?`
};
}
```
### Block Bash Entirely
```typescript
canUseTool: async (toolName, input) => {
if (toolName === 'Bash') {
return {
behavior: "deny",
message: "Bash execution disabled"
};
}
return { behavior: "allow" };
}
```
---
## Error Handling
Handle permission errors gracefully:
```typescript
for await (const message of response) {
if (message.type === 'error') {
if (message.error.type === 'permission_denied') {
console.log('Permission denied for:', message.error.tool);
// Continue with fallback or skip
}
}
}
```
---
**For more details**: See SKILL.md
**Template**: templates/permission-control.ts