@nahisaho/katashiro-security 0.4.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.
@@ -0,0 +1,364 @@
1
+ /**
2
+ * SecurityAnalyzer テスト
3
+ *
4
+ * @requirement REQ-012
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach } from 'vitest';
8
+ import {
9
+ SecurityAnalyzer,
10
+ Action,
11
+ SecurityAnalysis,
12
+ RiskLevel,
13
+ SecurityError,
14
+ } from '../src';
15
+
16
+ describe('SecurityAnalyzer', () => {
17
+ let analyzer: SecurityAnalyzer;
18
+
19
+ beforeEach(() => {
20
+ analyzer = new SecurityAnalyzer();
21
+ });
22
+
23
+ describe('REQ-012-01: リスクレベル評価', () => {
24
+ it('ファイル読み取りはlowリスク', () => {
25
+ const action: Action = {
26
+ type: 'file_read',
27
+ name: 'read file',
28
+ target: '/tmp/test.txt',
29
+ };
30
+
31
+ const result = analyzer.analyze(action);
32
+
33
+ expect(result.riskLevel).toBe('low');
34
+ expect(result.allowed).toBe(true);
35
+ expect(result.requiresConfirmation).toBe(false);
36
+ });
37
+
38
+ it('ネットワークリクエストはmediumリスク', () => {
39
+ const action: Action = {
40
+ type: 'network_request',
41
+ name: 'fetch data',
42
+ target: 'https://api.example.com/data',
43
+ };
44
+
45
+ const result = analyzer.analyze(action);
46
+
47
+ expect(result.riskLevel).toBe('medium');
48
+ expect(result.allowed).toBe(true);
49
+ });
50
+
51
+ it('コマンド実行はhighリスク', () => {
52
+ const action: Action = {
53
+ type: 'command_execute',
54
+ name: 'run command',
55
+ params: { command: 'ls -la' },
56
+ };
57
+
58
+ const result = analyzer.analyze(action);
59
+
60
+ expect(result.riskLevel).toBe('high');
61
+ expect(result.requiresConfirmation).toBe(true);
62
+ });
63
+ });
64
+
65
+ describe('REQ-012-02: 確認が必要なアクション', () => {
66
+ it('highリスクのアクションは確認が必要', () => {
67
+ const action: Action = {
68
+ type: 'command_execute',
69
+ name: 'run command',
70
+ params: { command: 'rm -rf /tmp/test' },
71
+ };
72
+
73
+ const result = analyzer.analyze(action);
74
+
75
+ expect(result.requiresConfirmation).toBe(true);
76
+ expect(result.reasons).toContain('Risk level high requires confirmation');
77
+ });
78
+
79
+ it('lowリスクのアクションは確認不要', () => {
80
+ const action: Action = {
81
+ type: 'file_read',
82
+ name: 'read file',
83
+ target: '/tmp/test.txt',
84
+ };
85
+
86
+ const result = analyzer.analyze(action);
87
+
88
+ expect(result.requiresConfirmation).toBe(false);
89
+ });
90
+ });
91
+
92
+ describe('REQ-012-03: 拒否パターンブロック', () => {
93
+ it('.env ファイルはブロックされる', () => {
94
+ const action: Action = {
95
+ type: 'file_read',
96
+ name: 'read env',
97
+ target: '/project/.env',
98
+ };
99
+
100
+ const result = analyzer.analyze(action);
101
+
102
+ expect(result.allowed).toBe(false);
103
+ expect(result.riskLevel).toBe('critical');
104
+ expect(result.blockReason).toContain('Environment files with secrets');
105
+ });
106
+
107
+ it('node_modules内のファイルはブロックされる', () => {
108
+ const action: Action = {
109
+ type: 'file_write',
110
+ name: 'modify dependency',
111
+ target: '/project/node_modules/lodash/index.js',
112
+ };
113
+
114
+ const result = analyzer.analyze(action);
115
+
116
+ expect(result.allowed).toBe(false);
117
+ expect(result.blockReason).toContain('Dependencies should not be modified');
118
+ });
119
+
120
+ it('.git 内部ファイルはブロックされる', () => {
121
+ const action: Action = {
122
+ type: 'file_write',
123
+ name: 'modify git',
124
+ target: '/project/.git/config',
125
+ };
126
+
127
+ const result = analyzer.analyze(action);
128
+
129
+ expect(result.allowed).toBe(false);
130
+ });
131
+
132
+ it('システム設定ファイルはブロックされる', () => {
133
+ const action: Action = {
134
+ type: 'file_read',
135
+ name: 'read system',
136
+ target: '/etc/passwd',
137
+ };
138
+
139
+ const result = analyzer.analyze(action);
140
+
141
+ expect(result.allowed).toBe(false);
142
+ });
143
+ });
144
+
145
+ describe('REQ-012-04: 許可パターン', () => {
146
+ it('Markdownファイルはlowリスク', () => {
147
+ const action: Action = {
148
+ type: 'file_write',
149
+ name: 'write markdown',
150
+ target: '/project/README.md',
151
+ };
152
+
153
+ const result = analyzer.analyze(action);
154
+
155
+ expect(result.riskLevel).toBe('low');
156
+ expect(result.allowed).toBe(true);
157
+ expect(result.reasons).toContain('Matched allow pattern: **/*.md');
158
+ });
159
+
160
+ it('JSONファイルはlowリスク', () => {
161
+ const action: Action = {
162
+ type: 'file_write',
163
+ name: 'write json',
164
+ target: '/project/config.json',
165
+ };
166
+
167
+ const result = analyzer.analyze(action);
168
+
169
+ expect(result.riskLevel).toBe('low');
170
+ expect(result.allowed).toBe(true);
171
+ });
172
+ });
173
+
174
+ describe('REQ-012-06: ファイル削除は高リスク', () => {
175
+ it('ファイル削除はhighリスク', () => {
176
+ const action: Action = {
177
+ type: 'file_delete',
178
+ name: 'delete file',
179
+ target: '/tmp/test.txt',
180
+ };
181
+
182
+ const result = analyzer.analyze(action);
183
+
184
+ expect(result.riskLevel).toBe('high');
185
+ expect(result.requiresConfirmation).toBe(true);
186
+ expect(result.matchedRules).toContain('file_delete_high_risk');
187
+ });
188
+
189
+ it('ディレクトリ削除もhighリスク', () => {
190
+ const action: Action = {
191
+ type: 'directory_delete',
192
+ name: 'delete directory',
193
+ target: '/tmp/test-dir',
194
+ };
195
+
196
+ const result = analyzer.analyze(action);
197
+
198
+ expect(result.riskLevel).toBe('high');
199
+ expect(result.requiresConfirmation).toBe(true);
200
+ });
201
+ });
202
+
203
+ describe('validateAction', () => {
204
+ it('許可されるアクションは例外を投げない', () => {
205
+ const action: Action = {
206
+ type: 'file_read',
207
+ name: 'read file',
208
+ target: '/tmp/test.txt',
209
+ };
210
+
211
+ expect(() => analyzer.validateAction(action)).not.toThrow();
212
+ });
213
+
214
+ it('ブロックされるアクションはACTION_BLOCKED例外', () => {
215
+ const action: Action = {
216
+ type: 'file_read',
217
+ name: 'read env',
218
+ target: '/project/.env',
219
+ };
220
+
221
+ expect(() => analyzer.validateAction(action)).toThrow(SecurityError);
222
+
223
+ try {
224
+ analyzer.validateAction(action);
225
+ } catch (e) {
226
+ expect((e as SecurityError).code).toBe('ACTION_BLOCKED');
227
+ }
228
+ });
229
+
230
+ it('確認が必要なアクションはCONFIRMATION_REQUIRED例外', () => {
231
+ const action: Action = {
232
+ type: 'file_delete',
233
+ name: 'delete file',
234
+ target: '/tmp/test.txt',
235
+ };
236
+
237
+ expect(() => analyzer.validateAction(action)).toThrow(SecurityError);
238
+
239
+ try {
240
+ analyzer.validateAction(action);
241
+ } catch (e) {
242
+ expect((e as SecurityError).code).toBe('CONFIRMATION_REQUIRED');
243
+ }
244
+ });
245
+ });
246
+
247
+ describe('validateActionWithConfirmation', () => {
248
+ it('確認済みのhighリスクアクションは許可', () => {
249
+ const action: Action = {
250
+ type: 'file_delete',
251
+ name: 'delete file',
252
+ target: '/tmp/test.txt',
253
+ };
254
+
255
+ expect(() => analyzer.validateActionWithConfirmation(action, true)).not.toThrow();
256
+ });
257
+
258
+ it('未確認のhighリスクアクションはCONFIRMATION_DENIED例外', () => {
259
+ const action: Action = {
260
+ type: 'file_delete',
261
+ name: 'delete file',
262
+ target: '/tmp/test.txt',
263
+ };
264
+
265
+ expect(() => analyzer.validateActionWithConfirmation(action, false)).toThrow(SecurityError);
266
+
267
+ try {
268
+ analyzer.validateActionWithConfirmation(action, false);
269
+ } catch (e) {
270
+ expect((e as SecurityError).code).toBe('CONFIRMATION_DENIED');
271
+ }
272
+ });
273
+ });
274
+
275
+ describe('カスタムポリシー', () => {
276
+ it('カスタム拒否パターンを追加できる', () => {
277
+ const customAnalyzer = new SecurityAnalyzer({
278
+ policy: {
279
+ denyPatterns: [
280
+ { pattern: '**/secret/**', description: 'Custom secret directory' },
281
+ ],
282
+ },
283
+ });
284
+
285
+ const action: Action = {
286
+ type: 'file_read',
287
+ name: 'read secret',
288
+ target: '/project/secret/data.txt',
289
+ };
290
+
291
+ const result = customAnalyzer.analyze(action);
292
+
293
+ expect(result.allowed).toBe(false);
294
+ expect(result.blockReason).toContain('Custom secret directory');
295
+ });
296
+
297
+ it('カスタムリスクルールを追加できる', () => {
298
+ const customAnalyzer = new SecurityAnalyzer({
299
+ additionalRules: [
300
+ {
301
+ name: 'custom_browser_rule',
302
+ description: 'Browser navigation is high risk',
303
+ match: { actionTypes: ['browser_navigate'] },
304
+ riskLevel: 'high',
305
+ },
306
+ ],
307
+ });
308
+
309
+ const action: Action = {
310
+ type: 'browser_navigate',
311
+ name: 'navigate',
312
+ target: 'https://example.com',
313
+ };
314
+
315
+ const result = customAnalyzer.analyze(action);
316
+
317
+ expect(result.riskLevel).toBe('high');
318
+ expect(result.matchedRules).toContain('custom_browser_rule');
319
+ });
320
+
321
+ it('ビルトインルールを無効化できる', () => {
322
+ const customAnalyzer = new SecurityAnalyzer({
323
+ useBuiltinRules: false,
324
+ });
325
+
326
+ const action: Action = {
327
+ type: 'file_delete',
328
+ name: 'delete file',
329
+ target: '/tmp/test.txt',
330
+ };
331
+
332
+ const result = customAnalyzer.analyze(action);
333
+
334
+ // ビルトインルールがないのでlowリスク
335
+ expect(result.riskLevel).toBe('low');
336
+ });
337
+ });
338
+
339
+ describe('ポリシー管理', () => {
340
+ it('ポリシーを取得できる', () => {
341
+ const policy = analyzer.getPolicy();
342
+
343
+ expect(policy.allowPatterns).toBeDefined();
344
+ expect(policy.denyPatterns).toBeDefined();
345
+ expect(policy.requireConfirmation).toContain('high');
346
+ });
347
+
348
+ it('リスクルールを取得できる', () => {
349
+ const rules = analyzer.getRiskRules();
350
+
351
+ expect(rules.length).toBeGreaterThan(0);
352
+ expect(rules.find((r) => r.name === 'file_delete_high_risk')).toBeDefined();
353
+ });
354
+
355
+ it('ポリシーを更新できる', () => {
356
+ analyzer.updatePolicy({
357
+ maxRiskLevel: 'high',
358
+ });
359
+
360
+ const policy = analyzer.getPolicy();
361
+ expect(policy.maxRiskLevel).toBe('high');
362
+ });
363
+ });
364
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "esModuleInterop": true
9
+ },
10
+ "include": ["src/**/*"],
11
+ "exclude": ["node_modules", "dist", "tests"]
12
+ }