gencode-ai 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +11 -0
- package/CLAUDE.md +70 -0
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/dist/agent/agent.d.ts +84 -0
- package/dist/agent/agent.d.ts.map +1 -0
- package/dist/agent/agent.js +233 -0
- package/dist/agent/agent.js.map +1 -0
- package/dist/agent/index.d.ts +6 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +6 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/types.d.ts +47 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +5 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/cli/components/App.d.ts +14 -0
- package/dist/cli/components/App.d.ts.map +1 -0
- package/dist/cli/components/App.js +395 -0
- package/dist/cli/components/App.js.map +1 -0
- package/dist/cli/components/CommandSuggestions.d.ts +13 -0
- package/dist/cli/components/CommandSuggestions.d.ts.map +1 -0
- package/dist/cli/components/CommandSuggestions.js +32 -0
- package/dist/cli/components/CommandSuggestions.js.map +1 -0
- package/dist/cli/components/Header.d.ts +9 -0
- package/dist/cli/components/Header.d.ts.map +1 -0
- package/dist/cli/components/Header.js +13 -0
- package/dist/cli/components/Header.js.map +1 -0
- package/dist/cli/components/Input.d.ts +13 -0
- package/dist/cli/components/Input.d.ts.map +1 -0
- package/dist/cli/components/Input.js +27 -0
- package/dist/cli/components/Input.js.map +1 -0
- package/dist/cli/components/Logo.d.ts +2 -0
- package/dist/cli/components/Logo.d.ts.map +1 -0
- package/dist/cli/components/Logo.js +8 -0
- package/dist/cli/components/Logo.js.map +1 -0
- package/dist/cli/components/Messages.d.ts +37 -0
- package/dist/cli/components/Messages.d.ts.map +1 -0
- package/dist/cli/components/Messages.js +106 -0
- package/dist/cli/components/Messages.js.map +1 -0
- package/dist/cli/components/ModelSelector.d.ts +13 -0
- package/dist/cli/components/ModelSelector.d.ts.map +1 -0
- package/dist/cli/components/ModelSelector.js +72 -0
- package/dist/cli/components/ModelSelector.js.map +1 -0
- package/dist/cli/components/Spinner.d.ts +12 -0
- package/dist/cli/components/Spinner.d.ts.map +1 -0
- package/dist/cli/components/Spinner.js +45 -0
- package/dist/cli/components/Spinner.js.map +1 -0
- package/dist/cli/components/index.d.ts +12 -0
- package/dist/cli/components/index.d.ts.map +1 -0
- package/dist/cli/components/index.js +12 -0
- package/dist/cli/components/index.js.map +1 -0
- package/dist/cli/components/theme.d.ts +31 -0
- package/dist/cli/components/theme.d.ts.map +1 -0
- package/dist/cli/components/theme.js +36 -0
- package/dist/cli/components/theme.js.map +1 -0
- package/dist/cli/index-legacy.d.ts +7 -0
- package/dist/cli/index-legacy.d.ts.map +1 -0
- package/dist/cli/index-legacy.js +431 -0
- package/dist/cli/index-legacy.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +116 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/ink-cli.d.ts +7 -0
- package/dist/cli/ink-cli.d.ts.map +1 -0
- package/dist/cli/ink-cli.js +105 -0
- package/dist/cli/ink-cli.js.map +1 -0
- package/dist/cli/session-picker.d.ts +16 -0
- package/dist/cli/session-picker.d.ts.map +1 -0
- package/dist/cli/session-picker.js +280 -0
- package/dist/cli/session-picker.js.map +1 -0
- package/dist/cli/ui.d.ts +61 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +364 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +6 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/manager.d.ts +31 -0
- package/dist/config/manager.d.ts.map +1 -0
- package/dist/config/manager.js +65 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/config/types.d.ts +22 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +6 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/index.d.ts +10 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +9 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/init.d.ts +20 -0
- package/dist/memory/init.d.ts.map +1 -0
- package/dist/memory/init.js +332 -0
- package/dist/memory/init.js.map +1 -0
- package/dist/memory/manager.d.ts +85 -0
- package/dist/memory/manager.d.ts.map +1 -0
- package/dist/memory/manager.js +234 -0
- package/dist/memory/manager.js.map +1 -0
- package/dist/memory/types.d.ts +74 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +6 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/permissions/index.d.ts +7 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +6 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/permissions/manager.d.ts +32 -0
- package/dist/permissions/manager.d.ts.map +1 -0
- package/dist/permissions/manager.js +79 -0
- package/dist/permissions/manager.js.map +1 -0
- package/dist/permissions/types.d.ts +14 -0
- package/dist/permissions/types.d.ts.map +1 -0
- package/dist/permissions/types.js +17 -0
- package/dist/permissions/types.js.map +1 -0
- package/dist/providers/anthropic.d.ts +20 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +185 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/gemini.d.ts +21 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +241 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/index.d.ts +34 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +72 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai.d.ts +19 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +221 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/types.d.ts +125 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +6 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/session/index.d.ts +6 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +6 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/manager.d.ts +101 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +295 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/session/types.d.ts +39 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/session/types.js +10 -0
- package/dist/session/types.js.map +1 -0
- package/dist/tools/builtin/bash.d.ts +7 -0
- package/dist/tools/builtin/bash.d.ts.map +1 -0
- package/dist/tools/builtin/bash.js +80 -0
- package/dist/tools/builtin/bash.js.map +1 -0
- package/dist/tools/builtin/edit.d.ts +7 -0
- package/dist/tools/builtin/edit.d.ts.map +1 -0
- package/dist/tools/builtin/edit.js +32 -0
- package/dist/tools/builtin/edit.js.map +1 -0
- package/dist/tools/builtin/glob.d.ts +7 -0
- package/dist/tools/builtin/glob.d.ts.map +1 -0
- package/dist/tools/builtin/glob.js +36 -0
- package/dist/tools/builtin/glob.js.map +1 -0
- package/dist/tools/builtin/grep.d.ts +7 -0
- package/dist/tools/builtin/grep.d.ts.map +1 -0
- package/dist/tools/builtin/grep.js +59 -0
- package/dist/tools/builtin/grep.js.map +1 -0
- package/dist/tools/builtin/read.d.ts +7 -0
- package/dist/tools/builtin/read.d.ts.map +1 -0
- package/dist/tools/builtin/read.js +29 -0
- package/dist/tools/builtin/read.js.map +1 -0
- package/dist/tools/builtin/write.d.ts +7 -0
- package/dist/tools/builtin/write.d.ts.map +1 -0
- package/dist/tools/builtin/write.js +24 -0
- package/dist/tools/builtin/write.js.map +1 -0
- package/dist/tools/index.d.ts +38 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +32 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/registry.d.ts +22 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +71 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/types.d.ts +62 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +126 -0
- package/dist/tools/types.js.map +1 -0
- package/docs/README.md +16 -0
- package/docs/proposals/0001-web-fetch-tool.md +293 -0
- package/docs/proposals/0002-web-search-tool.md +306 -0
- package/docs/proposals/0003-task-subagents.md +333 -0
- package/docs/proposals/0004-plan-mode.md +338 -0
- package/docs/proposals/0005-todo-system.md +299 -0
- package/docs/proposals/0006-memory-system.md +539 -0
- package/docs/proposals/0007-context-management.md +429 -0
- package/docs/proposals/0008-checkpointing.md +327 -0
- package/docs/proposals/0009-hooks-system.md +343 -0
- package/docs/proposals/0010-mcp-integration.md +382 -0
- package/docs/proposals/0011-custom-commands.md +374 -0
- package/docs/proposals/0012-ask-user-question.md +317 -0
- package/docs/proposals/0013-multi-edit-tool.md +345 -0
- package/docs/proposals/0014-lsp-tool.md +478 -0
- package/docs/proposals/0015-ls-tool.md +407 -0
- package/docs/proposals/0016-kill-shell-tool.md +455 -0
- package/docs/proposals/0017-background-tasks.md +489 -0
- package/docs/proposals/0018-parallel-tool-execution.md +415 -0
- package/docs/proposals/0019-session-enhancements.md +462 -0
- package/docs/proposals/0020-session-summarization.md +447 -0
- package/docs/proposals/0021-skills-system.md +409 -0
- package/docs/proposals/0022-plugin-system.md +467 -0
- package/docs/proposals/0023-permission-enhancements.md +470 -0
- package/docs/proposals/0024-keyboard-shortcuts.md +443 -0
- package/docs/proposals/0025-cost-tracking.md +447 -0
- package/docs/proposals/0026-git-integration.md +475 -0
- package/docs/proposals/0027-enhanced-read-tool.md +514 -0
- package/docs/proposals/0028-enhanced-bash-tool.md +511 -0
- package/docs/proposals/0029-notebook-edit-tool.md +413 -0
- package/docs/proposals/0030-plugin-marketplace.md +360 -0
- package/docs/proposals/0031-command-suggestions.md +295 -0
- package/docs/proposals/0032-ide-integrations.md +328 -0
- package/docs/proposals/0033-enterprise-deployment.md +221 -0
- package/docs/proposals/0034-sandboxing.md +273 -0
- package/docs/proposals/0035-auto-updater.md +311 -0
- package/docs/proposals/0036-enhanced-glob-tool.md +267 -0
- package/docs/proposals/0037-enhanced-grep-tool.md +360 -0
- package/docs/proposals/0038-interactive-cli-ui.md +373 -0
- package/docs/proposals/0039-streaming-enhancements.md +359 -0
- package/docs/proposals/0040-multi-provider-enhancements.md +369 -0
- package/docs/proposals/README.md +84 -0
- package/docs/proposals/TEMPLATE.md +57 -0
- package/docs/proposals/research/claude-code-research.md +307 -0
- package/examples/agent-demo.ts +115 -0
- package/examples/basic.ts +166 -0
- package/package.json +50 -0
- package/src/agent/agent.ts +276 -0
- package/src/agent/index.ts +6 -0
- package/src/agent/types.ts +62 -0
- package/src/cli/components/App.tsx +565 -0
- package/src/cli/components/CommandSuggestions.tsx +58 -0
- package/src/cli/components/Header.tsx +36 -0
- package/src/cli/components/Input.tsx +60 -0
- package/src/cli/components/Logo.tsx +16 -0
- package/src/cli/components/Messages.tsx +210 -0
- package/src/cli/components/ModelSelector.tsx +135 -0
- package/src/cli/components/Spinner.tsx +72 -0
- package/src/cli/components/index.ts +21 -0
- package/src/cli/components/theme.ts +36 -0
- package/src/cli/index.tsx +136 -0
- package/src/config/index.ts +7 -0
- package/src/config/manager.ts +77 -0
- package/src/config/types.ts +25 -0
- package/src/index.ts +86 -0
- package/src/permissions/index.ts +7 -0
- package/src/permissions/manager.ts +97 -0
- package/src/permissions/types.ts +29 -0
- package/src/providers/anthropic.ts +224 -0
- package/src/providers/gemini.ts +295 -0
- package/src/providers/index.ts +97 -0
- package/src/providers/openai.ts +261 -0
- package/src/providers/types.ts +181 -0
- package/src/session/index.ts +6 -0
- package/src/session/manager.ts +354 -0
- package/src/session/types.ts +49 -0
- package/src/tools/builtin/bash.ts +92 -0
- package/src/tools/builtin/edit.ts +37 -0
- package/src/tools/builtin/glob.ts +42 -0
- package/src/tools/builtin/grep.ts +67 -0
- package/src/tools/builtin/read.ts +34 -0
- package/src/tools/builtin/write.ts +27 -0
- package/src/tools/index.ts +36 -0
- package/src/tools/registry.ts +83 -0
- package/src/tools/types.ts +172 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
# Proposal: Permission Enhancements
|
|
2
|
+
|
|
3
|
+
- **Proposal ID**: 0023
|
|
4
|
+
- **Author**: mycode team
|
|
5
|
+
- **Status**: Draft
|
|
6
|
+
- **Created**: 2025-01-15
|
|
7
|
+
- **Updated**: 2025-01-15
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
Enhance the permission system with pattern-based rules, prompt-based approvals, persistent allowlists, and fine-grained control over tool execution. This provides users with flexible, secure control over agent capabilities.
|
|
12
|
+
|
|
13
|
+
## Motivation
|
|
14
|
+
|
|
15
|
+
The current permission system is basic:
|
|
16
|
+
|
|
17
|
+
1. **Tool-level only**: Can't distinguish safe vs dangerous uses
|
|
18
|
+
2. **No persistence**: Approvals lost between sessions
|
|
19
|
+
3. **No patterns**: Can't approve "all git commands"
|
|
20
|
+
4. **Manual each time**: Repetitive confirmation fatigue
|
|
21
|
+
5. **No audit trail**: No record of what was approved
|
|
22
|
+
|
|
23
|
+
Enhanced permissions balance security with usability.
|
|
24
|
+
|
|
25
|
+
## Claude Code Reference
|
|
26
|
+
|
|
27
|
+
Claude Code provides sophisticated permission handling:
|
|
28
|
+
|
|
29
|
+
### ExitPlanMode Permissions
|
|
30
|
+
```typescript
|
|
31
|
+
ExitPlanMode({
|
|
32
|
+
allowedPrompts: [
|
|
33
|
+
{ tool: "Bash", prompt: "run tests" },
|
|
34
|
+
{ tool: "Bash", prompt: "install dependencies" },
|
|
35
|
+
{ tool: "Bash", prompt: "build the project" }
|
|
36
|
+
]
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Settings-Based Permissions
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"permissions": {
|
|
44
|
+
"allow": [
|
|
45
|
+
"Bash(git add:*)",
|
|
46
|
+
"Bash(git commit:*)",
|
|
47
|
+
"Bash(npm install:*)"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Permission Guidelines
|
|
54
|
+
- Scope permissions narrowly
|
|
55
|
+
- Use semantic descriptions, not literal commands
|
|
56
|
+
- Read-only permissions for read-only operations
|
|
57
|
+
- Session-scoped by default
|
|
58
|
+
- Input-aware caching
|
|
59
|
+
|
|
60
|
+
## Detailed Design
|
|
61
|
+
|
|
62
|
+
### API Design
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// src/permissions/types.ts
|
|
66
|
+
type PermissionMode = 'auto' | 'confirm' | 'deny';
|
|
67
|
+
|
|
68
|
+
interface PermissionRule {
|
|
69
|
+
tool: string | RegExp;
|
|
70
|
+
mode: PermissionMode;
|
|
71
|
+
pattern?: string | RegExp; // Input pattern matching
|
|
72
|
+
prompt?: string; // Semantic description
|
|
73
|
+
scope?: 'session' | 'project' | 'global';
|
|
74
|
+
expiresAt?: Date;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface PromptPermission {
|
|
78
|
+
tool: string;
|
|
79
|
+
prompt: string; // Semantic description
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface PermissionConfig {
|
|
83
|
+
defaultMode: PermissionMode;
|
|
84
|
+
rules: PermissionRule[];
|
|
85
|
+
allowedPrompts: PromptPermission[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface PermissionContext {
|
|
89
|
+
tool: string;
|
|
90
|
+
input: unknown;
|
|
91
|
+
sessionId: string;
|
|
92
|
+
projectPath: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface PermissionDecision {
|
|
96
|
+
allowed: boolean;
|
|
97
|
+
reason: string;
|
|
98
|
+
matchedRule?: PermissionRule;
|
|
99
|
+
requiresConfirmation: boolean;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
interface PermissionAuditEntry {
|
|
103
|
+
timestamp: Date;
|
|
104
|
+
tool: string;
|
|
105
|
+
input: unknown;
|
|
106
|
+
decision: 'allowed' | 'denied' | 'confirmed';
|
|
107
|
+
rule?: string;
|
|
108
|
+
userId?: string;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Enhanced Permission Manager
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// src/permissions/manager.ts
|
|
116
|
+
class PermissionManager {
|
|
117
|
+
private config: PermissionConfig;
|
|
118
|
+
private sessionApprovals: Map<string, Set<string>> = new Map();
|
|
119
|
+
private persistentRules: PermissionRule[] = [];
|
|
120
|
+
private auditLog: PermissionAuditEntry[] = [];
|
|
121
|
+
private promptMatcher: PromptMatcher;
|
|
122
|
+
|
|
123
|
+
constructor(config?: Partial<PermissionConfig>) {
|
|
124
|
+
this.config = {
|
|
125
|
+
defaultMode: 'confirm',
|
|
126
|
+
rules: DEFAULT_RULES,
|
|
127
|
+
allowedPrompts: [],
|
|
128
|
+
...config
|
|
129
|
+
};
|
|
130
|
+
this.loadPersistentRules();
|
|
131
|
+
this.promptMatcher = new PromptMatcher();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async checkPermission(
|
|
135
|
+
context: PermissionContext
|
|
136
|
+
): Promise<PermissionDecision> {
|
|
137
|
+
const { tool, input, sessionId } = context;
|
|
138
|
+
|
|
139
|
+
// Check explicit deny rules first
|
|
140
|
+
const denyRule = this.findMatchingRule(context, 'deny');
|
|
141
|
+
if (denyRule) {
|
|
142
|
+
this.logAudit(context, 'denied', denyRule);
|
|
143
|
+
return {
|
|
144
|
+
allowed: false,
|
|
145
|
+
reason: `Denied by rule: ${denyRule.prompt || denyRule.pattern}`,
|
|
146
|
+
matchedRule: denyRule,
|
|
147
|
+
requiresConfirmation: false
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Check auto-allow rules
|
|
152
|
+
const autoRule = this.findMatchingRule(context, 'auto');
|
|
153
|
+
if (autoRule) {
|
|
154
|
+
this.logAudit(context, 'allowed', autoRule);
|
|
155
|
+
return {
|
|
156
|
+
allowed: true,
|
|
157
|
+
reason: 'Auto-approved by rule',
|
|
158
|
+
matchedRule: autoRule,
|
|
159
|
+
requiresConfirmation: false
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Check prompt-based permissions
|
|
164
|
+
const promptMatch = await this.matchPrompt(tool, input);
|
|
165
|
+
if (promptMatch) {
|
|
166
|
+
this.logAudit(context, 'allowed', { prompt: promptMatch });
|
|
167
|
+
return {
|
|
168
|
+
allowed: true,
|
|
169
|
+
reason: `Matches approved prompt: ${promptMatch}`,
|
|
170
|
+
requiresConfirmation: false
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check session approvals cache
|
|
175
|
+
const cacheKey = this.getCacheKey(tool, input);
|
|
176
|
+
const sessionCache = this.sessionApprovals.get(sessionId);
|
|
177
|
+
if (sessionCache?.has(cacheKey)) {
|
|
178
|
+
return {
|
|
179
|
+
allowed: true,
|
|
180
|
+
reason: 'Previously approved in session',
|
|
181
|
+
requiresConfirmation: false
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Default: requires confirmation
|
|
186
|
+
return {
|
|
187
|
+
allowed: false,
|
|
188
|
+
reason: 'Requires user confirmation',
|
|
189
|
+
requiresConfirmation: true
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async requestPermission(
|
|
194
|
+
context: PermissionContext,
|
|
195
|
+
confirmCallback: ConfirmCallback
|
|
196
|
+
): Promise<boolean> {
|
|
197
|
+
const decision = await this.checkPermission(context);
|
|
198
|
+
|
|
199
|
+
if (decision.allowed) return true;
|
|
200
|
+
if (!decision.requiresConfirmation) return false;
|
|
201
|
+
|
|
202
|
+
// Request user confirmation
|
|
203
|
+
const confirmed = await confirmCallback(
|
|
204
|
+
context.tool,
|
|
205
|
+
context.input,
|
|
206
|
+
this.getSuggestions(context)
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
if (confirmed) {
|
|
210
|
+
this.cacheApproval(context);
|
|
211
|
+
this.logAudit(context, 'confirmed');
|
|
212
|
+
} else {
|
|
213
|
+
this.logAudit(context, 'denied');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return confirmed;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
addAllowedPrompts(prompts: PromptPermission[]): void {
|
|
220
|
+
this.config.allowedPrompts.push(...prompts);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
clearSessionApprovals(sessionId: string): void {
|
|
224
|
+
this.sessionApprovals.delete(sessionId);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private async matchPrompt(tool: string, input: unknown): Promise<string | null> {
|
|
228
|
+
for (const permission of this.config.allowedPrompts) {
|
|
229
|
+
if (permission.tool !== tool) continue;
|
|
230
|
+
|
|
231
|
+
const matches = await this.promptMatcher.matches(
|
|
232
|
+
permission.prompt,
|
|
233
|
+
input
|
|
234
|
+
);
|
|
235
|
+
if (matches) return permission.prompt;
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private findMatchingRule(
|
|
241
|
+
context: PermissionContext,
|
|
242
|
+
mode: PermissionMode
|
|
243
|
+
): PermissionRule | undefined {
|
|
244
|
+
const allRules = [...this.config.rules, ...this.persistentRules];
|
|
245
|
+
|
|
246
|
+
return allRules.find(rule => {
|
|
247
|
+
if (rule.mode !== mode) return false;
|
|
248
|
+
if (!this.matchesTool(rule.tool, context.tool)) return false;
|
|
249
|
+
if (rule.pattern && !this.matchesPattern(rule.pattern, context.input)) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private matchesTool(pattern: string | RegExp, tool: string): boolean {
|
|
257
|
+
if (typeof pattern === 'string') {
|
|
258
|
+
return pattern === tool || pattern === '*';
|
|
259
|
+
}
|
|
260
|
+
return pattern.test(tool);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private matchesPattern(pattern: string | RegExp, input: unknown): boolean {
|
|
264
|
+
const inputStr = JSON.stringify(input);
|
|
265
|
+
if (typeof pattern === 'string') {
|
|
266
|
+
// Support glob-like patterns
|
|
267
|
+
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
|
268
|
+
return regex.test(inputStr);
|
|
269
|
+
}
|
|
270
|
+
return pattern.test(inputStr);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Persistence
|
|
274
|
+
async saveRule(rule: PermissionRule): Promise<void> {
|
|
275
|
+
this.persistentRules.push(rule);
|
|
276
|
+
await this.savePersistentRules();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private getSuggestions(context: PermissionContext): PermissionSuggestion[] {
|
|
280
|
+
return [
|
|
281
|
+
{ action: 'allow_once', label: 'Allow this time' },
|
|
282
|
+
{ action: 'allow_session', label: 'Allow for this session' },
|
|
283
|
+
{ action: 'allow_always', label: 'Always allow this' },
|
|
284
|
+
{ action: 'deny', label: 'Deny' }
|
|
285
|
+
];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Prompt Matcher
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
// src/permissions/prompt-matcher.ts
|
|
294
|
+
class PromptMatcher {
|
|
295
|
+
private patterns: Map<string, (input: unknown) => boolean> = new Map();
|
|
296
|
+
|
|
297
|
+
constructor() {
|
|
298
|
+
this.registerBuiltInPatterns();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private registerBuiltInPatterns(): void {
|
|
302
|
+
// Git operations
|
|
303
|
+
this.patterns.set('run tests', input =>
|
|
304
|
+
this.matchesCommand(input, ['npm test', 'pytest', 'go test', 'jest'])
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
this.patterns.set('install dependencies', input =>
|
|
308
|
+
this.matchesCommand(input, ['npm install', 'pip install', 'cargo build'])
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
this.patterns.set('build the project', input =>
|
|
312
|
+
this.matchesCommand(input, ['npm run build', 'make', 'cargo build'])
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
this.patterns.set('git operations', input =>
|
|
316
|
+
this.matchesCommand(input, ['git '])
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async matches(prompt: string, input: unknown): Promise<boolean> {
|
|
321
|
+
// Check exact pattern match
|
|
322
|
+
const pattern = this.patterns.get(prompt.toLowerCase());
|
|
323
|
+
if (pattern) return pattern(input);
|
|
324
|
+
|
|
325
|
+
// Fuzzy semantic matching (for complex prompts)
|
|
326
|
+
return this.semanticMatch(prompt, input);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private matchesCommand(input: unknown, prefixes: string[]): boolean {
|
|
330
|
+
const command = (input as { command?: string })?.command || '';
|
|
331
|
+
return prefixes.some(prefix => command.startsWith(prefix));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private semanticMatch(prompt: string, input: unknown): boolean {
|
|
335
|
+
// Simple keyword matching for now
|
|
336
|
+
const inputStr = JSON.stringify(input).toLowerCase();
|
|
337
|
+
const keywords = prompt.toLowerCase().split(/\s+/);
|
|
338
|
+
return keywords.some(kw => inputStr.includes(kw));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### File Changes
|
|
344
|
+
|
|
345
|
+
| File | Action | Description |
|
|
346
|
+
|------|--------|-------------|
|
|
347
|
+
| `src/permissions/types.ts` | Modify | Enhanced types |
|
|
348
|
+
| `src/permissions/manager.ts` | Modify | Enhanced manager |
|
|
349
|
+
| `src/permissions/prompt-matcher.ts` | Create | Semantic matching |
|
|
350
|
+
| `src/permissions/persistence.ts` | Create | Rule persistence |
|
|
351
|
+
| `src/permissions/audit.ts` | Create | Audit logging |
|
|
352
|
+
| `src/cli/commands/permissions.ts` | Create | Permission CLI |
|
|
353
|
+
|
|
354
|
+
## User Experience
|
|
355
|
+
|
|
356
|
+
### Permission Prompt with Options
|
|
357
|
+
```
|
|
358
|
+
┌─ Permission Request ──────────────────────────────┐
|
|
359
|
+
│ Tool: Bash │
|
|
360
|
+
│ Command: npm install lodash │
|
|
361
|
+
│ │
|
|
362
|
+
│ This operation requires your approval. │
|
|
363
|
+
│ │
|
|
364
|
+
│ [1] Allow once │
|
|
365
|
+
│ [2] Allow for this session │
|
|
366
|
+
│ [3] Always allow "npm install" commands │
|
|
367
|
+
│ [4] Deny │
|
|
368
|
+
└───────────────────────────────────────────────────┘
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### View Permissions
|
|
372
|
+
```
|
|
373
|
+
User: /permissions
|
|
374
|
+
|
|
375
|
+
Permission Rules:
|
|
376
|
+
┌────────────────────────────────────────────────────────────┐
|
|
377
|
+
│ Type Tool Pattern Scope Mode │
|
|
378
|
+
├────────────────────────────────────────────────────────────┤
|
|
379
|
+
│ Built-in Read * session auto │
|
|
380
|
+
│ Built-in Glob * session auto │
|
|
381
|
+
│ Built-in Grep * session auto │
|
|
382
|
+
│ Custom Bash npm install:* project auto │
|
|
383
|
+
│ Custom Bash git add:* global auto │
|
|
384
|
+
│ Session Bash pytest session auto │
|
|
385
|
+
└────────────────────────────────────────────────────────────┘
|
|
386
|
+
|
|
387
|
+
Pending Prompts (from plan approval):
|
|
388
|
+
• Bash: run tests
|
|
389
|
+
• Bash: build the project
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Permission Audit
|
|
393
|
+
```
|
|
394
|
+
User: /permissions audit
|
|
395
|
+
|
|
396
|
+
Recent Permission Decisions:
|
|
397
|
+
┌────────────────────────────────────────────────────────────────┐
|
|
398
|
+
│ Time Tool Input Decision Rule │
|
|
399
|
+
├────────────────────────────────────────────────────────────────┤
|
|
400
|
+
│ 10:42 Bash npm test allowed prompt:tests │
|
|
401
|
+
│ 10:41 Read src/index.ts allowed built-in │
|
|
402
|
+
│ 10:40 Write src/new.ts confirmed user-approval │
|
|
403
|
+
│ 10:38 Bash rm -rf /tmp/test denied blocked-cmd │
|
|
404
|
+
└────────────────────────────────────────────────────────────────┘
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Alternatives Considered
|
|
408
|
+
|
|
409
|
+
### Alternative 1: Capability-Based Security
|
|
410
|
+
Use capability tokens instead of rules.
|
|
411
|
+
|
|
412
|
+
**Pros**: More formal model
|
|
413
|
+
**Cons**: Complex for users
|
|
414
|
+
**Decision**: Deferred - Consider for enterprise
|
|
415
|
+
|
|
416
|
+
### Alternative 2: AI-Based Permission Decisions
|
|
417
|
+
Let AI assess risk of operations.
|
|
418
|
+
|
|
419
|
+
**Pros**: Intelligent decisions
|
|
420
|
+
**Cons**: Unpredictable, trust issues
|
|
421
|
+
**Decision**: Rejected - Users need control
|
|
422
|
+
|
|
423
|
+
### Alternative 3: Whitelist-Only Mode
|
|
424
|
+
Only allow explicitly approved operations.
|
|
425
|
+
|
|
426
|
+
**Pros**: Maximum security
|
|
427
|
+
**Cons**: Too restrictive for usability
|
|
428
|
+
**Decision**: Available as strict mode
|
|
429
|
+
|
|
430
|
+
## Security Considerations
|
|
431
|
+
|
|
432
|
+
1. **Rule Validation**: Validate rule patterns
|
|
433
|
+
2. **Audit Trail**: Immutable audit log
|
|
434
|
+
3. **Escalation Prevention**: Can't grant more permissions than held
|
|
435
|
+
4. **Expiration**: Time-limited approvals
|
|
436
|
+
5. **Scope Limits**: Limit global rules
|
|
437
|
+
|
|
438
|
+
## Testing Strategy
|
|
439
|
+
|
|
440
|
+
1. **Unit Tests**:
|
|
441
|
+
- Rule matching logic
|
|
442
|
+
- Pattern matching
|
|
443
|
+
- Prompt matching
|
|
444
|
+
- Cache behavior
|
|
445
|
+
|
|
446
|
+
2. **Integration Tests**:
|
|
447
|
+
- Full permission flow
|
|
448
|
+
- Persistence
|
|
449
|
+
- Audit logging
|
|
450
|
+
|
|
451
|
+
3. **Security Tests**:
|
|
452
|
+
- Bypass attempts
|
|
453
|
+
- Injection attacks
|
|
454
|
+
- Escalation scenarios
|
|
455
|
+
|
|
456
|
+
## Migration Path
|
|
457
|
+
|
|
458
|
+
1. **Phase 1**: Enhanced rule matching
|
|
459
|
+
2. **Phase 2**: Prompt-based permissions
|
|
460
|
+
3. **Phase 3**: Persistence layer
|
|
461
|
+
4. **Phase 4**: Audit logging
|
|
462
|
+
5. **Phase 5**: CLI management
|
|
463
|
+
|
|
464
|
+
Backward compatible with existing rules.
|
|
465
|
+
|
|
466
|
+
## References
|
|
467
|
+
|
|
468
|
+
- [Claude Code Permission System](https://code.claude.com/docs/en/permissions)
|
|
469
|
+
- [Capability-Based Security](https://en.wikipedia.org/wiki/Capability-based_security)
|
|
470
|
+
- [Principle of Least Privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege)
|