@massu/core 0.1.1 → 0.1.2
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/README.md +2 -2
- package/dist/hooks/cost-tracker.js +23 -35
- package/dist/hooks/post-edit-context.js +2 -2
- package/dist/hooks/post-tool-use.js +43 -58
- package/dist/hooks/pre-compact.js +23 -38
- package/dist/hooks/pre-delete-check.js +18 -31
- package/dist/hooks/quality-event.js +23 -35
- package/dist/hooks/session-end.js +62 -78
- package/dist/hooks/session-start.js +33 -42
- package/dist/hooks/user-prompt.js +23 -38
- package/package.json +8 -14
- package/src/adr-generator.ts +9 -2
- package/src/analytics.ts +9 -3
- package/src/audit-trail.ts +10 -3
- package/src/cloud-sync.ts +14 -18
- package/src/commands/init.ts +1 -5
- package/src/cost-tracker.ts +11 -6
- package/src/dependency-scorer.ts +9 -2
- package/src/docs-tools.ts +13 -10
- package/src/hooks/post-edit-context.ts +3 -3
- package/src/hooks/session-end.ts +3 -3
- package/src/hooks/session-start.ts +2 -2
- package/src/memory-db.ts +1351 -23
- package/src/memory-tools.ts +14 -15
- package/src/observability-tools.ts +13 -2
- package/src/prompt-analyzer.ts +9 -2
- package/src/regression-detector.ts +9 -3
- package/src/security-scorer.ts +9 -2
- package/src/sentinel-db.ts +43 -88
- package/src/sentinel-tools.ts +8 -11
- package/src/server.ts +1 -2
- package/src/team-knowledge.ts +9 -2
- package/src/tools.ts +771 -35
- package/src/validate-features-runner.ts +0 -1
- package/src/validation-engine.ts +9 -2
- package/dist/cli.js +0 -7890
- package/dist/server.js +0 -7008
- package/src/__tests__/adr-generator.test.ts +0 -260
- package/src/__tests__/analytics.test.ts +0 -282
- package/src/__tests__/audit-trail.test.ts +0 -382
- package/src/__tests__/backfill-sessions.test.ts +0 -690
- package/src/__tests__/cli.test.ts +0 -290
- package/src/__tests__/cloud-sync.test.ts +0 -261
- package/src/__tests__/config-sections.test.ts +0 -359
- package/src/__tests__/config.test.ts +0 -732
- package/src/__tests__/cost-tracker.test.ts +0 -348
- package/src/__tests__/db.test.ts +0 -177
- package/src/__tests__/dependency-scorer.test.ts +0 -325
- package/src/__tests__/docs-integration.test.ts +0 -178
- package/src/__tests__/docs-tools.test.ts +0 -199
- package/src/__tests__/domains.test.ts +0 -236
- package/src/__tests__/hooks.test.ts +0 -221
- package/src/__tests__/import-resolver.test.ts +0 -95
- package/src/__tests__/integration/path-traversal.test.ts +0 -134
- package/src/__tests__/integration/pricing-consistency.test.ts +0 -88
- package/src/__tests__/integration/tool-registration.test.ts +0 -146
- package/src/__tests__/memory-db.test.ts +0 -404
- package/src/__tests__/memory-enhancements.test.ts +0 -316
- package/src/__tests__/memory-tools.test.ts +0 -199
- package/src/__tests__/middleware-tree.test.ts +0 -177
- package/src/__tests__/observability-tools.test.ts +0 -595
- package/src/__tests__/observability.test.ts +0 -437
- package/src/__tests__/observation-extractor.test.ts +0 -167
- package/src/__tests__/page-deps.test.ts +0 -60
- package/src/__tests__/prompt-analyzer.test.ts +0 -298
- package/src/__tests__/regression-detector.test.ts +0 -295
- package/src/__tests__/rules.test.ts +0 -87
- package/src/__tests__/schema-mapper.test.ts +0 -29
- package/src/__tests__/security-scorer.test.ts +0 -238
- package/src/__tests__/security-utils.test.ts +0 -175
- package/src/__tests__/sentinel-db.test.ts +0 -491
- package/src/__tests__/sentinel-scanner.test.ts +0 -750
- package/src/__tests__/sentinel-tools.test.ts +0 -324
- package/src/__tests__/sentinel-types.test.ts +0 -750
- package/src/__tests__/server.test.ts +0 -452
- package/src/__tests__/session-archiver.test.ts +0 -524
- package/src/__tests__/session-state-generator.test.ts +0 -900
- package/src/__tests__/team-knowledge.test.ts +0 -327
- package/src/__tests__/tools.test.ts +0 -340
- package/src/__tests__/transcript-parser.test.ts +0 -195
- package/src/__tests__/trpc-index.test.ts +0 -25
- package/src/__tests__/validate-features-runner.test.ts +0 -517
- package/src/__tests__/validation-engine.test.ts +0 -300
- package/src/core-tools.ts +0 -685
- package/src/memory-queries.ts +0 -804
- package/src/memory-schema.ts +0 -546
- package/src/tool-helpers.ts +0 -41
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
-
|
|
4
|
-
import { describe, it, expect } from 'vitest';
|
|
5
|
-
import { existsSync } from 'fs';
|
|
6
|
-
import { parsePrismaSchema } from '../schema-mapper.ts';
|
|
7
|
-
import { getResolvedPaths } from '../config.ts';
|
|
8
|
-
|
|
9
|
-
const schemaExists = existsSync(getResolvedPaths().prismaSchemaPath);
|
|
10
|
-
|
|
11
|
-
describe('parsePrismaSchema', () => {
|
|
12
|
-
it('parses the Prisma schema file', () => {
|
|
13
|
-
if (!schemaExists) {
|
|
14
|
-
// No schema in this project - verify it throws gracefully
|
|
15
|
-
expect(() => parsePrismaSchema()).toThrow('Prisma schema not found');
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
const models = parsePrismaSchema();
|
|
19
|
-
expect(models.length).toBeGreaterThan(0);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('finds models with fields', () => {
|
|
23
|
-
if (!schemaExists) return;
|
|
24
|
-
const models = parsePrismaSchema();
|
|
25
|
-
for (const model of models) {
|
|
26
|
-
expect(model.fields.length).toBeGreaterThan(0);
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
});
|
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
-
|
|
4
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
5
|
-
import Database from 'better-sqlite3';
|
|
6
|
-
import { writeFileSync, mkdirSync, rmSync } from 'fs';
|
|
7
|
-
import { join } from 'path';
|
|
8
|
-
import {
|
|
9
|
-
getSecurityToolDefinitions,
|
|
10
|
-
isSecurityTool,
|
|
11
|
-
scoreFileSecurity,
|
|
12
|
-
storeSecurityScore,
|
|
13
|
-
handleSecurityToolCall,
|
|
14
|
-
} from '../security-scorer.ts';
|
|
15
|
-
|
|
16
|
-
function createTestDb(): Database.Database {
|
|
17
|
-
const db = new Database(':memory:');
|
|
18
|
-
db.exec(`
|
|
19
|
-
CREATE TABLE security_scores (
|
|
20
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
21
|
-
session_id TEXT NOT NULL,
|
|
22
|
-
file_path TEXT NOT NULL,
|
|
23
|
-
risk_score INTEGER NOT NULL DEFAULT 0,
|
|
24
|
-
findings TEXT,
|
|
25
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
CREATE TABLE sessions (
|
|
29
|
-
session_id TEXT PRIMARY KEY,
|
|
30
|
-
status TEXT,
|
|
31
|
-
started_at_epoch INTEGER
|
|
32
|
-
);
|
|
33
|
-
`);
|
|
34
|
-
return db;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
describe('security-scorer', () => {
|
|
38
|
-
let db: Database.Database;
|
|
39
|
-
const testDir = '/tmp/security-scorer-test';
|
|
40
|
-
|
|
41
|
-
beforeEach(() => {
|
|
42
|
-
db = createTestDb();
|
|
43
|
-
try {
|
|
44
|
-
rmSync(testDir, { recursive: true, force: true });
|
|
45
|
-
} catch { /* ignore */ }
|
|
46
|
-
mkdirSync(testDir, { recursive: true });
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
afterEach(() => {
|
|
50
|
-
db.close();
|
|
51
|
-
try {
|
|
52
|
-
rmSync(testDir, { recursive: true, force: true });
|
|
53
|
-
} catch { /* ignore */ }
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe('getSecurityToolDefinitions', () => {
|
|
57
|
-
it('returns 3 tool definitions', () => {
|
|
58
|
-
const tools = getSecurityToolDefinitions();
|
|
59
|
-
expect(tools).toHaveLength(3);
|
|
60
|
-
expect(tools.map(t => t.name.split('_').slice(-2).join('_'))).toEqual([
|
|
61
|
-
'security_score',
|
|
62
|
-
'security_heatmap',
|
|
63
|
-
'security_trend',
|
|
64
|
-
]);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('has required fields in tool definitions', () => {
|
|
68
|
-
const tools = getSecurityToolDefinitions();
|
|
69
|
-
tools.forEach(tool => {
|
|
70
|
-
expect(tool.name).toBeTruthy();
|
|
71
|
-
expect(tool.description).toBeTruthy();
|
|
72
|
-
expect(tool.inputSchema).toBeDefined();
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
describe('isSecurityTool', () => {
|
|
78
|
-
it('returns true for security tool names', () => {
|
|
79
|
-
expect(isSecurityTool('massu_security_score')).toBe(true);
|
|
80
|
-
expect(isSecurityTool('massu_security_heatmap')).toBe(true);
|
|
81
|
-
expect(isSecurityTool('massu_security_trend')).toBe(true);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('returns false for non-security tool names', () => {
|
|
85
|
-
expect(isSecurityTool('massu_adr_list')).toBe(false);
|
|
86
|
-
expect(isSecurityTool('massu_unknown')).toBe(false);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe('scoreFileSecurity', () => {
|
|
91
|
-
it('returns 0 for non-existent file', () => {
|
|
92
|
-
const result = scoreFileSecurity('nonexistent.ts', testDir);
|
|
93
|
-
expect(result.riskScore).toBe(0);
|
|
94
|
-
expect(result.findings).toEqual([]);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('detects hardcoded credentials', () => {
|
|
98
|
-
const filePath = join(testDir, 'test.ts');
|
|
99
|
-
writeFileSync(filePath, `const api_key = "sk-1234567890abcdef";\n`);
|
|
100
|
-
|
|
101
|
-
const result = scoreFileSecurity(filePath, testDir);
|
|
102
|
-
expect(result.riskScore).toBeGreaterThan(0);
|
|
103
|
-
expect(result.findings.length).toBeGreaterThan(0);
|
|
104
|
-
expect(result.findings[0].severity).toBe('critical');
|
|
105
|
-
expect(result.findings[0].description).toContain('credential');
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('detects publicProcedure.mutation vulnerability', () => {
|
|
109
|
-
const filePath = join(testDir, 'router.ts');
|
|
110
|
-
writeFileSync(filePath, `export const router = publicProcedure.mutation(async () => {});\n`);
|
|
111
|
-
|
|
112
|
-
const result = scoreFileSecurity(filePath, testDir);
|
|
113
|
-
expect(result.riskScore).toBeGreaterThan(0);
|
|
114
|
-
const mutation = result.findings.find(f => f.description.includes('Mutation without authentication'));
|
|
115
|
-
expect(mutation).toBeDefined();
|
|
116
|
-
expect(mutation?.severity).toBe('critical');
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('detects eval usage', () => {
|
|
120
|
-
const filePath = join(testDir, 'dangerous.ts');
|
|
121
|
-
writeFileSync(filePath, `const result = eval(userInput);\n`);
|
|
122
|
-
|
|
123
|
-
const result = scoreFileSecurity(filePath, testDir);
|
|
124
|
-
expect(result.riskScore).toBeGreaterThan(0);
|
|
125
|
-
const evalFinding = result.findings.find(f => f.description.includes('eval()'));
|
|
126
|
-
expect(evalFinding).toBeDefined();
|
|
127
|
-
expect(evalFinding?.severity).toBe('high');
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('detects dangerouslySetInnerHTML in tsx files', () => {
|
|
131
|
-
const filePath = join(testDir, 'component.tsx');
|
|
132
|
-
writeFileSync(filePath, `<div dangerouslySetInnerHTML={{ __html: html }} />\n`);
|
|
133
|
-
|
|
134
|
-
const result = scoreFileSecurity(filePath, testDir);
|
|
135
|
-
expect(result.riskScore).toBeGreaterThan(0);
|
|
136
|
-
const xss = result.findings.find(f => f.description.includes('XSS risk'));
|
|
137
|
-
expect(xss).toBeDefined();
|
|
138
|
-
expect(xss?.severity).toBe('high');
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('returns 0 for clean file', () => {
|
|
142
|
-
const filePath = join(testDir, 'clean.ts');
|
|
143
|
-
writeFileSync(filePath, `export function add(a: number, b: number) { return a + b; }\n`);
|
|
144
|
-
|
|
145
|
-
const result = scoreFileSecurity(filePath, testDir);
|
|
146
|
-
expect(result.riskScore).toBe(0);
|
|
147
|
-
expect(result.findings).toEqual([]);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('caps risk score at 100', () => {
|
|
151
|
-
const filePath = join(testDir, 'critical.ts');
|
|
152
|
-
writeFileSync(filePath, `
|
|
153
|
-
const password = "hardcoded-secret-12345";
|
|
154
|
-
const token = "sk-1234567890abcdef";
|
|
155
|
-
const apiKey = "another-secret-key-abc";
|
|
156
|
-
publicProcedure.mutation(async () => {});
|
|
157
|
-
eval(userInput);
|
|
158
|
-
exec(\`rm -rf \${dir}\`);
|
|
159
|
-
`);
|
|
160
|
-
|
|
161
|
-
const result = scoreFileSecurity(filePath, testDir);
|
|
162
|
-
expect(result.riskScore).toBeLessThanOrEqual(100);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('blocks path traversal attacks', () => {
|
|
166
|
-
const result = scoreFileSecurity('../../../etc/passwd', testDir);
|
|
167
|
-
expect(result.riskScore).toBe(100);
|
|
168
|
-
expect(result.findings[0].severity).toBe('critical');
|
|
169
|
-
expect(result.findings[0].description).toContain('Path traversal blocked');
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
describe('storeSecurityScore', () => {
|
|
174
|
-
it('stores security score in database', () => {
|
|
175
|
-
storeSecurityScore(db, 'session-123', 'src/test.ts', 42, [
|
|
176
|
-
{ pattern: 'test', severity: 'high', line: 10, description: 'Test finding' },
|
|
177
|
-
]);
|
|
178
|
-
|
|
179
|
-
const row = db.prepare('SELECT * FROM security_scores WHERE session_id = ?').get('session-123') as Record<string, unknown>;
|
|
180
|
-
expect(row.file_path).toBe('src/test.ts');
|
|
181
|
-
expect(row.risk_score).toBe(42);
|
|
182
|
-
|
|
183
|
-
const findings = JSON.parse(row.findings as string) as Array<Record<string, unknown>>;
|
|
184
|
-
expect(findings).toHaveLength(1);
|
|
185
|
-
expect(findings[0].description).toBe('Test finding');
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
describe('handleSecurityToolCall', () => {
|
|
190
|
-
it('handles security_score for file', () => {
|
|
191
|
-
const filePath = join(testDir, 'test.ts');
|
|
192
|
-
writeFileSync(filePath, `export const x = 1;\n`);
|
|
193
|
-
|
|
194
|
-
// Create active session
|
|
195
|
-
db.prepare(`INSERT INTO sessions (session_id, status, started_at_epoch) VALUES (?, ?, ?)`).run(
|
|
196
|
-
'test-session',
|
|
197
|
-
'active',
|
|
198
|
-
Math.floor(Date.now() / 1000)
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
const result = handleSecurityToolCall('massu_security_score', { file_path: filePath }, db);
|
|
202
|
-
const text = result.content[0].text;
|
|
203
|
-
expect(text).toContain('Security Score');
|
|
204
|
-
expect(text).toContain(filePath);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('handles security_heatmap with no data', () => {
|
|
208
|
-
const result = handleSecurityToolCall('massu_security_heatmap', {}, db);
|
|
209
|
-
const text = result.content[0].text;
|
|
210
|
-
expect(text).toContain('No files with risk score');
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('handles security_heatmap with threshold', () => {
|
|
214
|
-
storeSecurityScore(db, 'session-1', 'file1.ts', 50, []);
|
|
215
|
-
storeSecurityScore(db, 'session-1', 'file2.ts', 80, []);
|
|
216
|
-
storeSecurityScore(db, 'session-1', 'file3.ts', 20, []);
|
|
217
|
-
|
|
218
|
-
const result = handleSecurityToolCall('massu_security_heatmap', { threshold: 30 }, db);
|
|
219
|
-
const text = result.content[0].text;
|
|
220
|
-
expect(text).toContain('Security Heat Map');
|
|
221
|
-
expect(text).toContain('file1.ts');
|
|
222
|
-
expect(text).toContain('file2.ts');
|
|
223
|
-
expect(text).not.toContain('file3.ts');
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it('handles security_trend with no data', () => {
|
|
227
|
-
const result = handleSecurityToolCall('massu_security_trend', {}, db);
|
|
228
|
-
const text = result.content[0].text;
|
|
229
|
-
expect(text).toContain('No security scan data');
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it('handles unknown tool name', () => {
|
|
233
|
-
const result = handleSecurityToolCall('massu_security_unknown', {}, db);
|
|
234
|
-
const text = result.content[0].text;
|
|
235
|
-
expect(text).toContain('Unknown security tool');
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
});
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
-
|
|
4
|
-
import { describe, it, expect } from 'vitest';
|
|
5
|
-
import {
|
|
6
|
-
ensureWithinRoot,
|
|
7
|
-
escapeRegex,
|
|
8
|
-
safeRegex,
|
|
9
|
-
globToSafeRegex,
|
|
10
|
-
redactSensitiveContent,
|
|
11
|
-
enforceSeverityFloors,
|
|
12
|
-
MINIMUM_SEVERITY_WEIGHTS,
|
|
13
|
-
} from '../security-utils.ts';
|
|
14
|
-
|
|
15
|
-
describe('ensureWithinRoot', () => {
|
|
16
|
-
const root = '/projects/my-app';
|
|
17
|
-
|
|
18
|
-
it('allows paths within root', () => {
|
|
19
|
-
expect(ensureWithinRoot('src/index.ts', root)).toBe('/projects/my-app/src/index.ts');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('allows nested paths', () => {
|
|
23
|
-
expect(ensureWithinRoot('src/lib/utils/helpers.ts', root)).toBe('/projects/my-app/src/lib/utils/helpers.ts');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('blocks path traversal with ../', () => {
|
|
27
|
-
expect(() => ensureWithinRoot('../../etc/passwd', root)).toThrow('Path traversal blocked');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('blocks path traversal with encoded sequences', () => {
|
|
31
|
-
expect(() => ensureWithinRoot('../../../etc/shadow', root)).toThrow('Path traversal blocked');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('normalizes paths with ./ segments', () => {
|
|
35
|
-
expect(ensureWithinRoot('./src/../src/index.ts', root)).toBe('/projects/my-app/src/index.ts');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('blocks traversal that resolves outside after normalization', () => {
|
|
39
|
-
expect(() => ensureWithinRoot('src/../../../../etc/passwd', root)).toThrow('Path traversal blocked');
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe('escapeRegex', () => {
|
|
44
|
-
it('escapes special regex characters', () => {
|
|
45
|
-
expect(escapeRegex('hello.world')).toBe('hello\\.world');
|
|
46
|
-
expect(escapeRegex('a+b*c')).toBe('a\\+b\\*c');
|
|
47
|
-
expect(escapeRegex('foo(bar)')).toBe('foo\\(bar\\)');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('leaves normal strings unchanged', () => {
|
|
51
|
-
expect(escapeRegex('hello world')).toBe('hello world');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('escapes all PCRE special chars', () => {
|
|
55
|
-
const special = '.*+?^${}()|[]\\';
|
|
56
|
-
const escaped = escapeRegex(special);
|
|
57
|
-
// Every char should be escaped
|
|
58
|
-
expect(escaped).toBe('\\.\\*\\+\\?\\^\\$\\{\\}\\(\\)\\|\\[\\]\\\\');
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
describe('safeRegex', () => {
|
|
63
|
-
it('compiles simple patterns', () => {
|
|
64
|
-
const re = safeRegex('hello|world');
|
|
65
|
-
expect(re).toBeInstanceOf(RegExp);
|
|
66
|
-
expect(re!.test('hello')).toBe(true);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('rejects nested quantifiers (ReDoS)', () => {
|
|
70
|
-
expect(safeRegex('(a+)+')).toBeNull();
|
|
71
|
-
expect(safeRegex('(a*)*')).toBeNull();
|
|
72
|
-
expect(safeRegex('(a+){2,}')).toBeNull();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('rejects excessively long patterns', () => {
|
|
76
|
-
expect(safeRegex('a'.repeat(501))).toBeNull();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('rejects invalid regex syntax', () => {
|
|
80
|
-
expect(safeRegex('(?P<invalid')).toBeNull();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('accepts reasonable patterns', () => {
|
|
84
|
-
expect(safeRegex('\\bctx\\.prisma\\b')).toBeInstanceOf(RegExp);
|
|
85
|
-
expect(safeRegex('import.*from')).toBeInstanceOf(RegExp);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('supports flags', () => {
|
|
89
|
-
const re = safeRegex('hello', 'i');
|
|
90
|
-
expect(re!.test('HELLO')).toBe(true);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
describe('globToSafeRegex', () => {
|
|
95
|
-
it('converts simple glob with single star', () => {
|
|
96
|
-
const re = globToSafeRegex('src/**/*.ts');
|
|
97
|
-
expect(re.test('src/lib/utils.ts')).toBe(true);
|
|
98
|
-
expect(re.test('src/deep/nested/file.ts')).toBe(true);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('does not match across path separators with single star', () => {
|
|
102
|
-
const re = globToSafeRegex('src/*.ts');
|
|
103
|
-
expect(re.test('src/index.ts')).toBe(true);
|
|
104
|
-
expect(re.test('src/lib/index.ts')).toBe(false);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('escapes special regex chars in the glob', () => {
|
|
108
|
-
const re = globToSafeRegex('src/components/(portal)/*.tsx');
|
|
109
|
-
expect(re.test('src/components/(portal)/page.tsx')).toBe(true);
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
describe('redactSensitiveContent', () => {
|
|
114
|
-
it('redacts API keys', () => {
|
|
115
|
-
expect(redactSensitiveContent('key: sk-abc123def456ghij')).toContain('[REDACTED_KEY]');
|
|
116
|
-
expect(redactSensitiveContent('token: ghp_1234567890abcdef')).toContain('[REDACTED_KEY]');
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('redacts email addresses', () => {
|
|
120
|
-
expect(redactSensitiveContent('contact user@example.com')).toContain('[REDACTED_EMAIL]');
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('redacts Bearer tokens', () => {
|
|
124
|
-
expect(redactSensitiveContent('Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.payload.sig'))
|
|
125
|
-
.toContain('[REDACTED_TOKEN]');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('redacts absolute file paths', () => {
|
|
129
|
-
expect(redactSensitiveContent('file at /Users/john/secrets/key.pem')).toContain('[REDACTED_PATH]');
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('redacts connection strings', () => {
|
|
133
|
-
expect(redactSensitiveContent('postgres://admin:s3cret@db.host.com/mydb'))
|
|
134
|
-
.toContain('[REDACTED_CREDENTIALS]');
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it('preserves non-sensitive content', () => {
|
|
138
|
-
const safe = 'This is a normal prompt about implementing a feature';
|
|
139
|
-
expect(redactSensitiveContent(safe)).toBe(safe);
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
describe('enforceSeverityFloors', () => {
|
|
144
|
-
const defaults = { critical: 25, high: 15, medium: 8, low: 3 };
|
|
145
|
-
|
|
146
|
-
it('uses config values when above floor', () => {
|
|
147
|
-
const result = enforceSeverityFloors({ critical: 30, high: 20 }, defaults);
|
|
148
|
-
expect(result.critical).toBe(30);
|
|
149
|
-
expect(result.high).toBe(20);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('enforces minimum floors', () => {
|
|
153
|
-
const result = enforceSeverityFloors({ critical: 0, high: 0, medium: 0, low: 0 }, defaults);
|
|
154
|
-
expect(result.critical).toBe(MINIMUM_SEVERITY_WEIGHTS.critical);
|
|
155
|
-
expect(result.high).toBe(MINIMUM_SEVERITY_WEIGHTS.high);
|
|
156
|
-
expect(result.medium).toBe(MINIMUM_SEVERITY_WEIGHTS.medium);
|
|
157
|
-
expect(result.low).toBe(MINIMUM_SEVERITY_WEIGHTS.low);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('preserves defaults for missing config keys', () => {
|
|
161
|
-
const result = enforceSeverityFloors({ critical: 50 }, defaults);
|
|
162
|
-
expect(result.critical).toBe(50);
|
|
163
|
-
expect(result.high).toBe(15); // default preserved
|
|
164
|
-
expect(result.medium).toBe(8); // default preserved
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('prevents complete disabling of security scoring', () => {
|
|
168
|
-
const result = enforceSeverityFloors(
|
|
169
|
-
{ critical: 0, high: 0, medium: 0, low: 0 },
|
|
170
|
-
defaults
|
|
171
|
-
);
|
|
172
|
-
const totalWeight = Object.values(result).reduce((sum, v) => sum + v, 0);
|
|
173
|
-
expect(totalWeight).toBeGreaterThan(0);
|
|
174
|
-
});
|
|
175
|
-
});
|