tabby-ai-assistant 1.0.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.
Files changed (134) hide show
  1. package/README.md +232 -0
  2. package/dist/components/chat/chat-input.component.d.ts +65 -0
  3. package/dist/components/chat/chat-interface.component.d.ts +71 -0
  4. package/dist/components/chat/chat-message.component.d.ts +53 -0
  5. package/dist/components/chat/chat-settings.component.d.ts +62 -0
  6. package/dist/components/common/error-message.component.d.ts +11 -0
  7. package/dist/components/common/loading-spinner.component.d.ts +4 -0
  8. package/dist/components/security/consent-dialog.component.d.ts +11 -0
  9. package/dist/components/security/password-prompt.component.d.ts +10 -0
  10. package/dist/components/security/risk-confirm-dialog.component.d.ts +36 -0
  11. package/dist/components/settings/ai-settings-tab.component.d.ts +72 -0
  12. package/dist/components/settings/general-settings.component.d.ts +60 -0
  13. package/dist/components/settings/provider-config.component.d.ts +182 -0
  14. package/dist/components/settings/security-settings.component.d.ts +23 -0
  15. package/dist/components/terminal/ai-toolbar-button.component.d.ts +10 -0
  16. package/dist/components/terminal/command-preview.component.d.ts +15 -0
  17. package/dist/components/terminal/command-suggestion.component.d.ts +16 -0
  18. package/dist/index.d.ts +8 -0
  19. package/dist/index.js +2 -0
  20. package/dist/index.js.LICENSE.txt +18 -0
  21. package/dist/main.d.ts +8 -0
  22. package/dist/providers/tabby/ai-config.provider.d.ts +18 -0
  23. package/dist/providers/tabby/ai-hotkey.provider.d.ts +21 -0
  24. package/dist/providers/tabby/ai-settings-tab.provider.d.ts +11 -0
  25. package/dist/providers/tabby/ai-toolbar-button.provider.d.ts +17 -0
  26. package/dist/services/chat/chat-history.service.d.ts +67 -0
  27. package/dist/services/chat/chat-session.service.d.ts +58 -0
  28. package/dist/services/chat/command-generator.service.d.ts +49 -0
  29. package/dist/services/core/ai-assistant.service.d.ts +88 -0
  30. package/dist/services/core/ai-provider-manager.service.d.ts +119 -0
  31. package/dist/services/core/config-provider.service.d.ts +137 -0
  32. package/dist/services/core/logger.service.d.ts +21 -0
  33. package/dist/services/providers/anthropic-provider.service.d.ts +39 -0
  34. package/dist/services/providers/base-provider.service.d.ts +137 -0
  35. package/dist/services/providers/glm-provider.service.d.ts +91 -0
  36. package/dist/services/providers/minimax-provider.service.d.ts +93 -0
  37. package/dist/services/providers/openai-compatible.service.d.ts +39 -0
  38. package/dist/services/providers/openai-provider.service.d.ts +38 -0
  39. package/dist/services/security/consent-manager.service.d.ts +65 -0
  40. package/dist/services/security/password-manager.service.d.ts +67 -0
  41. package/dist/services/security/risk-assessment.service.d.ts +65 -0
  42. package/dist/services/security/security-validator.service.d.ts +36 -0
  43. package/dist/services/terminal/command-analyzer.service.d.ts +20 -0
  44. package/dist/services/terminal/context-menu.service.d.ts +24 -0
  45. package/dist/services/terminal/hotkey.service.d.ts +28 -0
  46. package/dist/services/terminal/terminal-context.service.d.ts +100 -0
  47. package/dist/types/ai.types.d.ts +107 -0
  48. package/dist/types/provider.types.d.ts +105 -0
  49. package/dist/types/security.types.d.ts +85 -0
  50. package/dist/types/terminal.types.d.ts +150 -0
  51. package/dist/utils/encryption.utils.d.ts +83 -0
  52. package/dist/utils/formatting.utils.d.ts +106 -0
  53. package/dist/utils/validation.utils.d.ts +83 -0
  54. package/integration-test-output.txt +50 -0
  55. package/integration-tests/api-integration.test.ts +183 -0
  56. package/jest.config.js +47 -0
  57. package/package.json +73 -0
  58. package/setup-jest.ts +37 -0
  59. package/src/components/chat/chat-input.component.html +61 -0
  60. package/src/components/chat/chat-input.component.scss +183 -0
  61. package/src/components/chat/chat-input.component.ts +149 -0
  62. package/src/components/chat/chat-interface.component.html +119 -0
  63. package/src/components/chat/chat-interface.component.scss +354 -0
  64. package/src/components/chat/chat-interface.component.ts +224 -0
  65. package/src/components/chat/chat-message.component.html +65 -0
  66. package/src/components/chat/chat-message.component.scss +178 -0
  67. package/src/components/chat/chat-message.component.ts +93 -0
  68. package/src/components/chat/chat-settings.component.html +132 -0
  69. package/src/components/chat/chat-settings.component.scss +172 -0
  70. package/src/components/chat/chat-settings.component.ts +168 -0
  71. package/src/components/common/error-message.component.ts +124 -0
  72. package/src/components/common/loading-spinner.component.ts +72 -0
  73. package/src/components/security/consent-dialog.component.ts +77 -0
  74. package/src/components/security/password-prompt.component.ts +79 -0
  75. package/src/components/security/risk-confirm-dialog.component.html +87 -0
  76. package/src/components/security/risk-confirm-dialog.component.scss +360 -0
  77. package/src/components/security/risk-confirm-dialog.component.ts +96 -0
  78. package/src/components/settings/ai-settings-tab.component.html +140 -0
  79. package/src/components/settings/ai-settings-tab.component.scss +371 -0
  80. package/src/components/settings/ai-settings-tab.component.ts +193 -0
  81. package/src/components/settings/general-settings.component.html +103 -0
  82. package/src/components/settings/general-settings.component.scss +285 -0
  83. package/src/components/settings/general-settings.component.ts +123 -0
  84. package/src/components/settings/provider-config.component.html +95 -0
  85. package/src/components/settings/provider-config.component.scss +60 -0
  86. package/src/components/settings/provider-config.component.ts +206 -0
  87. package/src/components/settings/security-settings.component.html +51 -0
  88. package/src/components/settings/security-settings.component.scss +66 -0
  89. package/src/components/settings/security-settings.component.ts +71 -0
  90. package/src/components/terminal/ai-toolbar-button.component.ts +49 -0
  91. package/src/components/terminal/command-preview.component.ts +185 -0
  92. package/src/components/terminal/command-suggestion.component.ts +128 -0
  93. package/src/index.ts +163 -0
  94. package/src/main.ts +16 -0
  95. package/src/providers/tabby/ai-config.provider.ts +70 -0
  96. package/src/providers/tabby/ai-hotkey.provider.ts +55 -0
  97. package/src/providers/tabby/ai-settings-tab.provider.ts +18 -0
  98. package/src/providers/tabby/ai-toolbar-button.provider.ts +49 -0
  99. package/src/services/chat/chat-history.service.ts +239 -0
  100. package/src/services/chat/chat-session.service.spec.ts +249 -0
  101. package/src/services/chat/chat-session.service.ts +180 -0
  102. package/src/services/chat/command-generator.service.ts +301 -0
  103. package/src/services/core/ai-assistant.service.ts +334 -0
  104. package/src/services/core/ai-provider-manager.service.ts +314 -0
  105. package/src/services/core/config-provider.service.ts +347 -0
  106. package/src/services/core/logger.service.ts +104 -0
  107. package/src/services/providers/anthropic-provider.service.ts +373 -0
  108. package/src/services/providers/base-provider.service.ts +369 -0
  109. package/src/services/providers/glm-provider.service.ts +467 -0
  110. package/src/services/providers/minimax-provider.service.ts +427 -0
  111. package/src/services/providers/openai-compatible.service.ts +394 -0
  112. package/src/services/providers/openai-provider.service.ts +376 -0
  113. package/src/services/security/consent-manager.service.ts +332 -0
  114. package/src/services/security/password-manager.service.ts +188 -0
  115. package/src/services/security/risk-assessment.service.ts +340 -0
  116. package/src/services/security/security-validator.service.ts +143 -0
  117. package/src/services/terminal/command-analyzer.service.ts +43 -0
  118. package/src/services/terminal/context-menu.service.ts +45 -0
  119. package/src/services/terminal/hotkey.service.ts +53 -0
  120. package/src/services/terminal/terminal-context.service.ts +317 -0
  121. package/src/styles/ai-assistant.scss +449 -0
  122. package/src/types/ai.types.ts +133 -0
  123. package/src/types/provider.types.ts +147 -0
  124. package/src/types/security.types.ts +103 -0
  125. package/src/types/terminal.types.ts +186 -0
  126. package/src/utils/encryption.utils.spec.ts +250 -0
  127. package/src/utils/encryption.utils.ts +271 -0
  128. package/src/utils/formatting.utils.ts +359 -0
  129. package/src/utils/validation.utils.spec.ts +225 -0
  130. package/src/utils/validation.utils.ts +314 -0
  131. package/tsconfig.json +45 -0
  132. package/webpack-docker.config.js +42 -0
  133. package/webpack.config.js +59 -0
  134. package/webpack.config.js.backup +57 -0
@@ -0,0 +1,188 @@
1
+ import { Injectable } from '@angular/core';
2
+ import * as CryptoJS from 'crypto-js';
3
+ import { PasswordValidationResult } from '../../types/security.types';
4
+ import { LoggerService } from '../core/logger.service';
5
+
6
+ @Injectable({ providedIn: 'root' })
7
+ export class PasswordManagerService {
8
+ private readonly STORAGE_KEY = 'ai-assistant-password-hash';
9
+ private readonly MAX_ATTEMPTS = 5;
10
+ private readonly LOCKOUT_TIME = 15 * 60 * 1000; // 15分钟
11
+
12
+ private attempts = 0;
13
+ private lockoutUntil: number | null = null;
14
+
15
+ constructor(private logger: LoggerService) {
16
+ this.loadState();
17
+ }
18
+
19
+ /**
20
+ * 设置密码
21
+ */
22
+ setPassword(password: string): void {
23
+ const hash = this.hashPassword(password);
24
+ localStorage.setItem(this.STORAGE_KEY, hash);
25
+ this.logger.info('Password set successfully');
26
+ }
27
+
28
+ /**
29
+ * 验证密码
30
+ */
31
+ async requestPassword(): Promise<boolean> {
32
+ // 检查是否被锁定
33
+ if (this.isLocked()) {
34
+ const remainingTime = this.getRemainingLockoutTime();
35
+ this.logger.warn('Password attempts locked', { remainingTime });
36
+ alert(`账户已锁定,请等待 ${Math.ceil(remainingTime / 60000)} 分钟后再试`);
37
+ return false;
38
+ }
39
+
40
+ // 显示密码输入框
41
+ const password = prompt('请输入密码以执行此操作:');
42
+ if (password === null) {
43
+ // 用户取消
44
+ return false;
45
+ }
46
+
47
+ // 验证密码
48
+ const isValid = await this.verifyPassword(password);
49
+
50
+ if (isValid) {
51
+ this.resetAttempts();
52
+ this.logger.info('Password verified successfully');
53
+ return true;
54
+ } else {
55
+ this.attempts++;
56
+ this.logger.warn('Password verification failed', { attempts: this.attempts });
57
+
58
+ if (this.attempts >= this.MAX_ATTEMPTS) {
59
+ this.lockoutUntil = Date.now() + this.LOCKOUT_TIME;
60
+ this.saveState();
61
+ this.logger.error('Password attempts exceeded, account locked');
62
+ alert(`密码错误次数过多,账户已锁定 ${this.LOCKOUT_TIME / 60000} 分钟`);
63
+ }
64
+
65
+ return false;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * 检查是否有密码保护
71
+ */
72
+ hasPassword(): boolean {
73
+ return !!localStorage.getItem(this.STORAGE_KEY);
74
+ }
75
+
76
+ /**
77
+ * 清除密码
78
+ */
79
+ clearPassword(): void {
80
+ localStorage.removeItem(this.STORAGE_KEY);
81
+ this.resetAttempts();
82
+ this.logger.info('Password cleared');
83
+ }
84
+
85
+ /**
86
+ * 验证密码是否正确
87
+ */
88
+ private async verifyPassword(password: string): Promise<boolean> {
89
+ const storedHash = localStorage.getItem(this.STORAGE_KEY);
90
+ if (!storedHash) {
91
+ // 没有设置密码,允许通过
92
+ return true;
93
+ }
94
+
95
+ const inputHash = this.hashPassword(password);
96
+ return inputHash === storedHash;
97
+ }
98
+
99
+ /**
100
+ * 哈希密码
101
+ */
102
+ private hashPassword(password: string): string {
103
+ // 使用SHA-256哈希密码
104
+ return CryptoJS.SHA256(password).toString();
105
+ }
106
+
107
+ /**
108
+ * 检查是否被锁定
109
+ */
110
+ private isLocked(): boolean {
111
+ return this.lockoutUntil !== null && Date.now() < this.lockoutUntil;
112
+ }
113
+
114
+ /**
115
+ * 获取剩余锁定时间
116
+ */
117
+ private getRemainingLockoutTime(): number {
118
+ if (this.lockoutUntil === null) return 0;
119
+ return Math.max(0, this.lockoutUntil - Date.now());
120
+ }
121
+
122
+ /**
123
+ * 重置尝试次数
124
+ */
125
+ private resetAttempts(): void {
126
+ this.attempts = 0;
127
+ this.lockoutUntil = null;
128
+ this.saveState();
129
+ }
130
+
131
+ /**
132
+ * 保存状态
133
+ */
134
+ private saveState(): void {
135
+ const state = {
136
+ attempts: this.attempts,
137
+ lockoutUntil: this.lockoutUntil
138
+ };
139
+ localStorage.setItem('ai-assistant-password-state', JSON.stringify(state));
140
+ }
141
+
142
+ /**
143
+ * 加载状态
144
+ */
145
+ private loadState(): void {
146
+ try {
147
+ const stateStr = localStorage.getItem('ai-assistant-password-state');
148
+ if (stateStr) {
149
+ const state = JSON.parse(stateStr);
150
+ this.attempts = state.attempts || 0;
151
+ this.lockoutUntil = state.lockoutUntil;
152
+
153
+ // 检查锁定是否过期
154
+ if (this.lockoutUntil && Date.now() >= this.lockoutUntil) {
155
+ this.resetAttempts();
156
+ }
157
+ }
158
+ } catch (error) {
159
+ this.logger.error('Failed to load password state', error);
160
+ }
161
+ }
162
+
163
+ /**
164
+ * 获取验证结果
165
+ */
166
+ getValidationResult(): PasswordValidationResult {
167
+ return {
168
+ valid: this.attempts === 0 && !this.isLocked(),
169
+ attempts: this.attempts,
170
+ locked: this.isLocked(),
171
+ lockExpiry: this.lockoutUntil || undefined
172
+ };
173
+ }
174
+
175
+ /**
176
+ * 获取总尝试次数
177
+ */
178
+ getTotalAttempts(): number {
179
+ return this.attempts;
180
+ }
181
+
182
+ /**
183
+ * 获取失败次数
184
+ */
185
+ getFailedAttempts(): number {
186
+ return Math.max(0, this.attempts - 1);
187
+ }
188
+ }
@@ -0,0 +1,340 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { RiskLevel, RiskAssessment } from '../../types/security.types';
3
+ import { LoggerService } from '../core/logger.service';
4
+
5
+ /**
6
+ * 风险评估服务
7
+ * 负责评估终端命令的安全风险级别
8
+ */
9
+ @Injectable({ providedIn: 'root' })
10
+ export class RiskAssessmentService {
11
+ // 危险模式匹配规则
12
+ private readonly DANGEROUS_PATTERNS = [
13
+ {
14
+ pattern: /rm\s+-rf\s+\//,
15
+ description: '删除根目录',
16
+ severity: RiskLevel.CRITICAL
17
+ },
18
+ {
19
+ pattern: /sudo\s+rm/,
20
+ description: 'sudo删除命令',
21
+ severity: RiskLevel.CRITICAL
22
+ },
23
+ {
24
+ pattern: />\s*\/dev\/null/,
25
+ description: '输出重定向到黑洞',
26
+ severity: RiskLevel.HIGH
27
+ },
28
+ {
29
+ pattern: /chmod\s+777/,
30
+ description: '危险权限修改',
31
+ severity: RiskLevel.HIGH
32
+ },
33
+ {
34
+ pattern: /mv\s+.*\s+\//,
35
+ description: '移动到根目录',
36
+ severity: RiskLevel.HIGH
37
+ },
38
+ {
39
+ pattern: /fork\s*\(/,
40
+ description: 'fork炸弹',
41
+ severity: RiskLevel.CRITICAL
42
+ },
43
+ {
44
+ pattern: /dd\s+if=/,
45
+ description: 'dd命令(可能危险)',
46
+ severity: RiskLevel.HIGH
47
+ },
48
+ {
49
+ pattern: /format\s+/,
50
+ description: '格式化命令',
51
+ severity: RiskLevel.CRITICAL
52
+ },
53
+ {
54
+ pattern: /del\s+\/s\s+/i,
55
+ description: 'Windows删除命令',
56
+ severity: RiskLevel.HIGH
57
+ },
58
+ {
59
+ pattern: /rd\s+\/s\s+/i,
60
+ description: 'Windows删除目录命令',
61
+ severity: RiskLevel.HIGH
62
+ }
63
+ ];
64
+
65
+ // 系统修改命令
66
+ private readonly SYSTEM_COMMANDS = [
67
+ 'rm', 'del', 'rd', 'mv', 'cp', 'chmod', 'chown', 'dd', 'fdisk',
68
+ 'mkfs', 'format', 'mount', 'umount', 'sysctl', 'echo', 'tee'
69
+ ];
70
+
71
+ // 网络命令(可能需要额外检查)
72
+ private readonly NETWORK_COMMANDS = [
73
+ 'curl', 'wget', 'ssh', 'scp', 'ftp', 'telnet', 'nc', 'netcat'
74
+ ];
75
+
76
+ // 只读安全命令
77
+ private readonly READONLY_COMMANDS = [
78
+ 'ls', 'cat', 'grep', 'find', 'ps', 'df', 'du', 'top', 'htop',
79
+ 'pwd', 'whoami', 'which', 'whereis', 'type', 'file', 'stat',
80
+ 'head', 'tail', 'less', 'more', 'sort', 'uniq', 'wc', 'echo'
81
+ ];
82
+
83
+ constructor(private logger: LoggerService) {}
84
+
85
+ /**
86
+ * 评估命令风险
87
+ */
88
+ async assessRisk(command: string): Promise<RiskLevel> {
89
+ const assessment = await this.performAssessment(command);
90
+ return assessment.level;
91
+ }
92
+
93
+ /**
94
+ * 执行详细的风险评估
95
+ */
96
+ async performAssessment(command: string): Promise<RiskAssessment> {
97
+ this.logger.debug('Assessing risk for command', { command });
98
+
99
+ const matchedPatterns = [];
100
+ let maxSeverity = RiskLevel.LOW;
101
+ const reasons: string[] = [];
102
+
103
+ // 1. 检查危险模式
104
+ for (const rule of this.DANGEROUS_PATTERNS) {
105
+ if (rule.pattern.test(command)) {
106
+ matchedPatterns.push({
107
+ pattern: rule.pattern.source,
108
+ match: command.match(rule.pattern)?.[0] || '',
109
+ severity: rule.severity
110
+ });
111
+ reasons.push(`检测到危险模式:${rule.description}`);
112
+
113
+ if (this.getSeverityLevel(rule.severity) > this.getSeverityLevel(maxSeverity)) {
114
+ maxSeverity = rule.severity;
115
+ }
116
+ }
117
+ }
118
+
119
+ // 2. 检查系统修改命令
120
+ const hasSystemCommand = this.hasCommand(command, this.SYSTEM_COMMANDS);
121
+ if (hasSystemCommand && maxSeverity < RiskLevel.MEDIUM) {
122
+ maxSeverity = RiskLevel.MEDIUM;
123
+ reasons.push('包含系统修改命令');
124
+ }
125
+
126
+ // 3. 检查网络命令
127
+ const hasNetworkCommand = this.hasCommand(command, this.NETWORK_COMMANDS);
128
+ if (hasNetworkCommand && maxSeverity < RiskLevel.MEDIUM) {
129
+ maxSeverity = RiskLevel.MEDIUM;
130
+ reasons.push('包含网络命令,可能涉及外部请求');
131
+ }
132
+
133
+ // 4. 检查是否主要是只读命令
134
+ const hasOnlyReadonly = this.hasOnlyReadonlyCommands(command, this.READONLY_COMMANDS);
135
+ if (hasOnlyReadonly && maxSeverity < RiskLevel.MEDIUM) {
136
+ maxSeverity = RiskLevel.LOW;
137
+ reasons.push('仅包含只读安全命令');
138
+ }
139
+
140
+ // 5. 计算风险分数
141
+ const score = this.calculateRiskScore(command, maxSeverity, matchedPatterns);
142
+
143
+ // 6. 生成建议
144
+ const suggestions = this.generateSuggestions(command, maxSeverity, reasons);
145
+
146
+ const assessment: RiskAssessment = {
147
+ level: maxSeverity,
148
+ score,
149
+ reasons,
150
+ patterns: matchedPatterns,
151
+ suggestions
152
+ };
153
+
154
+ this.logger.info('Risk assessment completed', { assessment });
155
+ return assessment;
156
+ }
157
+
158
+ /**
159
+ * 检查命令中是否包含指定命令
160
+ */
161
+ private hasCommand(command: string, cmdList: string[]): boolean {
162
+ const lowerCommand = command.toLowerCase();
163
+ return cmdList.some(cmd => new RegExp(`\\b${cmd}\\b`).test(lowerCommand));
164
+ }
165
+
166
+ /**
167
+ * 检查是否仅包含只读命令
168
+ */
169
+ private hasOnlyReadonlyCommands(command: string, readonlyCmds: string[]): boolean {
170
+ const lowerCommand = command.toLowerCase();
171
+
172
+ // 提取所有命令词
173
+ const words = lowerCommand.split(/\s+/);
174
+ const cmdWords = words.filter(word =>
175
+ !word.startsWith('-') &&
176
+ !word.startsWith('/') &&
177
+ !word.startsWith('.') &&
178
+ !/^\d+$/.test(word)
179
+ );
180
+
181
+ // 检查是否所有命令都在只读列表中
182
+ return cmdWords.every(word =>
183
+ readonlyCmds.some(cmd => word === cmd) ||
184
+ this.isArgumentOrPath(word)
185
+ );
186
+ }
187
+
188
+ /**
189
+ * 检查是否是参数或路径
190
+ */
191
+ private isArgumentOrPath(word: string): boolean {
192
+ return word.startsWith('-') ||
193
+ word.startsWith('/') ||
194
+ word.startsWith('./') ||
195
+ word.startsWith('../') ||
196
+ word.includes(':') ||
197
+ word.includes('=') ||
198
+ /^\d+$/.test(word);
199
+ }
200
+
201
+ /**
202
+ * 计算风险分数
203
+ */
204
+ private calculateRiskScore(command: string, level: RiskLevel, patterns: any[]): number {
205
+ let score = 0;
206
+
207
+ // 基础分数
208
+ switch (level) {
209
+ case RiskLevel.CRITICAL:
210
+ score = 90;
211
+ break;
212
+ case RiskLevel.HIGH:
213
+ score = 70;
214
+ break;
215
+ case RiskLevel.MEDIUM:
216
+ score = 40;
217
+ break;
218
+ case RiskLevel.LOW:
219
+ score = 10;
220
+ break;
221
+ }
222
+
223
+ // 根据匹配的模式调整
224
+ score += patterns.length * 10;
225
+
226
+ // 根据命令长度调整(长命令可能更复杂)
227
+ if (command.length > 200) {
228
+ score += 10;
229
+ }
230
+
231
+ return Math.min(score, 100);
232
+ }
233
+
234
+ /**
235
+ * 生成安全建议
236
+ */
237
+ private generateSuggestions(_command: string, level: RiskLevel, reasons: string[]): string[] {
238
+ const suggestions: string[] = [];
239
+
240
+ if (level === RiskLevel.CRITICAL) {
241
+ suggestions.push('此命令非常危险,可能导致数据丢失或系统损坏');
242
+ suggestions.push('强烈建议在执行前备份重要数据');
243
+ suggestions.push('考虑使用更安全的替代方案');
244
+ } else if (level === RiskLevel.HIGH) {
245
+ suggestions.push('此命令可能修改系统或删除文件');
246
+ suggestions.push('请确认您了解命令的作用');
247
+ suggestions.push('建议先在测试环境中验证');
248
+ } else if (level === RiskLevel.MEDIUM) {
249
+ suggestions.push('此命令可能涉及系统操作');
250
+ suggestions.push('请确保您有适当的权限');
251
+ } else {
252
+ suggestions.push('此命令相对安全');
253
+ }
254
+
255
+ // 根据具体原因添加建议
256
+ if (reasons.some(r => r.includes('网络命令'))) {
257
+ suggestions.push('注意网络安全,避免访问未知来源');
258
+ }
259
+
260
+ if (reasons.some(r => r.includes('权限'))) {
261
+ suggestions.push('检查文件权限,避免给予过高的权限');
262
+ }
263
+
264
+ return suggestions;
265
+ }
266
+
267
+ /**
268
+ * 获取风险级别对应的数值
269
+ */
270
+ private getSeverityLevel(level: RiskLevel): number {
271
+ switch (level) {
272
+ case RiskLevel.LOW:
273
+ return 1;
274
+ case RiskLevel.MEDIUM:
275
+ return 2;
276
+ case RiskLevel.HIGH:
277
+ return 3;
278
+ case RiskLevel.CRITICAL:
279
+ return 4;
280
+ default:
281
+ return 0;
282
+ }
283
+ }
284
+
285
+ /**
286
+ * 检查是否为危险命令
287
+ */
288
+ async isDangerous(command: string): Promise<boolean> {
289
+ const assessment = await this.performAssessment(command);
290
+ return assessment.level === RiskLevel.HIGH || assessment.level === RiskLevel.CRITICAL;
291
+ }
292
+
293
+ /**
294
+ * 获取风险级别描述
295
+ */
296
+ getRiskLevelDescription(level: RiskLevel): string {
297
+ switch (level) {
298
+ case RiskLevel.LOW:
299
+ return '低风险 - 安全命令';
300
+ case RiskLevel.MEDIUM:
301
+ return '中风险 - 需要注意的命令';
302
+ case RiskLevel.HIGH:
303
+ return '高风险 - 危险命令';
304
+ case RiskLevel.CRITICAL:
305
+ return '极高风险 - 极危险命令';
306
+ default:
307
+ return '未知风险';
308
+ }
309
+ }
310
+
311
+ /**
312
+ * 获取风险级别颜色
313
+ */
314
+ getRiskLevelColor(level: RiskLevel): string {
315
+ switch (level) {
316
+ case RiskLevel.LOW:
317
+ return '#28a745'; // 绿色
318
+ case RiskLevel.MEDIUM:
319
+ return '#ffc107'; // 黄色
320
+ case RiskLevel.HIGH:
321
+ return '#fd7e14'; // 橙色
322
+ case RiskLevel.CRITICAL:
323
+ return '#dc3545'; // 红色
324
+ default:
325
+ return '#6c757d'; // 灰色
326
+ }
327
+ }
328
+
329
+ /**
330
+ * 批量评估多个命令
331
+ */
332
+ async assessMultiple(commands: string[]): Promise<{ command: string; level: RiskLevel }[]> {
333
+ const results = [];
334
+ for (const command of commands) {
335
+ const level = await this.assessRisk(command);
336
+ results.push({ command, level });
337
+ }
338
+ return results;
339
+ }
340
+ }
@@ -0,0 +1,143 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { RiskLevel, ValidationResult } from '../../types/security.types';
3
+ import { RiskAssessmentService } from './risk-assessment.service';
4
+ import { ConsentManagerService } from './consent-manager.service';
5
+ import { PasswordManagerService } from './password-manager.service';
6
+ import { LoggerService } from '../core/logger.service';
7
+
8
+ @Injectable({ providedIn: 'root' })
9
+ export class SecurityValidatorService {
10
+ constructor(
11
+ private riskAssessment: RiskAssessmentService,
12
+ private consentManager: ConsentManagerService,
13
+ private passwordManager: PasswordManagerService,
14
+ private logger: LoggerService
15
+ ) {}
16
+
17
+ /**
18
+ * 验证并确认命令执行
19
+ */
20
+ async validateAndConfirm(
21
+ command: string,
22
+ explanation: string,
23
+ _context?: any
24
+ ): Promise<ValidationResult> {
25
+ this.logger.info('Validating command', { command });
26
+
27
+ try {
28
+ // 1. 风险评估
29
+ const assessment = await this.riskAssessment.performAssessment(command);
30
+ const riskLevel = assessment.level;
31
+
32
+ this.logger.debug('Risk assessment completed', { riskLevel, score: assessment.score });
33
+
34
+ // 2. 检查用户同意
35
+ const hasConsent = await this.consentManager.hasConsent(command, riskLevel);
36
+ if (hasConsent) {
37
+ this.logger.info('User consent found, skipping confirmation', { command });
38
+ return {
39
+ approved: true,
40
+ riskLevel,
41
+ skipConfirmation: true,
42
+ timestamp: new Date()
43
+ };
44
+ }
45
+
46
+ // 3. 根据风险级别进行验证
47
+ if (riskLevel === RiskLevel.CRITICAL || riskLevel === RiskLevel.HIGH) {
48
+ // 高风险需要密码确认
49
+ this.logger.warn('High risk command detected, requesting password', { command });
50
+ const passwordValid = await this.passwordManager.requestPassword();
51
+ if (!passwordValid) {
52
+ return {
53
+ approved: false,
54
+ riskLevel,
55
+ reason: 'Invalid password',
56
+ timestamp: new Date()
57
+ };
58
+ }
59
+ } else if (riskLevel === RiskLevel.MEDIUM) {
60
+ // 中风险需要显式确认
61
+ this.logger.info('Medium risk command, requesting user confirmation', { command });
62
+ const confirmed = await this.consentManager.requestConsent(
63
+ command,
64
+ explanation,
65
+ riskLevel
66
+ );
67
+ if (!confirmed) {
68
+ return {
69
+ approved: false,
70
+ riskLevel,
71
+ reason: 'User cancelled',
72
+ timestamp: new Date()
73
+ };
74
+ }
75
+ }
76
+
77
+ // 4. 存储用户同意
78
+ await this.consentManager.storeConsent(command, riskLevel);
79
+
80
+ this.logger.info('Command validation approved', { command, riskLevel });
81
+ return {
82
+ approved: true,
83
+ riskLevel,
84
+ timestamp: new Date()
85
+ };
86
+
87
+ } catch (error) {
88
+ this.logger.error('Command validation failed', error);
89
+ return {
90
+ approved: false,
91
+ riskLevel: RiskLevel.HIGH,
92
+ reason: error instanceof Error ? error.message : 'Validation error',
93
+ timestamp: new Date()
94
+ };
95
+ }
96
+ }
97
+
98
+ /**
99
+ * 检查命令是否安全
100
+ */
101
+ async isCommandSafe(command: string): Promise<boolean> {
102
+ const assessment = await this.riskAssessment.performAssessment(command);
103
+ return assessment.level === RiskLevel.LOW;
104
+ }
105
+
106
+ /**
107
+ * 获取风险级别
108
+ */
109
+ async getRiskLevel(command: string): Promise<RiskLevel> {
110
+ return this.riskAssessment.assessRisk(command);
111
+ }
112
+
113
+ /**
114
+ * 检查是否需要确认
115
+ */
116
+ async requiresConfirmation(command: string): Promise<boolean> {
117
+ const assessment = await this.riskAssessment.performAssessment(command);
118
+ const hasConsent = await this.consentManager.hasConsent(command, assessment.level);
119
+ return !hasConsent && (assessment.level === RiskLevel.MEDIUM ||
120
+ assessment.level === RiskLevel.HIGH ||
121
+ assessment.level === RiskLevel.CRITICAL);
122
+ }
123
+
124
+ /**
125
+ * 清除所有用户同意
126
+ */
127
+ async clearAllConsents(): Promise<void> {
128
+ await this.consentManager.clearAllConsents();
129
+ this.logger.info('All user consents cleared');
130
+ }
131
+
132
+ /**
133
+ * 获取安全统计
134
+ */
135
+ async getSecurityStats(): Promise<any> {
136
+ return {
137
+ totalValidations: await this.consentManager.getTotalValidations(),
138
+ activeConsents: await this.consentManager.getActiveConsentsCount(),
139
+ passwordAttempts: await this.passwordManager.getTotalAttempts(),
140
+ failedAttempts: await this.passwordManager.getFailedAttempts()
141
+ };
142
+ }
143
+ }